1,java.util.stream(函数式编程风格)
1)概念
Stream的操作可以串行执行或者并行执行。
优点:可读性、不可变性。
- 保护数据源;
对Stream中任何元素的修改都不会导致数据源被修改,比如过滤删除流中的一个元素,再次遍历该数据源依然可以获取该元素。 - 只遍历一遍数据源;
即使有多个map、filter,也只遍历一次。清晰、明了。
惰性求值,所有的中间操作都不会真正执行,在终端操作时完成整个流水线。
中间流页不存储任何数据。 - 少量数据处理时(size<1000),stream处理效率低于Iterator,但对于本来耗时就短的代码,stream可读性更高。
大量数据(size>1w)时,stream效率远远高于iterator,尤其是使用并行流时,利用多核cpu。 - 并行流(parallelStream):当能分配到多个cpu核时性能很高,否则性能还不如stream(单核不推荐使用并行流)。
parallelStream底层实现:JVM 的 ForkJoinPool。
2)流的特性
1>Intermediate
- 一个流可以后面跟随零个或多个 intermediate 操作。
- Intermediate操作是惰性化的(lazy),并没有真正开始流的遍历。
- 相关操作:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
2>Terminal
- 一个流只能有一个 terminal 操作,作为流的结束操作。
- Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
- 相关操作:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
3>Short-circuiting
- 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
- 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
- 当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
- 相关操作:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit.
3)流的使用
三个步骤:
聚合操作为最终操作。
1>获取一个数据源(source)
- 数据源包含集合、数组、I/O channel、generator(发生器)等。
- 创建Stream(串行执行)、parallelStream(多个线程并行执行);
功能 | 举例 | 备注 |
---|---|---|
获取一个顺序流stream() |
数组转流:Arrays.asList("1","2").stream() list转流: list.stream() map转流: aMap.entrySet().stream() |
通过Collection接口的默认方法default Stream< E> stream() 获取; |
获取一个并行流:parallelStream() |
Arrays.asList("1","2").parallelStream() list.parallelStream() |
通过Collection接口的默认方法default Stream< E> parallelStream() 获取; 注意线程安全问题 |
常量转换为stream | Stream stream = Stream.of("a", "b", "c") |
通过Stream接口的静态工厂方法实现。 数据量小的情况下性能并不会特别好。 |
生成一个无限长度的流 | Stream.generate(() -> Math.random()); Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println); |
一般和limit一起使用 |
流的合并 Stream.concat() |
Stream.concat(Stream.of(1, 4, 3), Stream.of(2, 5)).forEach() |
1. 新的流顺序:流1+流2; 2. 并行性:两个流有一个并行则新流也是并行的; 关闭:新流关闭,原来的2个流也都关闭。 |
2>中间操作(转换stream)
每次转换原有的Stream对象不改变,返回一个新的Stream对象。
- stream 中含有装箱类型,在进行中间操作之前,最好转成对应的数值流,减少由于频繁的拆箱、装箱造成的性能损失;
功能 | 举例 | 备注 |
---|---|---|
去重 | distinct() |
去重逻辑依赖元素的equals方法 |
过滤 | . filter(word -> word.length() > 0) |
filter里面是true,表示保留 |
一对一转换 | .map(String::toUpperCase) .map(n -> n * n) map转list: .map(Map.Entry::getValue) |
新生成的Stream只包含转换生成的元素 |
一对多转换 | .flatMap((childList) -> childList.stream()) |
把子Stream中的元素压缩到父集合中 |
转换为int | .mapToInt(x -> x.length()) .mapToInt(Integer::parseInt) |
|
查看中间执行结果 | peek | 主要用于调试目的,通常用于查看中间操作的结果。 例如在对流进行过滤或映射前后,此时会生成一个新的流输出中间计算结果。不会影响原来steam的内容 |
截断 | .limit(10) | 用法: 1. 与skip搭配进行分页计算; 2. topN |
跳过元素 | skip(2) | 跳过前n个元素并丢弃,返回新的stream。如果元素数量小于n,返回空stream;.stream().skip(2).limit(2).collect(Collectors.toList())) 输入[0, 1, 2, 3, 4],输出[2, 3] 分页 |
排序 | .sorted() 详细用法见下文 |
返回新的stream,不会影响原有数据源。 |
1>sort 排序
- map升序:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
- map降序:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
- list升序:
list.stream().sorted()...
- list降序:
list.stream().sorted(Comparator.reverseOrder())...
List<Object>
排序
.sorted(new Comparator<OrgInfo>() {
@Override
public int compare(OrgInfo o1, OrgInfo o2) {
//升序排序,降序反写
return o1.getId()-o2.getId();
}
})
3>Reduce Stream(聚合操作,最终操作)
将输入元素转换为一个最终形式返回。
功能 | 方法 | 备注 |
---|---|---|
归约 | Optional<String> reduced = list.stream().reduce((s1, s2) -> s1 + "#" + s2) |
将传入的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算); 归约后的结果是通过Optional接口表示; reduce接收的是BinaryOperator类型的lambada表达式 |
转换为list | .collect(toList()) |
|
转换为set | .collect(toSet()) |
|
转换为map | .collect(Collectors.toMap(Task::getTitle, Function.identity(), (existing, replacement) -> existing)) |
Function.identity() 相当于task->task ;(existing, replacement) -> existing) 表示key值冲突保留之前(replace表示新值替换旧值) |
对值分组为map | .collect(Collectors.groupingBy(task -> task.getType())) |
|
分割流中的元素 | .collect(Collectors.partitioningBy(task -> task.getDueOn().isAfter(LocalDate.now()))) | 比如将任务分割为要做和已经做完的任务 |
求和 | Integer count = list.stream().mapToInt(Task::getCount).sum() |
|
求最值 | max和min | |
计数 | .count() |
|
数据匹配 | allMatch:是不是Stream中的所有元素都满足给定的匹配条件; anyMatch:Stream中是否存在任何一个元素满足匹配条件; noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件 |
返回boolean值 boolean anyStartsWithA = list.stream().anyMatch((s) -> s.startsWith("a")) |
返回Stream中的第一个元素 | findFirst | 返回Stream中的第一个元素,如果Stream为空,返回空Optional |
循环 | forEach | 和for性能无差异; 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环 |
4)其它
①流转换为其它数据结构
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 =