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

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

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

预备知识: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`)的更多相关文章

  1. java8 流式编程

    为什么需要流式操作 集合API是Java API中最重要的部分.基本上每一个java程序都离不开集合.尽管很重要,但是现有的集合处理在很多方面都无法满足需要. 一个原因是,许多其他的语言或者类库以声明 ...

  2. [零]java8 函数式编程入门官方文档中文版 java.util.stream 中文版 流处理的相关概念

    前言 本文为java.util.stream 包文档的译文 极其个别部分可能为了更好理解,陈述略有改动,与原文几乎一致 原文可参考在线API文档 https://docs.oracle.com/jav ...

  3. java.util.stream 库简介

    Java Stream简介 Java SE 8 中主要的新语言特性是拉姆达表达式.可以将拉姆达表达式想作一种匿名方法:像方法一样,拉姆达表达式具有带类型的参数.主体和返回类型.但真正的亮点不是拉姆达表 ...

  4. Java8 新特性之Stream----java.util.stream

    这个包主要提供元素的streams函数操作,比如对collections的map,reduce. 例如: int sum = widgets.stream() .filter(b -> b.ge ...

  5. jackson 流式API

    http://www.cnblogs.com/lee0oo0/articles/2652528.html Jackson提供了三种可选的JSON处理方法 1.流式API     com.fasterx ...

  6. 流式计算新贵Kafka Stream设计详解--转

    原文地址:https://mp.weixin.qq.com/s?__biz=MzA5NzkxMzg1Nw==&mid=2653162822&idx=1&sn=8c4611436 ...

  7. Hadoop_11_HDFS的流式 API 操作

    对于MapReduce等框架来说,需要有一套更底层的API来获取某个指定文件中的一部分数据,而不是一整个文件 因此使用流的方式来操作 HDFS上的文件,可以实现读取指定偏移量范围的数据 1.客户端测试 ...

  8. java8流式编程(一)

    传送门 <JAVA8开发指南>为什么你需要关注 JAVA8 <Java8开发指南>翻译邀请 Java8初体验(一)lambda表达式语法 Java8初体验(二)Stream语法 ...

  9. 深入分析Java的内置日志API(java.util.logging)(一)

    简介   任何的软件系统,日志都是非常重要的一部分.良好统一的日志规范会大大提高应用程序的可维护性.可靠性,并进而提高开发效率,指导业务.在早期,Java工程师往往都是利用 System.err.pr ...

随机推荐

  1. office2016下载安装

    https://jingyan.baidu.com/article/359911f5acfa4357fe030631.html

  2. 学习CSS Grid布局

    一. 重要术语: CSS Grid(网格) 布局(又称为 "Grid(网格)" ),是一个二维的基于网格的布局系统,它的目标是完全改变我们基于网格的用户界面的布局方式. FlexB ...

  3. H5页面基础元素

    H5页面结构元素示例 <!DOCTYPE html> <html lang="zh"> <head> <meta charset=&quo ...

  4. P2704 [NOI2001]炮兵阵地 (状压DP)

    题目: P2704 [NOI2001]炮兵阵地 解析: 和互不侵犯一样 就是多了一格 用\(f[i][j][k]\)表示第i行,上一行状态为\(j\),上上行状态为\(k\)的最多的可以放的炮兵 发现 ...

  5. 一问带你区分清楚Authentication,Authorization以及Cookie、Session、Token

    上周写了一个 适合初学者入门 Spring Security With JWT 的 Demo .Demo 地址:https://github.com/Snailclimb/spring-securit ...

  6. SQL Server强制释放内存

    --强制释放内存 CREATE procedure [dbo].ClearMemory as begin --清除所有缓存 DBCC DROPCLEANBUFFERS --打开高级配置 EXEC (' ...

  7. vue-quill-editor富文本编辑器 中文翻译组件,编辑与展示

    vue项目中用到了富文本编辑器,网上找了一些,觉得vue-quill-editor最好用, ui简洁,功能也好配,够用了,文档不好读,有些小细节需要自己注意,我懒得分析,就封装成了组件 大家用的时候直 ...

  8. springBoot配置druid监控报错Failed to bind properties under 'spring.datasource.druid' to javax.sql.DataSource

    报错信息: Description: Failed to bind properties under 'spring.datasource.druid' to javax.sql.DataSource ...

  9. python安装第三方库--换镜像源

    python安装第三方库--换镜像源 1. 更换anaconda源 清华大学镜像:清华大学镜像 anaconda下载地址:https://mirrors.tuna.tsinghua.edu.cn/an ...

  10. JS高阶---简介+数据类型

    首先看下大概流程 [一]基础 接下来看下数据类型分类和判断 (1)数据类型分类 基本类型/值类型5种 ---字符串String.数字Number.布尔值Boolean.未定义undefined.空nu ...