Java服务_java中的Lambda表达式与Stream流函数总结
1.Lambda表达式
1.1 Lambda表达式概述
Lambda表达式就是函数式编程。
面向对象编程的思想强调的是对象,必须通过对象的形式来做一些事情,比如调用函数之类的,一般情况会比较复杂。函数编程思想就是尽量忽略对象的复杂用法,通过一段代码完成面向对象想要做的代码量。
函数编程标准格式为:(参数列表) -> {方法代码}
- 小括号内的语法与传统方法参数列表一直,没有参数就留空,有多个参数就用逗号分隔;
- 大括号内的语法与传统方法体要求一致;
- 箭头是新引入的语法格式,方向固定,代表指向动作。
线程案例:
1 | // 新建线程方法正常书写格式 |
比较器案例:
1 | List<Integer> list = new ArrayList<>(); |
1.2 Lambda表达式使用条件
首先必须是接口,然后接口中有且只有一个抽象方法,这样的接口才能使用Lambda表达式,这种接口叫函数式接口。
Lambda表达式的省略格式:
- 小括号中的形参类型可以省略,也可以不省略;
- 如果小括号中只有一个参数,那么小括号可以省略;
- 如果大括号中只有一条语句,那么大括号、分号、return可以一起省略。
线程案例:
1 | new Thread(() -> System.out.println("省略")).start(); |
比较器案例:
1 | Collections.sort(list, (o1, o2) -> o2 - o1); |
1.3 Lambda表达式表现形式
1.变量的形式:变量的类型为函数式接口,就么可以复制一个Lambda表达式。【不常用】
1 | // 变量的形式-比较器 |
2.参数的形式:方法的形参类型为函数式接口,就可以传入一个Lambda表达式。【常用】
1 | // 将函数式接口类型的形参类型,传给Collections |
3.返回值的形式:方法的返回值类型为函数式接口,就可以返回一个Lambda表达式。【常用】
1 | // 定义一个方法 |
1.Stream流引入
1.1 Stream流概述
基于Lambda所带来的函数式编程,又引入了一个全新的Stream概念,用于解决集合类库既有的弊端。
现有一个需求案例:将list集合中姓张的元素过滤到一个新的集合中,然后将过滤出来的姓张的元素中,再过滤出来长度为3的元素,存储到一个新的集合中。
常规方法:
1 | // 已知的知识来解决需求 |
用Stream流操作集合,获取流,过滤操作,打印输出:
1 | list1.stream().filter(name -> name.startsWith("张")) |
1.2 获取Stream流
一般是根据集合来获取Stream流,比如集合Collection接口中有一个default Stream<E> stream()方法,可以获取流:
1.根据List获取流
2.根据Set获取流
3.根据Map获取流
3.1根据Map集合的键来获取流
3.2根据Map集合的值获取流
3.3根据Map集合的键值对对象获取流
4.根据数组获取流
1.根据List获取流
1 | // 创建List集合 |
2.根据Set获取流
1 | // 创建List集合 |
3.根据Map获取流
本质还是将Map中的key、value、entry转化为set
1 | // 创建Map集合 |
4.根据数组获取流
1 | // 根据数组获取流 |
1.3 收集Stream流
将集合转化成Stream流进行各种非终结操作之后,还可以将Stream流中的数据收集到单例集合中去,最常用的就是转换为List集合和Set集合。案例:
1 | List<String> list2 = new ArrayList<>(); |
1.4 Stream流方法大全
1 | Stream<T> filter(Predicate<? super T> predicate);//过滤流中的元素,返回一个新的流。 |
1 | package java.util.stream; |
3.工程使用案例
3.1 自定义排序
sorted()方法配合匿名自定义排序方法,使得流中数据按自定义方法排序,显得非常优雅。
1 | return factLogicInfoList.stream().sorted((f1, f2) -> { |
3.2 对Map按照key或者value排序
1.List转成Map案例:
1 | private static Map<String, Task> taskMap_duplicates(List<Task> tasks) { |
上述案例使用Collectors.toMap方法将Stream流转化为一个HashMap并返回,其中Task::getTitle表示获取Task元素对象的title属性作为key,identity()表示将Task元素对象本身作为value,(t1, t2) -> t2表示多个值关联到同一个键时取后一个值存储结果Map中。
2.List转成顺序Map案例:
1 | public Map<String, Task> collectToMap(List<Task> tasks) { |
上述案例使用toMap方法变体,将数据按顺序存入LinkedHashMap中。
3.Map转成顺序Map案例:
1 | Map result = map.entrySet().stream() |
默认情况下, Collectors.toMap将返回一个HashMap,但是HashMap中数据无顺序,在这里通过使用toMap方法的另一个变体来使用LinkedHashMap存储有序数据;另外此变体允许我们指定一个合并方法(oldValue, newValue) -> oldValue来处理多个值关联到同一个键的冲突问题。
4.按key排序案例:
1 | public class SortByKeyExample { |
第二种写法没那么优雅,但是同样有用,就是按顺序将Stream流元素放入LinkedHashMap中,该方法中使用了forEachOrdered()方法,这个方法本身其实挺好用的。
5.按照value排序:
1 | public class SortByValueExample { |
按照value排序,此处自定义按照value大小的倒序来存放。
3.3 对集合中元素进行求和
1 | public class ReduceDemo { |
在上述代码中,在reduce里的第一个参数声明为初始值,第二个参数接收一个lambda表达式,代表当前流中的两 个元素,它会反复相加每一个元素,直到流被归约成一个终结果。优化成如下写法也是一样的效果:
1 | Integer reduce = integers.stream().reduce(0,Integer::sum); |
3.4 对集合中元素进行分组
1.单级分组
1 | // 通过性别对学生进行分组 |
通过Collectors.groupingBy()实现类似数据库中的group by分组操作。
2.多级分组
1 | // 在通过性别分组之后,再根据是否通过考试分组 |
对于groupingBy()它提供了两个参数的重载方法,可以用于完成儿级分组,这个重载方法在接收普通函数之外,还会再接收一个Collector.groupingBy()类型的参数,其会将内层分组(第二个参数)结果,传递给外层分组(第一个参数)作为其继续分组的依据。多个二级分组嵌套就成了多级分组。
3.多级分组变形
在日常开发中,我们很有可能不是需要返回一个数据集合,还有可能对数据进行汇总操作。对于二级分组收集器传递给外层分组收集器的可以是任意数据类型,而不一定是它的数据集合,正好可以实现这些汇总操作。
1 | //根据年龄进行分组,获取并汇总人数 |
1 | //要根据年龄与是否及格进行分组,并获取每组中年龄的学生 |
3.5 map()与peek()的区别
案例:
1 | Stream<String> stream = Stream.of("hello", "felord.cn"); |
如果测试上述代码可以发现,压根打印输出,这是因为流的生命周期有三个阶段:
- 起始生成阶段。
- 中间操作会逐一获取元素并进行处理,可有可无。所有中间操作都是惰性的,因此,流在管道中流动之前,任何操作都不会产生任何影响。
- 终端操作。通常分为最终的消费(foreach之类的)和 归纳(collect)两类。还有重要的一点就是终端操作启动了流在管道中的流动。
案例:
1 | List<DrivePreCalcTimeStrategyPO> lessThanDayAccuTimeStrategyList = driveSpeedUpStrategyBO.getTimeStrategyList().stream() |
peek()操作一般用于不想改变流中元素本身的类型或者只想元素的内部状态时,而map()则用于改变流中元素本身类型,即从元素中派生出另一种类型的操作。比如对Collection<T>中的T元素的某些属性进行批量set的时候用peek()操作就比较合适,如果我们要从Collection<T>中获取T的某个属性的集合时用map()更合适。