Java 8 stream 新手入门

core-java
标签: #<Tag:0x00007f1d26abd4a8>

#1

什么是 stream?

这里的 stream 不是 I/O, 对于刚刚接触 stream 的同学, 姑且可以把它理解为遍历一组数据的工具.
java stream 包含一系列的类和方法.

一些关于 stream 基本知识:

  • stream的代码风格是链式调用, 用来实现一系列操作. 比如这样:
list.stream().filter(xxx).map(xxx).sorted(xxx).forEach();
  • stream 里常用的方法包括2大类, 中间操作(intermediate operations), 完结操作(terminal operation). 中间操作, 简单来说就是在链式调用中处在中间的方法, 它们仍然返回Stream, 这样就可以继续调用链. 完结操作一般没有返回值或其他类型的值, 而不是 Stream
  • 一个 stream 只能调用一个完结操作方法. 但可以调用多个中间操作方法.
  • 还有一类方法称为短路操作(Short circuiting operations), 就是会中断 Stream 的遍历, 比如查询第一个元素, 直接返回, 然后就结束.
  • Stream 不支持索引访问, 也就是说不能想数组一样直接通过数字索引访问元素
  • Stream 的很多方法都是使用 Lambda 表达式为参数.

如何得到 Stream:

  • 从Collection中得到 stream
Collection.stream()
  • 从数组中得到
Arrays.stream()
Stream.of()
  • 通过 JDK 提供的方法
LongStream.range()
IntStream.range()
  • 还有一些其他方法, 比较少用, 就先不介绍了

常用的方法

  • 中间操作(intermediate operations)
    map, flatMap, filter, sorted

  • 既是中间操作又是短路操作(Short circuiting operations)
    limit, skip

  • 完结操作(terminal operation)
    forEach, toArray, reduce, collect

  • 既是完结操作里又是短路操作(Short circuiting operations)
    findFirst, findAny, anyMatch, allMatch, noneMatch

forEach()

  • 属于完结操作方法
  • 简单来说就是对每个元素都调用一遍 Consumer 的 accept 方法.
  • 这个方法没有返回值.
void forEach(Consumer<? super T> action);
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

例子
将 of()方法中的所有字符串转为大写并打印

Consumer<String> printlnUpperCase = string -> System.out.println(string.toUpperCase());
Stream.of("a", "b", "c", "d").forEach(printlnUpperCase);

filter()

对所有元素进行过滤. 对每个元素执行 Predicate的 test 方法, 如果返回 true 则保留该元素, 返回 false 则舍弃元素.

Stream<T> filter(Predicate<? super T> predicate);
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

例子
只打印 以1结尾的字符串.

Stream.of("a1", "b2", "c1", "d3")
        .filter(string -> string.endsWith("1"))
        .forEach(System.out :: println);

sorted()

sorted 有两个方法, 其中一个是默认排序.
另一个方法可以自定义排序规则, 通过实现Comparator 的 compare 方法, 返回 1, 0, -1

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

例子

Consumer println = System.out :: println;
Stream.of(4, 1, 9, 7, 0).sorted().forEach(println); //默认正序
Stream.of(4, 1, 9, 7, 0).sorted((i1, i2) -> i1 > i2 ? -1 : 1).forEach(println);//自定义排序规则实现倒序

toArray

它是一个完结操作, 不需要参数, 返回一个数组.

Object[] toArray();

例子

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).toArray();

limit/skip

  • 这两个方法既是中间操作又是短路操作.
  • skip 方法的 n 参数表示要从第几个元素开始遍历. 也就是说跳过前 n 个
  • 传入 limit 方法的 maxSize 是遍历的最大值, 比如传入7, 就表示只遍历到第7个元素
  • 这两个方法配合使用时, 注意调用的先后顺序, 具体看下边的例子
Stream<T> skip(long n);
Stream<T> limit(long maxSize);

例子

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).limit(7).skip(5).forEach(println); //打印 6, 7

上边的例子是先把第7个以后的元素舍弃掉, 然后再跳过5个元素, 于是剩下的只有6和7了.

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).skip(5).limit(7).forEach(println); //打印 6, 7, 8, 9

这个例子是先跳过前5个元素, 从第6个元素开始打印7个元素, 但是只剩下了4个元素, 于是打印了6, 7, 8, 9

findFirst, findAny

  • 它们既是完结操作里又是短路操作.
  • findFirst的作用是返回 stream 遍历的第一个元素.
  • findAny, 可能返回第一个元素, 它和 findFirst 的区别是, 他不能保证一定是第一个元素, 按照官方文档的意思,它在parallel operations里性能比较高

This is to allow for maximal performance in parallel operations;

  • 它不需要参入, 不过无论都会返回一个Optional 对象, 也就是说它不会报空指针异常.
    关于Optional对象的使用, 我会在其他文章里说明.
Optional<T> findFirst();
Optional<T> findAny();

例子

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).findFirst().ifPresent(System.out :: println);

anyMatch

  • 既是完结操作里又是短路操作
  • 它可以判断 stream 中是否有匹配的元素. 返回 boolean 值
  • 通过实现Predicate 的test方法可以自定义匹配规则.
boolean anyMatch(Predicate<? super T> predicate);
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

例子

