java8 Lambda表达式以及Stream 流
Lambda表达式
Lambda表达式规则
-
Lambda表达式可以看作是一段可以传递的代码,
-
Lambda表达式只能用于函数式接口,而函数式接口只有一个抽象方法,所以可以省略方法名,参数类型等
-
Lambda格式:(形参列表) -> Lambda体 ; 在java中Lambda表达式本质是函数式接口的实现
Runnable runnable = () -> System.out.println("执行任务") ;
- 形参列表是接口中的抽象方法的形参列表
- Lambda体 是重写的抽象方法的方法体
-
Lambda语法:
- 形参列表 中 参数类型可以省略,如果只有一个参数,小括号可以省略
- lambda体如果只有一行语句,return和 {} 都可以省略
java常见的函数式接口,可以根据自己的业务需求重新这些方法简化代码
-
Consumer,消费型接口,有参数无返回,对参数进行某种操作
void accept(T t);
-
Supplier,供给型接口,无参有返回,返回某个对象
T get();
-
Function,函数型接口,有参有返回,对 T类型的参数进行操作返回 R类型的结果
R apply(T t); private static void extracted() { // Function<T, R> T为入参,R为返回结果 Function<Integer,String> function = (in)->{return in+"123";}; String apply = function.apply(12); System.out.println(apply); //12123 }
-
Predicate,断定型接口,有参有返回,输入参数T,返回boolean值,用于判断对象是否满足某个条件
boolean test(T t);
-
其他函数式接口
- BiConsumer,功能和Consumer类似,只是多一个参数
- BiPredicate,功能和Predicate类似,同样只是多了一个参数
- BiFunction,功能和Function类似,同样只是多了一个参数
- IntFunction、LongFunction、DobleFunction、ToIntFunction、ToLongFunction、ToDobleFunction 返回对应类型的数据
方法引用
-
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
-
方法引用是对Lambda的进一步简化,本质上就是Lambda表达式,而 Lambda 是函数式接口的实例,所以方法引用也是函数式接口的实例
-
语法为: 类(对象): : 方法名
-
方法引用 不需要方法的参数,调用可分为三种情况
- 情况1,对象 : : 非静态方法
-
情况2,类 : : 非静态方法 (在面向对象oop中是不允许的)
- 情况3,类 : : 静态方法
- 情况1和情况2 方法引用的要求:接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型完全相同
如下三种写法,从直接new到方法引用,代码的逻辑是完全一致的
private static void extracted1() {
Consumer<String> consumer =new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("写法1");
}
private static void extracted2() {
Consumer<String> consumer= (s)->System.out.println(s);
consumer.accept("写法2");
}
private static void extracted3() {
//方法引用是对Lambda的进一步简化,用来替换Lambda表达式,因为System.out对象的println方法已经实现过了,所以只需要直接调用就好了,不需要重新实现
//接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型完全相同,如下参数都是一个字符串,返回值都是void,所以可以使用参数引用
Consumer<String> consumer= System.out::println;
consumer.accept("写法3");
}
构造器引用
类似与方法应用,参数列表和返回值匹配就可以使用构造器引用,因为构造器也是方法
类名::new
同理,数组引用,可以把数组看作一个类
private static void extracted5() {
//下面两种写法都是创建一个 10个长的数组
Function<Integer,String[]> function = in-> new String[in];
String[] apply = function.apply(10);
Function<Integer,String[]> function1 = String[]::new;
String[] apply1 = function1.apply(10);
}
stream
Java8 Stream
- 与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。
- Java 8 中的 Stream 是对集合(Collection)对象功能的增强,使用的是函数式编程模式,它可以被用来对集合进行链状流式的操作
- 专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
- Stream 不会存储数据,也不会改变原对象,每次操作都回返回一个新的stream
- Stream 分为顺序流和并行流
一个流式处理可以分为三个部分:
- 获取一个数据源→ 数据转换 → 执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
转换成流
- 创建流的五种方式:
- 通过 stream 方法把 集合 或数组 (Arrays.stream())转换为流
- 通过 Stream.of 方法直接传入多个元素构成一个流;( 例如:Stream.of(10,20,30); )
- 通过 IntStream 或 DoubleStream 构造基本类型的流。( 例如:IntStream.of(10,20,30); )
- 创建无限流,无限流的使用较少
- 通过 Stream.iterate 方法使用迭代的方式构造一个无限流,然后使用 limit 限制流元素个数,
- 通过 Stream.generate 方法从外部传入一个提供元素的 供应商来构造无限流,然后使用 limit 限制流元素个数
中间操作
-
过滤:
-
filter,根据条件过滤
-
limit,操作也类似于SQL语句中的LIMIT关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,
-
skip,操作与limit操作相反,如同其字面意思一样,是跳过前n个元素
-
distinct,操作类似于我们在写SQL语句时,添加的DISTINCT关键字,用于去重处理
public class StreamEntity { private String name; private Integer age; private String high; } public static void test() { //向集合中初始化了一些 StreamEntity 对象,以供操作 List<StreamEntity> list = getList(); //过滤 年龄大于25的对象 ,后续使用lambda打印 list.stream().filter(a -> a.getAge() > 25).forEach(a -> System.out.println(a)); //截取 集合中前两条数据 ,后续使用方法引用打印 list.stream().limit(2).forEach(System.out::println); //跳过前两条数据 list.stream().skip(2).forEach(System.out::println); //筛选,通过对象的hashcode和equals对集合中的数据进行去重 list.stream().distinct().forEach(System.out::println); }
-
-
映射,包含两类映射操作:map和flatMap,就像sql可以仅输出我们需要的字段数据一样
- map可以在filter筛选的基础之上,通过map将实体映射成为我们需要的字符串
- flatMap与map的区别在于 flatMap是将一个流中的每个值都转成一个个流(流中的元素也有可能是流),然后再将这些流扁平化成为一个流,有点类似与集合的addAll,调用addAll向集合中添加集合,会把集合中的每一个参数都加到目标集合,而不是把集合对象加入到目标集合中
//将元素中转换为其他形式或者提取信息 ,如下:提取对象中的年龄,并加1 list.stream().map(a->a.getAge()+1).forEach(System.out::println); //flatMap List<List<StreamEntity>> res =new ArrayList<>(); res.add(list); res.add(list); //这里已经把集合中的集合全部拆出来了 Stream<String> objectStream = res.stream().flatMap(a -> a.stream().map(b->b.getName())); objectStream.forEach(System.out::println);
-
排序
-
sorted(),进行自然排序,如果排序的对象没有实现 Comparator ,也就是没有排序规则,会报错
-
sorted(Comparator<? super T> comparator),如果排序的对象没有实现 Comparator ,也可以传入一个比较器
//按照年龄自然排序 list.stream().map(a->a.getAge()).sorted().forEach(System.out::println); //传入比较器 list.stream().sorted((a, b) -> a.getAge() - b.getAge()).forEach(System.out::println); //使用方法引用为: list.stream().sorted(Comparator.comparingInt(StreamEntity::getAge)).forEach(System.out::println);
-
终止操作(终端操作)
-
不调用终止操作,中间操作是不会执行的,调用终止操作产生结果后,stream就不能再被使用了
-
查找
- allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true
- anyMatch则是检测是否存在一个或多个满足指定的参数行为,如果满足则返回true
- noneMatch用于检测是否不存在满足指定行为的元素,如果不存在则返回true
- findFirst用于返回满足条件的第一个元素,findFirst不携带参数,具体的查找条件可以通过filter设置
- findAny相对于findFirst的区别在于,findAny不一定返回第一个,而是返回任意一个
// boolean b = list.stream().map(a -> a.getAge()).allMatch(a -> a > 30); // 用于返回满足条件的第一个元素,查找条件可以通过filter设置 Optional<Integer> first = list.stream().map(a -> a.getAge()).filter(a -> a > 30).findFirst(); Integer integer = first.get()
- count 返回流中的元素个数
- max 返回流中的最大值
- min 返回流中的最小值
- forEach 内部迭代
long count = list.stream().count(); //根据自定义比较器求最大 StreamEntity s1 = list.stream().max((a, b) -> a.getAge() - b.getAge()).get(); //根据自定义比较器求最小 StreamEntity s2 = list.stream().min((a, b) -> a.getAge() - b.getAge()).get(); //迭代 list.stream().forEach(a-> System.out.println(a.getAge()+1));
-
归约
- reduce 可以将流中的元素反复结合起来,得到一个值
// 获取年龄的总和 Integer s1 = list.stream().map(a -> a.getAge()).reduce((b, d) -> b + d).get(); // reduce 的一个参数是初始值 Integer s2 = list.stream().map(a -> a.getAge()).reduce(0, (b, d) -> b + d);
-
收集
- 归约 ,收集器也提供了相应的归约操作
- collect 收集,需要用过Collectors中返回的Collector,将结果收集到指定的结果集中,例如 list、map、set 等
//转换为 list List<StreamEntity> collect = list.stream().collect(Collectors.toList()); //转化为 map Map<Integer, StreamEntity> collect1 = list.stream().collect(Collectors.toMap(a -> a.getAge(), a -> a)); //转换为 set Set<String> collect2 = list.stream().map(a -> a.getName()).collect(Collectors.toSet());
-
分组,在数据库操作中,我们可以通过GROUP BY关键字对查询到的数据进行分组,java8的流式处理也为我们提供了这样的功能,Collectors.groupingBy来操作集合
- 分区,可以看做是分组的一种特殊情况,在分区中key只有两种情况:true或false,目的是将待分区集合按照条件一分为二
Optional
- 参考 Google 的 Guava,用于尽量避免空指针异常
- Optional 是一个容器类,用于保存任意类型的值,代表这个值存在,或者保存null,代表值不存在,
常见方法:
-
创建Optional 对象
-
empty() 创建空的实例
-
of(T t),t不能为空
-
ofNullable(T t)
-
-
获取Optional 对象的值
- get方法会在值为null的时候抛出异常,可以用ifPresent()方法验证值是否为空
- orElse()方法可以在创建实例时就指定返回的默认值