Stream
List<String> deviceNames = deviceList
.stream()
.filter(x->{x.getId()<100})
.map(EnvironmentDeviceDTO::getDeviceName)
.distinct()
.collect(Collectors.toList());
概述:一个对集合处理十分方便的工具。
将要处理的元素集合看作一种流,借助Stream API 对流中的元素进行操作,如筛选、排序、聚合等。
操作类型
Stream
可以由数组或者集合创建,对流的操作可以分为两种:
- 中间操作,以管道的形式,每次操作后返回一个新的流,可以连续、多个使用。
- 终端操作,每个流的结束操作,结果返回一个新的集合或者值。
其实最开始对stream流的了解就到这里了,我也以为够用了,不就是像写SQL一样,从逻辑上对集合不断约束就行了吗,一步一步执行,直到我看到这个例子:
IntStream.range(1, 10)
.peek(x -> System.out.print("\nA" + x))
.limit(3)
.peek(x -> System.out.print("B" + x))
.forEach(x -> System.out.print("C" + x));
执行结果: A1B1C1 A2B2C2 A3B3C3
而按我最开始的思路应该是 A1A2A3A4…A10 B1B2B3 C1C2C3
就发现事情没有我想的那么简单,所以还得接着往下探究。
操作状态(对于理解原理很重要)
中间操作:有状态 | 无状态
有状态的中间操作会等待前序操作结束才会执行,如sort(),得等前序所有元素执行完事才排序
无状态的中间操作会直接执行,与前序无关,如map()
结束操作:非短路操作 | 短路操作
非短路操作会等待所有元素处理完才返回,如collect()
短路操作满足条件就返回了,如findFirst()
特性:
- stream不会储存数据,只是对原有数据源做计算操作。
- stream具有惰性计算的特点,只有当执行终端操作时才会执行前面的中间操作。
- 由第一条可接着分析:stream不是数据结构,它没有内部储存,只是通过操作《管道》从源数据中抓取元素,
从原理发掘
code:
int longestStringLengthStartingWithA
= strings.stream()
.filter(s -> s.startsWith("A"))
.mapToInt(String::length)
.max();
int longest = 0;
for(String str : strings){
if(str.startsWith("A")){// 1. filter(), 保留以A开头的字符串
int len = str.length();// 2. mapToInt(), 转换成长度
longest = Math.max(len, longest);// 3. max(), 保留最长的长度
}
}
- 并不是每执行一次函数就进行一次迭代(其实我了解之前就是这样以为的。。其实如果是这样的话效率其实十分低下,比如filter+map+foreach,就会有三次迭代,而其实自己实现功能只会有一次迭代),正确的思路是用一种方式记录用户每一步的操作,当用户调用结束操作时将记录的操作叠加到一次迭代中全部执行掉。
- 记录操作:调用一系列的中间操作,不断的产生新的Stream,这些stream通过双向链表的形式链接在一起,构成流水线。
- 操作如何叠加:给所有的操作(stage)都实现同一个接口《SINK》,约定好执行方案,每一个操作只需负责自己的处理->转发,那么对于每一个操作可能就这样处理了:
void accept(U u){
1. 使用当前Sink包装的回调函数处理u
2. 将处理结果传递给流水线下游的Sink
}
- 结束操作就是调用链的出口。
创建方式
- 通过集合中的stream()方法创建
public static void methodOne(){
List<String> list = Arrays.asList("11","33","22");
Stream<String> stream = list.stream(); // 串行
Stream<String> stringStream = list.parallelStream(); // 并行流
stream.forEach(System.out::println);
}
- 通过Arrays中的stream()方法创建
public static void methodTwo(){
int[] array = {1,2,4,5,6,7,1};
IntStream stream = Arrays.stream(array);
stream.forEach(System.out::println);
}
- 通过Stream中的静态方法创建(of,iterate,generate)
public static void methodThr(){
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
integerStream.forEach(System.out::println);
Stream<Integer> iterate = Stream.iterate(0, x -> x + 1).limit(10);
Stream<Double> generate = Stream.generate(Math::random).limit(10);
iterate.forEach(System.out::println);
generate.forEach(System.out::println);
}
并行流和串行流的区别,并行流是内部以多线程进行的,需要小心点是,要确保执行的任务对线程环境没有要求,如果任务量并不大建议不要使用,同事并行流不能保证处理结果的顺序。
常用的操作
中间操作方法分类:(高亮为无状态操作,正常为有状态操作)
- filter() 筛选
- map() 映射
- flatMap() 扁平化映射,将流中的每个值都换成另一个流,然后把所有流连接成一个流
- distinct() 去重
- sorted() 排序
- peek() 可以理解为没有返回值的map,用于对流进行操作
- limit() 限定多少个
- skip() 跳过
终端操作方法分类:(高亮为短路操作,正常为非短路操作)
- forEach() 遍历
- forEachOrdered() 并行时保证顺序遍历
- toArray()
- reduce(0,x,y->x+y) 规约,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作
- collect() 收集,把流收集成一个值,或者集合(还有分组,拼接)
- min() 求最小值啦
- max() 最大值
- count() 个数
- anyMatch() 匹配到没有
- allMatch()
- noneMatch()
- findFirst() 匹配获取值
- findAny()