System.out.println(Stream.of("a", "B", "C", "D").anyMatch(string -> string.toLowerCase().equals("c")));

allMatch

  • 既是完结操作里又是短路操作
  • 它可以用来判断 stream 中的元素是否全部匹配给定的匹配规则, 返回 boolean 值
  • 通过实现Predicate 的test方法可以自定义匹配规则.
boolean allMatch(Predicate<? super T> predicate);

例子

System.out.println(Stream.of("c", "C").allMatch(string -> string.toLowerCase().equals("c")));

noneMatch

  • 既是完结操作里又是短路操作
  • 它可以用来判断 stream 中的元素是否全部都不匹配给定的匹配规则, 返回 boolean 值
  • 通过实现Predicate 的test方法可以自定义匹配规则.
boolean noneMatch(Predicate<? super T> predicate);

例子

System.out.println(Stream.of("a", "b", "c", "d").noneMatch(string -> string.toLowerCase().equals("e")));

collect

  • collect 是一个非常常用的完结操作的方法
  • 他可以把stream 里的元素输出为不同的类型, 或对元素做统计之类的工作.
  • JDK 提供了很多常用的实现, 封装在Collectors里, 这些实现使用起来非常简单
  • 我们也可以自定义Collector. 通过四个参数 supplier, accumulator, combiner 和 finisher 来实现自定义.
<R, A> R collect(Collector<? super T, A, R> collector);

先介绍一些collectors里的常用操作

Collectors.toList()

把 stream 里的元素转换为 list.
例子
这个例子会把List<Car> 转为List<CarDTO>

List<Car> cars = Arrays.asList(new Car("car1"), new Car("car2"), new Car("car3"));
List<CarDTO> carDTOs = cars.stream().map(car -> new CarDTO(car.getName() + " DTO")).collect(Collectors.toList());

Car 和 CarDTO 是例子需要的两个类.

package com.ying.lambda.stream;

public class Car {

    private String name;
    private int milage;

    public Car () {

    }

    public Car (String name) {
        this.name = name;
    }

    public Car (String name, int milage) {
        this.name = name;
        this.milage = milage;
    }

    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "CarDTO name: " + name;
    }

    public int getMilage() {
        return milage;
    }

    public void setMilage(int milage) {
        this.milage = milage;
    }
}
public class CarDTO {

    public CarDTO () {

    }

    public CarDTO (String name) {
        this.name = name;
    }

    private String name;
    private int milage;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMilage() {
        return milage;
    }

    public void setMilage(int milage) {
        this.milage = milage;
    }

    @Override
    public String toString() {
        return "CarDTO name: " + name;
    }
}

Collectors.joining()

  • 把stream 中的 String 连接起来,
  • 这个 collector 只能用于 String 的 stream

例子

System.out.println(Stream.of("Eric", "Peter", "Anna").collect(Collectors.joining(" and "))); 
//打印结果为 Eric and Peter and Anna
System.out.println(Stream.of("Eric", "Peter").collect(Collectors.joining(" and ", "Both ", " are my firends."))); 
//打印结果为 Both Eric and Peter are my firends.

Collectors.summarizingInt, summarizingDouble, summarizingLong

  • 统计方法,
  • 返回IntSummaryStatistics, LongSummaryStatistics, DoubleSummaryStatistics

例子

IntSummaryStatistics milageSummaryStatistics = cars.stream()
.collect(Collectors.summarizingLong(car -> car.getMilage()));
System.out.println(milageSummaryStatistics);

//打印结果为 IntSummaryStatistics{count=3, sum=38302, min=102, average=12767.333333, max=36000}

Collectors.toMap()

将 stream 中的元素转为 map

例子:

Map<String, Car> carMap = cars.stream().collect(Collectors.toMap(car -> car.getName(), car -> car));
System.out.println(carMap);

// 打印结果 {car2=CarDTO name: car2, car3=CarDTO name: car3, car1=CarDTO name: car1}

flatMap

这个方法常用在把 Stream里的每一个元素再转为一个 stream. 加入说 List 里的每一个元素都是一个 list. flatMap 可以把这种2层的结构拉平, 变为一层的结构.

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
public interface Function<T, R> {
    R apply(T t);
}

例子

Stream.of("Eric,Peter", "Evans,Brown,Johnson", "Anna")
        .flatMap(string -> Arrays.stream(string.split(",")))
        .forEach(System.out::println);

//打印结果
Eric
Peter
Evans
Brown
Johnson
Anna

reduce

合并元素, 或者筛选元素

T reduce(T identity, BinaryOperator<T> accumulator);

例子
合并字符串的例子

System.out.println(Stream.of("Eric", "Peter", "Evans", "Brown", "Johnson").reduce("", String::concat));

//打印结果
EricPeterEvansBrownJohnson

返回 milage 最大的 Car 对象

List<Car> cars = Arrays.asList(new Car("car1", 2200), new Car("car2", 36000), new Car("car3", 102));

cars.stream()
        .reduce((car1, car2) -> car1.getMilage() > car2.getMilage() ? car1 : car2)
        .ifPresent(System.out::println);

//打印结果 CarDTO name: car2

参考:

Java 8 Stream Tutorial - Benjamin Winterberg
Java 8 中的 Streams API 详解
Java 8 Streams - Short circuiting operations