- , 15 9月 2021
- 作者 847954981@qq.com
- 后端学习
Java并发(Stream API)
Stream(流) 的主要作用是对 集合(Collection) 中的数据进行各种操作,增加了集合对象的功能。
Stream 经常与 Lambda 一起使用,这里的流的Java8的新特性,与Java原本的文件流是完全不同的。
数据流的操作过程,可以看做一个管道,管道由多个节点组成,每个节点完成一个操作。数据流输入这个管道,按照顺序经过每个节点,这种下一个节点必须等待上一个节点执行完毕的方法叫做串行。
数据流的重点不再是对象的运用,而是数据的计算,其特征是:函数式风格,即弱化了面向对象的严格、完整的语法,重心变为通过函数完成数据计算。
流的创建
流的创建有好多种
直接创建
代码语言:javascript复制Stream<String> stream = Stream.of("苹果", "哈密瓜", "香蕉", "西瓜", "火龙果");
由数组转换
代码语言:javascript复制String[] fruitArray = new String[] {"苹果", "哈密瓜", "香蕉", "西瓜", "火龙果"};
Stream<String> stream = Stream.of(fruitArray);
由集合转换
代码语言:javascript复制List<String> fruits = new ArrayList<>();
fruits.add("苹果");
fruits.add("哈密瓜");
fruits.add("香蕉");
fruits.add("西瓜");
fruits.add("火龙果");
Stream<String> stream = fruits.stream();
Stream可以利用forEach()方法迭代
代码语言:javascript复制Stream<String> stream = Stream.of("苹果", "哈密瓜", "香蕉", "西瓜", "火龙果");
stream.forEach(System.out::println);
聚合操作
filter()方法
这个方法作用是对数据对象进行过滤
代码语言:javascript复制pupils.stream()
.filter(pupil -> pupil.getAverageScore() >= 80 && pupil.getViolationCount() < 1)
使用 Lambda 语句告诉过滤器,需要哪些符合条件的数据
这里与 Lambda 语句不同的是,因为过滤条件语句为非可执行语句,写在小括号()中,而不是写在{} 中
map()方法
map()方法通称映射,作用为用新的元素将流中原本相同位置的元素替换掉,相对于每一个对象都经历以此转换。
如:
代码语言:javascript复制numbers.stream()
.map(num -> {
return num * num;
})
.forEach(System.out::println);
map()
方法的参数是一个 Lambda 表达式,在语句块中对流中的每个数据对象进行计算、处理,最后用 return
语句返回的对象,就是转换后的对象。
映射后的对象类型可以与流中原始的对象类型不一致,
如在流中,可以用字符串替换原来的整数。这就极大的提供了灵活性和拓展性,让流后继的操作可以更方便。
少数情况下,如果替换语句简单、系统能自动识别需要返回的值,代码可以简写为:
代码语言:javascript复制.map(num -> num * num)
当然,最好还是使用 return 来完成映射。
sorted()方法
sorted()方法就是用来排序的方法,把排序规则写成一个 Lambda表达式传给此方法即可,类似于 Collections的sort方法,如:
代码语言:javascript复制students.stream()
// 实现升序排序
.sorted((student1, student2) -> {
return student1.getRollNo() - student2.getRollNo();
})
.forEach(System.out::println);
注意的是 .sorted((student1, student2) 使用时,student1指代的是后一个元素,student2指代的是前一个元素!
如果语句简单,系统可以自动识别,那么代码可以简写
代码语言:javascript复制.sorted((student1, student2) -> student1.getRollNo() - student2.getRollNo())
当然建议还是写完整。
这里需要注意,sort方法返回非正数则两个相比较的元素需要交换位置,返回为正数则不需要。
limit() 方法
limit()方法作用是返回流的前 n 个元素,当然 n 不能为负数。如:
代码语言:javascript复制numbers.stream()
.sorted((n1, n2) -> n2 - n1)
.limit(3)
.forEach(System.out::println);
reduce()方法
reduce()方法的作用是合并了所有的元素,终止计算出一个结果,终止指流已经到达了终点结束了,不再继续流动。
forEach()也是流的终点。
reduce()方法返回的是一个比较复杂的对象,需要调用 get()方法返回最终的整数值
同理, get()方法的返回类型是系统自动根据流中的元素类型推定的。如求和:
代码语言:javascript复制int sum = numbers.stream()
.reduce((a, b) -> a b)
.get();
reduce() 方法的参数:
a
在第一次执行计算语句a b
时,指代流的第一个元素;然后充当缓存作用以存放本次计算结果。此后执行计算语句时,a
的值就是上一次的计算结果并继续充当缓存存放本次计算结果。b
参数第一次执行计算语句时指代流的第二个元素。此后依次指代流的每个元素。
注意:a、b 两个参数的作用是由位置决定的,变量名是任意的
结合下图理解:
即前一个参数(a)即为循环的集合
reduce()
方法的第一个参数(本例的 a
)有多重作用,并且系统是自动完成参数(本例的 a, b
)赋值的,所以仍然体现了 Stream
编程的重点仍然是计算(本例的 a b
)。
reduce()方法也可以操作对象:
代码语言:javascript复制Student result = students.stream().reduce(
(a, b) -> {
a.setMidtermScore(a.getMidtermScore() b.getMidtermScore());
return a;
}
)
.get();
System.out.println(result.getName() " - " result.getMidtermScore());
但利用上面方法会导致集合第一个元素被当做了缓存角色,正确性被破坏
一般我们使用操作对象,都会用reduce() 的另一个参数,自己 new 一个对象充当缓冲角色。
代码语言:javascript复制Student result = students.stream()
.reduce(new Student("", 0),
(a, b) -> {
a.setMidtermScore(a.getMidtermScore() b.getMidtermScore());
return a;
}
);
System.out.println(result.getName() " - " result.getMidtermScore());
这里reduce方法参数多加上了一个表示缓冲角色。而之后的a指代第一次为缓存
流收集 collect()方法
collect()方法的作用就是收集元素,但元素存放在哪呢?Collection.toList()是一个静态方法,作为参数告诉collect()方法存入一个List集合,所以collect()方法的返回值类型就是 List。
代码语言:javascript复制List<String> numResult = numbers.stream()
.sorted((n1, n2) -> n2 - n1)
.limit(3)
.map(a -> "" a)
.collect(Collectors.toList());
并行流
上面我们所学的串行流如果计算过程越复杂,数据量就越大,串行性能会越来越低,而这种模式无法发挥多核CPU的优势而难以优化。
为此我们需要用到并行流。
并行流的使用是添加 .parallelStream()方法与串行流的 .stream()方法向对应。
但如果流中的每一个元素之间存在逻辑依赖关系,则不适合使用并行流计算
如想要让数字(1,2,3,4,5)按顺序输出,因为并行流的输出时机是CPU动态决定的,无法确定,所以不能使用并行运算。