Java8新特性 - Stream API
流(Stream)是Java8引入的一个非常重要的特性,可用于极大简化数据集合的处理方式。
什么是流
流 Stream是Java8的新特性,它允许以声明的方式来处理数据集合。例如要从一个整数数组中找出最大的偶数,Java7的写法可能是:
public static int findMaxEven(int[] array) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < array.length; i++) {
if (array[i] % 2 == 0) {
if (array[i] > max) {
max = array[i];
}
}
}
return max;
}然而使用流,可能只需一行代码:
public static int findMaxEvenByStream(int[] array) {
return Arrays.stream(array).filter(n -> n % 2 == 0).max().getAsInt();
}这种声明式的语法有点类似SQL语句,你只需告诉数据库,需要查什么数据(select ... from ...)、根据什么条件(where)、是否要排序、分组,而不需要去实现查询数据、归并数据的算法,当然了解其算法能有助于写出更优质的代码。
关于流的操作
流与集合、数组的转换
// list转stream
list.stream();
// stream转list/set
stream.collect(Collectors.toList());
stream.collect(Collectors.toSet());
// stream转map,稍稍复杂一点,需要指定key/value的映射函数(λ表达式),另外还可能遇到key值冲突,因此需要提供解决key值冲突的mergeFunction
stream.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction));
// 数组转stream,需借助工具类Arrays
Arrays.stream(array);
// stream转数组
stream.toArray();
// 直接生成流,以IntStream为例
IntStream.range(1,100);
IntStream.of(1, 2, 3, 4, 5);
IntStream.builder().add(1).add(3).add(5).build();流水线操作
Java8中关于流的实现都在java.util.stream包中,其中java.util.stream.Stream接口上声明了流的操作(API)。
例如filter、limit、distinct等,这些操作仍都返回一个Stream,这意味着可以把多个操作串联起来形成一个流水线,这些操作被称为中间操作;还有一些操作不返回Stream,而是返回流水线处理的最终结果,例如count、max、min等,这些操作被称为终端操作。
中间操作可以互相串联起来,但并不会真正执行,终端操作会执行流水线并关闭流。
注意
一个流只能有一个终端操作,否则会报错java.lang.IllegalStateException: stream has already been operated upon or closed
常用的操作有:
| 操作 | 类型 | 描述 |
|---|---|---|
| filter | 中间 | T -> boolean |
| map | 中间 | T -> R |
| limit | 中间 | |
| sorted | 中间 | (T,T) -> int |
| distinct | 中间 | |
| forEach | 终端 | 遍历每个元素并执行消费操作 |
| count | 终端 | 返回流中元素的个数 |
| collect | 终端 | 把流转化为一个集合 |
| min | 终端 | 取最小值 |
| max | 终端 | 取最大值 |
示例
List<Student> students = ...;
// 求3年级同学的平均身高
double avgHeight = students.stream()
.filter(s -> s.getGrade() == 3)
.mapToInt(Student::getHeight)
.average()
.getAsDouble();
// 对2年5班的同学根据考试成绩进行排序,并取前10名
List<Student> result = students.stream()
.filter(s -> s.getGrade() == 2)
.filter(s -> s.getClassNo() == 5)
.sorted(Comparator.comparingInt(Student::getScore).reversed())
.limit(10)
.collect(Collectors.toList());关于流的性能
提到性能优化,通常很容易想到的是使用多线程,而并行流就是使用多线程来提升性能的一种方式。使用并行流很简单,只需要把上述示例代码中的stream()改为parallelStream()。
然而使用了并行流是否一定会带来性能提升呢,答案显然是否定的,引入并行会增加代码的复杂性(虽然这种复杂性被封装在流的实现里,但复杂性依然是存在的),使用不当反而会降低性能。关于流的性能,有以下几点建议:
使用流的建议:
- 少量数据不要使用并行流,数据量过少,使用并行流导致的线程切换开销甚至要多于计算本身,因此不推荐使用并行流
- 对于基本类型尽量用IntStream、LongStream等流,而不是Integer等包装类型组成的流,避免频繁装箱拆箱
- 并行流只在多核CPU下能够带来性能提升,一般来说并行流的默认线程数等同于CPU核数
- 依赖于元素顺序的操作在并行流上的性能比顺序流差,例如iterate、limit、findFirst 注1
- 是否考虑使用并行流的另一个考虑因素是数据结构是否易于拆分,例如ArrayList是易于拆分的而LinkedList需要遍历元素才能完成拆分。
注1
limit操作的性能与数据源是否有序也有关系,List是有序的而Set是无序的,在无序并行流上调用limit会比在有序流上更高效。另:可以调用unordered把有序流转为无序流。