Java8新特性(二):流Stream

流的概念

流(Stream)是Java8中新增加的API中的一员。流(Stream)通过声明式方式来处理集合(Collection),并且支持并行化操作。先来看一个例子。

假如现在有一个Student列表,Student的属性包括姓名name,年龄age,分数mark,现在要筛选出年龄小于20岁并且分数大于80的学生的姓名名单,以前的做法是这样的

1
2
3
4
5
6
7
8
List<Student> studentList = new ArrayList<>();
//增加studentList数据
List<String> studentNameList = new ArrayList<>();
for (Student student : studentList) {
if (student.getAge() < 20 && student.getMark() > 80) {
studentNameList.add(student.getName());
}
}

使用了流以后,代码变成这样了

1
2
3
4
5
studentNameList = studentList.stream()
.filter(student -> student.getAge() < 20)
.filter(student -> student.getMark() > 80)
.map(Student::getName)
.collect(Collectors.toList());

通过Stream API来操作集合,这种方式有以下几个好处

  • 声明性
    比起自己实现的方式更简洁易读
  • 复合性
    具有更佳的灵活性,可以将多个处理链式调用起来
  • 并行化处理
    支持并行化操作,性能更好

流的特性

在使用流(Stream)之前,先需要了解流的一些特性。

懒惰特性(laziness)

Stream的操作并不会立即执行,只有在真正需要结果的时候才会执行。

一次消费

Stream只能被消费一次,比如使用多次foreach进行多次遍历是不行的,只能遍历一次,如果要再遍历只能重新生成流。

流可以是无限的

集合是有固定大小的,而流(Stream)可以是无限的。limit和findAny这种短路操作可以对无限流完成很快的计算。

使用内部迭代(internal iteration)

在以前的版本中迭代往往都是外部迭代,需要用户通过比如(for,foreach)这样的循环操作去手动迭代,而Stream API则使用内部迭代,替用户完成了迭代的过程,这样也不用去保存一堆中间变量。

中间操作(intermediate operations)与终端操作(terminal operations)

中间操作通常返回类型是Stream,中间操作会返回一个新的Stream,终端操作会返回用户需要的类型。

操作类型 操作方法
中间操作(intermediate operations) filter,map,flatMap,distinct,sorted,peek,limit,skip,
concat,generate,empty…
终端操作(terminal operations) forEach,toArray,reduce,collect,min,max,count,
anyMatch,allMatch,noneMatch,findFirst,findAny…

常用的流操作

本节介绍一些常见的流操作,其中limit,findFirst,findAny,anyMatch,allMatch,noneMatch均是短路操作(Short-circuiting),短路操作是指一旦满足条件语句将不再执行下去,不一定操作整个流就可以结束。

中间操作

  • filter

    filter方法在Stream接口中的声明

    1
    Stream<T> filter(Predicate<? super T> predicate);

    该操作会接受一个谓词(Predicate)作为参数并且返回满足谓词的元素的流
    举例:筛选出满足学生年龄小于20的流

    1
    studentList.stream().filter(student -> student.getAge() < 20);
  • map

    map方法在Stream接口中的声明

    1
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    该操作会接受一个Function作为参数并且返回新的类型的元素所组成的流
    举例:筛选出所有学生的年龄的流

    1
    studentList.stream().map(Student::getAge);

使用mapToInt,mapToLong,mapToDouble可以避免装箱的开销。

  • flatMap

    flatMap方法在Stream接口中的声明

    1
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    该操作会将一个由多个列表组成的流转换成一个单独的流而不是多个流(扁平化)
    举例:将两个学生列表合并组成一条学生的流

    1
    Stream.of(studentList1, studentList2).flatMap(Collection::stream);

使用flatMapToInt,flatMapToLong,flatMapToDouble可以转换成IntStream,LongStream,DoubleStream,避免装箱的开销。

  • distinct

    disinct方法在Stream接口中的声明

    1
    Stream<T> distinct();

    该操作会返回一个元素各个之间不相同(根据hashCode和equals方法)的流。
    举例:筛选出所有学生的年龄并且去掉重复

    1
    2
    3
    studentList.stream()
    .map(Student::getAge)
    .distinct();
  • sorted

    sorted方法在Stream接口中的声明,有一个重载方法

    1
    2
    Stream<T> sorted();
    Stream<T> sorted(Comparator<? super T> comparator);

    该操作可以使用默认的Comparator也可以使用自定义的Comparator。
    举例:筛选出所有学生的年龄并且按从大到小的顺序排列

    1
    2
    3
    studentList.stream()
    .map(Student::getAge)
    .sorted(Comparator.reverseOrder());
  • limit

    limit方法在Stream接口中的声明

    1
    Stream<T> limit(long maxSize);

    该操作会接受一个size长度n,然后返回前n个元素所组成的流。
    举例:筛选出年龄最小的10个学生

    1
    2
    3
    studentList.stream()
    .sorted(Comparator.comparing(Student::getAge))
    .limit(10);
  • skip

    skip方法在Stream接口中的声明

    1
    Stream<T> skip(long n);

    该操作会接受一个size长度n,然后返回去掉前n个元素所组成的流
    举例:去掉前10个年龄最小的学生

    1
    2
    3
    studentList.stream()
    .sorted(Comparator.comparing(Student::getAge))
    .skip(10);
  • empty

    empty方法在Stream接口中的声明

    1
    2
    3
    public static<T> Stream<T> empty() {
    return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
    }

    该操作会返回一个空元素组成的流
    举例:返回一个空流

    1
    Stream.empty();

