码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2019-10-07 21:40:17      阅读:113      评论:0      收藏:0      [点我收藏+]

标签:Collector   mapper   数列   文档   默认   求和   结果   题目   匿名   

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

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

注意:本文所讲述的“流”不是 XXXInputStream、XXXOutputStream

预备知识: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。

lambda 表达式

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

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

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

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

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

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

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());

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

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

标签:Collector   mapper   数列   文档   默认   求和   结果   题目   匿名   

原文地址:https://www.cnblogs.com/Downstream-1998/p/11632045.html

踩
(0)
赞
(0)
   
举报
评论 一句话评论(0)
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!