Java8 流式 API(`java.util.stream`)

熟悉 ES6 的开发者,确定对数组的一些方法不是很陌生:mapfilter 等。在对一组对象进行统一操做时,利用这些方法写出来的代码比常规的迭代代码更加的简练。在 C♯ 中,有 LINQ 来实现。那么在 Java 中有这样的操做吗?答案是有的,Java8 中引入了大量新特性,其中一个就是 Java 的流式 API。html

在 Java 8 中,流(Stream)与迭代器相似,都是用来对集合内的元素进行某些操做。它们之间最大的差异,是对迭代器的每一个操做都会即时生效,而对流的操做则不是这样。流的操做有两种,中间操做和终止操做。对于中间操做并不会当即执行,只有当终止操做执行时,前面的中间操做才会一并执行(称之为惰性求值)。对于某些复杂操做,流的效率会比传统的迭代器要高。java

注意:本文所讲述的“流”不是 XXXInputStreamXXXOutputStreamapi

预备知识:lambda 表达式、Functional Interface

Functional Interface

在 Java8 中,新加入了一个注解:@FunctionalInterface,用于标记一个接口是函数接口(即有且只有一个方法(不包括那些有默认实现的方法和标记为 static 的方法))。一个典型的例子就是 Java 中用于多线程的 Runnable 接口:数组

@FunctionalInterface
public interface Runnable {
    void run();
}

另一个例子来自于 Java8 中预约义的一些接口(位于 java.util.function 包下)多线程

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

若是本身定义函数式接口,@FunctionalInterface 注解是可选的,只要接口内除静态方法和有默认实现的方法以外有且只有一个方法,那么这个接口就被认为是 Functional Interface。oracle

lambda 表达式

lambda 表达式是 Java 8 中新引进的语法糖,主要做用是快速定义一个函数(或一个方法)。其基本语法以下:app

(参数列表) -> { 表达式内容 }

其中参数列表内,每一个参数的类型是可选的,若是参数列表内没有参数,或参数不止一个时,须要用 () 进行占位。ide

lambda 表达式的主要做用,就是用于简化代码。熟悉 Java GUI 的读者知道,以前要给一个控件添加事件响应的时候,咱们一般是使用匿名内部类进行处理:函数

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 这里处理事件响应的代码
    }
});

显然,这种写法是比较麻烦的,咱们观察上面的代码,能够看到 ActionListener 中只有一个方法。控件的 addActionListener 实际上接受的是一个方法做为参数,事件发生时调用该方法做为响应。lambda 表达式的做用就是用于快速定义方法,因而能够对上面的方法改写成以下形式ui

button.addActionListener(e -> {
    // 处理事件响应
})

能够看到,引入 lambda 表达式后,整个方法都变得十分简洁。这就是 lambda 表达式的做用。

基本使用

打开流

能够用以下方法打开一个 Stream

  1. 使用 Collection 子类的 stream()(串行流)或 parallelStream()
  2. 使用 Arrays.stream() 方法为数组建立一个流
  3. 使用 Stream.of() 方法建立流
  4. 使用 Stream.iterate() 方法建立流
  5. 使用 Stream.generate() 方法建立流

其中前三种建立的流是有限流(里面的元素数量是有限个,由于建立该流的集合内元素数量也是有限的),后两种建立的流是无限流(里面的元素是由传入的参数进行生成的,具体可参阅 API 文档

对流进行操做

前文说过,流的操做有两种:中间操做和终止操做。辨别这两种操做的方法很简单:观察这些操做的返回值。若是方法的返回值是 Stream<T> 说明操做返回的是流自身,能够进行下一步操做,这一操做为中间操做,反之则为终止操做,终止操做结束后流即失效,想再次使用则须要建立新的流。

下面列举一些(至少我比较常常用到的)一些流的操做

操做 描述
<R> Stream<R> map(Function<? super T, ? extends R> mapper) 将流里面的每一个元素经过 mapper 转换为另外一个元素,并生成一个对应类型的流
Stream<T> filter(Predicate<? super T> predicate) 从流里挑出全部符合 predicate 条件的全部元素,并放入一个新的流中
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 将流里面的每一个元素“展开”,造成一个新的流(一般用于展开嵌套的 List 或数组(把矩阵转换为数组之类的))
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
常见的应用场景:求和。简单来讲就是对流内的每一个元素进行一次操做,最后获得一个结果
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
<R, A> collect<Collector<? super T, A, R> collector
常见的应用场景:把流中的元素收集到一个 List
boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
判断流中是否全部元素(存在元素)知足 predicate 判断条件

以上仅展现了部分经常使用操做,其他操做可参见 Stream 类的 API 文档,另外不要被 API 的参数吓到。这些参数实际上大部分是来自于 java.util.function 的接口,且均为前文所说的 Functional Interface,因此实际使用时,咱们都是传递 lambda 表达式给参数。

举例

对于选择题来讲,其选项能够由如下结构表示

class Question {
    String body;
    List<Option> options;
    
    // 省略 getter/setter
}

class Option {
    String answer;
    boolean right;
    
    // 省略 getter/setter
}

假如咱们有一个选择题的题库,要往里面添加一道选择题,要求在插入前要进行判断,说每一个题目必须有至少一个正确答案,则能够这样写:

boolean isValidQuestion(Question question) {
    return question.getOptions.stream().anyMatch(option -> option.isRight());
}

再举一个例子,已知 Date 类有一个 toInstant() 方法能够将 Date 转化为 Instant,现有一个 List<Date> 的变量 dates,想将其转化为 List<Instant> 类型,能够这样写:

dates.stream().map(Date::toInstant).collect(Collectors.toList());

目前我遇到的操做大体就这些,以后遇到实际的例子会继续添加到本文。

相关文章
相关标签/搜索