终端操作

  • forEach

    forEach方法在Stream接口中的声明

    1
    void forEach(Consumer<? super T> action);

    该操作会对每个元素进行Consumer操作
    举例:对学生流的每个学生姓名进行打印操作

    1
    studentList.stream().forEach(student -> System.out.println(student.getName()));
  • toArray

    toArray方法在Stream接口中的声明,有一个重载的方法

    1
    2
    Object[] toArray();
    <A> A[] toArray(IntFunction<A[]> generator);

    该操作会将流转换成数组
    举例:将学生流中的学生转换为学生数组

    1
    Student[] students = studentList.stream().toArray(Student[]::new);
  • min,max,count

    min,max,count在Stream接口中的声明

    1
    2
    3
    Optional<T> min(Comparator<? super T> comparator);
    Optional<T> max(Comparator<? super T> comparator);
    long count();

    这三个操作一目了然,分别是求最小值,最大值,数量。min和max比较的基准是传入的参数Comparator
    举例:求学生年龄最小值

    1
    2
    3
    Optional<Integer> minAge = studentList.stream()
    .map(Student::getAge)
    .min(Comparator.naturalOrder());

    举例:求学生年龄最大值

    1
    2
    3
    4
    int maxAge = studentList.stream()
    .map(Student::getAge)
    .max(Comparator.naturalOrder())
    .get();

    举例:求学生总数

    1
    long studentCount = studentList.stream().count();
  • reduce

    reduce方法在Stream接口中的声明,并且一共有三个方法

    1
    2
    3
    4
    5
    T reduce(T identity, BinaryOperator<T> accumulator);
    Optional<T> reduce(BinaryOperator<T> accumulator);
    <U> U reduce(U identity,
    BiFunction<U, ? super T, U> accumulator,
    BinaryOperator<U> combiner);

    reduce方法传入一个初始值,依照定义的运算规则(BinaryOperator…)将流中的元素进行组合,一直到最后一个元素,这有点类似递归的思想。Stream中的min,max,average,sum等都是特殊的reduce操作。
    举例:将学生的平均分打印出来

    1
    2
    3
    4
    double sum = studentList.stream()
    .map(Student::getMark)
    .reduce(0.0, Double::sum);
    System.out.println(sum / studentList.size());
  • anyMatch,allMatch,noneMatch

    anyMatch,allMatch,noneMatch在Stream接口中的声明

    1
    2
    3
    boolean anyMatch(Predicate<? super T> predicate);
    boolean allMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);

    anyMatch传参一个谓词,判断流中是否有任何满足谓词条件的元素,并返回一个布尔值;
    allMatch传参一个谓词,判断流中是否所有元素都满足谓词条件,并返回一个布尔值;
    noneMatch传参一个谓词,判断流中是否所有元素都不满足谓词条件,并返回一个布尔值;
    举例:找出是否有年龄大于25的学生

    1
    2
    boolean isAnyStudentOlderThan25 = studentList.stream()
    .anyMatch(student -> student.getAge() > 25);

    举例:是否所有学生分数都大于60

    1
    2
    boolean isAllStudentPassed = studentList.stream()
    .allMatch(student -> student.getMark() >= 60);

    举例:是否所有学生都没有缺考(分数=0)

    1
    2
    boolean doAllStudentsTakeExams = studentList.stream()
    .noneMatch(student -> student.getMark() == 0);
  • findFirst,findAny

    findFirst,findAny在Stream接口中的声明

    1
    2
    Optional<T> findFirst();
    Optional<T> findAny();

    findFirst是找出流中的第一个元素。
    findAny是找出流中任意一个元素,在非并行条件下, findAny往往是找到第一个元素,但不能保证一定是这样,在并行条件下计算,findAny就不一定是第一个元素,它可能是流中任意一个元素。
    举例:找出学生流中第一个学生

    1
    2
    Optional<Student> firstStudent = studentList.stream()
    .findFirst();

    举例:找出学生流中任意一个学生

    1
    2
    Optional<Student> anyStudent = studentList.stream()
    .findAny();
  • collect

    collect在Stream接口中的声明

    1
    2
    3
    4
    <R, A> R collect(Collector<? super T, A, R> collector);
    <R> R collect(Supplier<R> supplier,
    BiConsumer<R, ? super T> accumulator,
    BiConsumer<R, R> combiner);

    collect将流的数据集进行收集,返回用户所需要的类型。
    举例:使用Collector的toList方法生成学生流的姓名列表

    1
    2
    3
    List<String> studentNameList = studentList.stream()
    .map(Student::getName)
    .collect(Collectors.toList());

并行化处理

Java8支持对Collection进行并行化操作,调用Collection的parallelStream方法,可以将Collection转换为并行流,或者对流调用parallel方法,也可以将流转换为并行流。

1
2
studentList.stream().parallel();
studentList.parallelStream();

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!