Lambda表达式
本篇博客是学习过程的学习笔记
Lambda常用抽象接口
* java8 内置的四大核心函数式接口
*
* Consumer<T>:消费型接口
* void accept(T t);
*
* Supplier<T>:供给型接口
* T get();
*
* Function<T, R>:函数型接口
* R apply(T t);
*
* Predicate<T>:断言行接口
* boolean test(T t);
Lambda例子
/**
* @Author fxx
* @Date 2021/10/20 10:14
*/
@SpringBootTest(classes = PushApplication.class)
@RunWith(SpringRunner.class)
public class LambdaTest {
/**
* ->将lambda分成了2部分
* 左边:lambda表达式的参数列表
* 右边:lambda表达式中所需执行的功能,即lambda体
*
* 左右遇一括号省
* 左侧推断类型省
*
* Lambda表达式需要函数式接口的支持
* 函数式接口:接口中只有一个抽象方法的接口,称为函数式接口,可以使用注解@FunctionalInterface修饰,可以检查是否是函数式接口
*
*
*
* java8 内置的四大核心函数式接口
*
* Consumer<T>:消费型接口
* void accept(T t);
*
* Supplier<T>:供给型接口
* T get();
*
* Function<T, R>:函数型接口
* R apply(T t);
*
* Predicate<T>:断言行接口
* boolean test(T t);
*/
/**
* 语法格式一:无参数,无返回值
* () -> System.out.println("Hello");
*/
@Test
public void test01(){
int num = 1; //如果lambda里使用到了外部类变量,需要是final
Runnable r = () -> System.out.println("HelloWorld" + num);
r.run();
}
/**
* 语法格式二:有一个参数,无返回值
*/
@Test
public void test02(){
Consumer<String> con = (x) -> System.out.println(x);
con.accept("尚硅谷");
}
/**
* 语法格式三:若只有一个参数,()可以省略
*/
@Test
public void test03(){
Consumer<String> con = x -> System.out.println(x);
con.accept("尚硅谷");
}
/**
* 语法格式四:有两个以上参数,有返回值,并且lambda体中有多条语句,那么lambda体需要用{}包起来
*/
@Test
public void test04(){
Comparator<Integer> com = (x, y) -> {
System.out.println("lambda接口");
return Integer.compare(x, y);
};
}
/**
* 语法格式五:有两个以上参数,有返回值,若lambda体中只有一条语句,{}和return都可以省略不写
*/
@Test
public void test05(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
}
/**
* 语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文推断出数据类型,即“类型推断”
*/
@Test
public void test06(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
}
/**
* java8 内置的四大核心函数式接口
*
* Consumer<T>:消费型接口
* void accept(T t);
*
* Supplier<T>:供给型接口
* T get();
*
* Function<T, R>:函数型接口
* R apply(T t);
*
* Predicate<T>:断言行接口
* boolean test(T t);
*/
//测试消费型接口
@Test
public void test1(){
happy(1000, m -> System.out.println("本次消费:"+m + "元"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
//测试供给型接口
@Test
public void test2(){
List<Integer> list = getNumList(10, () -> (int) (Math.random() * 100));
for (Integer i:list) {
System.out.println(i);
}
}
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
//测试函数型接口
@Test
public void test3(){
String s = strHandler("\t\t\t 尚硅谷", (str) -> str.trim());
System.out.println(s);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}
//测试断言型接口
@Test
public void test4(){
List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
List<String> stringList = filterStr(list, str -> str.length() > 3);
for (String str :
stringList) {
System.out.println(str);
}
}
//需求:将满足条件的字符串放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();
for (String str : list){
if (pre.test(str)){
strList.add(str);
}
}
return strList;
}
}
方法引用例子
@SpringBootTest(classes = PushApplication.class)
@RunWith(SpringRunner.class)
public class MethodRefTest {
/**
* 方法引用:若Lambda体中的内容有方法已经实现了,我们可以使用《方法引用》
* 可以理解为方法引用是Lambda表达式的另外一种表现形式
*
* 注意:Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!!!!才能用方法引用
*
* 主要有三种语法格式:
* 对象::实例方法名
* 类::静态方法名
* 类::实例方法名,有一个前提,需要参数里,第一个参数是实例方法的调用者,第二个参数是实例方法的参数时,才可以调用
*
* 构造器引用:
* 格式:
* ClassName::new
*
* 注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
*
* 数组引用:
* Type[]::new;
*/
@Test
public void test1(){
//对象::实例方法名
PrintStream ps = System.out;
Consumer<String> con1 = (x) -> ps.println(x);
con1.accept("con1");
//对上面的改写
Consumer<String> con2 = ps::println;
con2.accept("con2");
//继续改写
Consumer<String> con3 = System.out::println;
con3.accept("con3");
}
@Test
public void test2(){
//类::静态方法名
Comparator<Integer> com1 = (x, y) -> Integer.compare(x, y);
//改写
Comparator<Integer> com2 = Integer::compare;
}
@Test
public void test3(){
//类::实例方法名
BiPredicate<String, String> bp1 = (x, y) -> x.equals(y);
//改写
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("xxx", "uuu"));
}
@Test
public void test4(){
//构造器引用
Supplier<FileSystem> sup1 = () -> new FileSystem();
//改写
Supplier<FileSystem> sup2 = FileSystem::new; //调用的是哪个构造器取决于抽象接口
FileSystem fileSystem = sup2.get();
System.out.println(fileSystem);
}
@Test
public void test5(){
//数组引用
Function<Integer, String[]> fun1 = (x) -> new String[x];
String[] strs = fun1.apply(10);
System.out.println(strs.length);
//改写
Function<Integer, String[]> fun2 = String[]::new;
String[] strs2 = fun2.apply(10);
System.out.println(strs2.length);
}
}
Stream API
集合讲的是数据,流讲的是计算
注意:
Stream
自己不会存储元素Stream
不会改变源对象。相反,他们会返回一个持有结果的新Stream
Stream
操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
Stream操作的三个步骤:
- 创建
Stream
:一个数据源(如:集合、数组),获取一个流 - 中间操作:一个中间操作链,对数据源的数据进行处理
- 终端操作:一个终端操作,执行中间操作链,并产生结果
例子
@SpringBootTest(classes = PushApplication.class)
@RunWith(SpringRunner.class)
public class StreamAPITest {
List<FileSystem> fileSystems = Arrays.asList(
new FileSystem("文1", 200.0, 10),
new FileSystem("文2", 300000.666, 20),
new FileSystem("文3", 0.6, 99),
new FileSystem("文4", 1999.3, 1),
new FileSystem("文5", 4888.666, 66666)
);
//创建Stream:4种方式
@Test
public void test1(){
//1.可以通过Collection系列集合提供的stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2.通过Arrays中的静态方法stream()获取数组流
FileSystem[] fileSystems = new FileSystem[10];
Stream<FileSystem> stream2 = Arrays.stream(fileSystems);
//3.通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
//4.创建无限流
//迭代
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.limit(10).forEach(System.out::println);
//生成
Stream.generate(() -> Math.random())
.limit(5)
.forEach(System.out::println);
}
/**
*
* 中间操作
* 筛选与切片
* filter:接收Lambda,从流中排除某些元素
* limit:截断流,使其元素不超过给定数量
* skip(n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
* distinct:筛选,通过流所生成的元素的hashCode()和equals()去除重复元素(自定义对象需要重写hashCode和equals)
*
* 映射
* map:接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
* flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个新流,针对Stream<Stream<T>>的操作,
* 将多次返回的小流,合并成一个新的大流
*
* 排序
* sorted():自然排序
* sorted(Comparator com):定制排序
*
*/
//内部迭代:迭代操作由Stream API完成
@Test
public void test2(){
Stream<FileSystem> fileSystemStream = fileSystems.stream()
//中间操作
.filter((f) -> f.getFileSize() > 300);
//终止操作:一次性执行全部内容,即《惰性求值》
fileSystemStream.forEach(System.out::println);
}
//外部迭代
@Test
public void test3(){
Iterator<FileSystem> iterator = fileSystems.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void test4(){
Stream<FileSystem> fileSystemStream = fileSystems.stream()
//中间操作
.filter((f) -> {
System.out.println("短路");
return f.getFileSize() > 300;
}).limit(2); //找到2个之后就不会再迭代
//终止操作:一次性执行全部内容,即《惰性求值》
fileSystemStream.forEach(System.out::println);
}
@Test
public void test5(){
List<String> list = Arrays.asList("aaa", "bbb", "ccc","ddd");
list.stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println);
System.out.println("--------------------------------");
fileSystems.stream()
.map(FileSystem::getFileName)
.forEach(System.out::println);
System.out.println("--------------------------------");
//这里map出来是Stream<Stream<Character>>
list.stream() //map之后的结果是{{a,a,a}{b,b,b}{c,c,c}}
.map(StreamAPITest::filterCharacter).forEach((sm) -> sm.forEach(System.out::println));
System.out.println("------------------------------");
//上面代码改写
list.stream() //用flatMap将{{a,a,a}{b,b,b}{c,c,c}}全部小流合成一个大流{a,a,a,b,b,b,c,c,c}
//即从Stream<Stream<T>>(二维)变成了Stream<T>(一维)
//和add(Object obj),addAll(Collection coll)差不多
.flatMap(StreamAPITest::filterCharacter)
.forEach(System.out::println);
}
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
@Test
public void test6(){
List<String> list = Arrays.asList("zzz", "ddd", "aaa", "ggg", "bbb", "eee");
//自然排序
list.stream()
.sorted()
.forEach(System.out::println);
System.out.println("--------------------------");
//定制排序
fileSystems.stream()
.sorted((f1, f2) -> f1.getFileSize().compareTo(f2.getFileSize()))
.forEach(System.out::println);
}
/**
* 终止操作
* 查找与匹配
* allMatch:检查是否匹配所有元素
* anyMatch:检查是否至少匹配一个元素
* noneMatch:检查是否没用匹配任何元素
* findFirst:返回第一个元素
* findAny:随机返回流中的一个元素
* count:返回流中元素的总个数
* max:返回流中最大值
* min:返回流中最小值
*/
@Test
public void test7(){
boolean b1 = fileSystems.stream()
.allMatch((e) -> e.getChunkNum() > 10);
System.out.println(b1);
boolean b2 = fileSystems.stream()
.anyMatch((e) -> e.getChunkNum() == 20);
System.out.println(b2);
boolean b3 = fileSystems.stream()
.noneMatch((e) -> e.getChunkNum() < 0);
System.out.println(b3);
Optional<FileSystem> first = fileSystems.stream()
.sorted((f1, f2) -> Double.compare(f1.getFileSize(), f2.getFileSize()))
.findFirst();
if (first.isPresent()){
System.out.println(first.get());
}
Optional<FileSystem> any = fileSystems.stream()
.sorted((f1, f2) -> Double.compare(f1.getFileSize(), f2.getFileSize()))
.findAny();
if (any.isPresent()){
System.out.println(any.get());
}
long count = fileSystems.stream()
.count();
System.out.println(count);
Optional<FileSystem> max = fileSystems.stream()
.max((f1, f2) -> Double.compare(f1.getChunkNum(), f2.getChunkNum()));
System.out.println(max);
Optional<FileSystem> min = fileSystems.stream()
.min((f1, f2) -> Double.compare(f1.getChunkNum(), f2.getChunkNum()));
System.out.println(min);
}
/**
* 归约
* reduce(T identity, BinaryOperator) / reduce(BinaryOperator):可以将流中元素反复结合起来,得到一个值,可以做统计
*
*/
@Test
public void test8(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//将0放到x的位置,stream里的元素放到y,算出结果之后又放到x,拿出下一个元素继续计算
Integer sum = list.stream().reduce(0, (x, y) -> x+y);
System.out.println(sum); //55
System.out.println("------------------------");
//map+reduce模式,可用于搜索
Optional<Double> op = fileSystems.stream()
.map(FileSystem::getFileSize)
.reduce(Double::sum);
if (op.isPresent()){
System.out.println(op.get());
}
}
/**
* 收集
* collect:将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
* Collector接口有一个工具类Collectors,提供了很多收集器
*/
@Test
public void test9(){
List<FileSystem> list = fileSystems.stream()
.collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println("-----------------------------");
Set<FileSystem> set = fileSystems.stream()
.collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println("------------------------------");
HashSet<String> hs = fileSystems.stream()
.map(FileSystem::getFileName)
.collect(Collectors.toCollection(HashSet::new));
hs.forEach(System.out::println);
System.out.println("------------------------------");
//总数
Long count = fileSystems.stream()
.collect(Collectors.counting());
System.out.println(count);
System.out.println("-------------------------");
//平均值
Double avg = fileSystems.stream()
.collect(Collectors.averagingDouble(FileSystem::getFileSize));
System.out.println(avg);
System.out.println("-----------------------------");
//总和
Double sum = fileSystems.stream()
.collect(Collectors.summingDouble(FileSystem::getFileSize));
System.out.println(sum);
System.out.println("-----------------------------");
//最大值
Optional<FileSystem> max = fileSystems.stream()
.collect(Collectors.maxBy((f1, f2) -> Double.compare(f1.getFileSize(), f2.getFileSize())));
System.out.println(max.get());
System.out.println("-----------------------------");
//最小值
Optional<FileSystem> min = fileSystems.stream()
.collect(Collectors.minBy((f1, f2) -> Double.compare(f1.getFileSize(), f2.getFileSize())));
System.out.println(min.get());
System.out.println("-----------------------------");
//分组
Map<Integer, List<FileSystem>> collect = fileSystems.stream()
.collect(Collectors.groupingBy(FileSystem::getChunkNum));
System.out.println(collect);
System.out.println("------------------------------");
//多级分组:套娃
Map<Integer, Map<String, List<FileSystem>>> map = fileSystems.stream()
.collect(Collectors.groupingBy(FileSystem::getChunkNum, Collectors.groupingBy((f) -> {
if (f.getFileSize() <= 200) {
return "小文件";
} else if (f.getFileSize() <= 2000) {
return "大文件";
} else {
return "超大文件";
}
})));
System.out.println(map);
System.out.println("------------------------------");
//分区
Map<Boolean, List<FileSystem>> fileMap = fileSystems.stream()
.collect(Collectors.partitioningBy((f) -> f.getFileSize() > 200));
System.out.println(fileMap);
System.out.println("------------------------------");
//多种计算属性
DoubleSummaryStatistics coll = fileSystems.stream()
.collect(Collectors.summarizingDouble(FileSystem::getChunkNum));
System.out.println(coll.getAverage());
System.out.println(coll.getMax());
System.out.println(coll.getMin());
System.out.println("--------------------------------");
//拼接
String str = fileSystems.stream()
.map(FileSystem::getFileName)
.collect(Collectors.joining(","));
//下面这种方式支持加前后缀
// .collect(Collectors.joining(",", "====", "xxxxx"));
System.out.println(str);
}
//并行流:使用了fork-join,多线程操作,更快,但是会占用cpu
@Test
public void test10(){
Instant start = Instant.now();
LongStream.rangeClosed(0, 100000000000L)
.parallel() //并行流
.reduce(0, Long::sum);
Instant end = Instant.now();
System.out.println("耗费时间:" + Duration.between(start, end).toMillis()); //17秒
}
}
并行流
分而治之,将大任务分成多个小任务,给多个线程执行
通过parallel()
和sequential()
可以在顺序流和并行流之间切换
/**
*
* Optional容器类常用方法:
* Optional.of(T t):创建一个Optional实例
* Optional.empty():创建一个空的Optional实例
* Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
* orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
* map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
* flatMap(Function mapper):与map类似,要求返回值必须是Optional
*
*/
``