Java8新特性 - Stream API

Louis2020-08-19Java技术JavaJava8

流(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等,这些操作被称为终端操作。

中间操作可以互相串联起来,但并不会真正执行,终端操作会执行流水线并关闭流。

WARNING

一个流只能有一个终端操作,否则会报错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()。

然而使用了并行流是否一定会带来性能提升呢,答案显然是否定的,引入并行会增加代码的复杂性(虽然这种复杂性被封装在流的实现里,但复杂性依然是存在的),使用不当反而会降低性能。关于流的性能,有以下几点建议:

使用流的建议:

  1. 少量数据不要使用并行流,数据量过少,使用并行流导致的线程切换开销甚至要多于计算本身,因此不推荐使用并行流
  2. 对于基本类型尽量用IntStream、LongStream等流,而不是Integer等包装类型组成的流,避免频繁装箱拆箱
  3. 并行流只在多核CPU下能够带来性能提升,一般来说并行流的默认线程数等同于CPU核数
  4. 依赖于元素顺序的操作在并行流上的性能比顺序流差,例如iterate、limit、findFirst 注1
  5. 是否考虑使用并行流的另一个考虑因素是数据结构是否易于拆分,例如ArrayList是易于拆分的而LinkedList需要遍历元素才能完成拆分。

注1

limit操作的性能与数据源是否有序也有关系,List是有序的而Set是无序的,在无序并行流上调用limit会比在有序流上更高效。另:可以调用unordered把有序流转为无序流。

Last Updated 2024/11/24 20:50:53