熟悉 ES6 的开发者,确定对数组的一些方法不是很陌生:map
、filter
等。在对一组对象进行统一操做时,利用这些方法写出来的代码比常规的迭代代码更加的简练。在 C♯ 中,有 LINQ 来实现。那么在 Java 中有这样的操做吗?答案是有的,Java8 中引入了大量新特性,其中一个就是 Java 的流式 API。html
在 Java 8 中,流(Stream
)与迭代器相似,都是用来对集合内的元素进行某些操做。它们之间最大的差异,是对迭代器的每一个操做都会即时生效,而对流的操做则不是这样。流的操做有两种,中间操做和终止操做。对于中间操做并不会当即执行,只有当终止操做执行时,前面的中间操做才会一并执行(称之为惰性求值)。对于某些复杂操做,流的效率会比传统的迭代器要高。java
注意:本文所讲述的“流”不是 XXXInputStream
、XXXOutputStream
api
在 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 表达式是 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
:
Collection
子类的 stream()
(串行流)或 parallelStream()
Arrays.stream()
方法为数组建立一个流Stream.of()
方法建立流Stream.iterate()
方法建立流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());
目前我遇到的操做大体就这些,以后遇到实际的例子会继续添加到本文。