熟悉 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. (一)RFB协议概述

    (文章是基于刚刚做过的一个项目,对相关知识点进行总结回顾.主要内容均是对之前收集资料的总结概括,很多内容转载自其它地方.因为时间比较长,没有一一记录转载地址,特此感谢!) 一.简介 RFB(远程帧缓冲 ...

  2. 使用xdebug对php做性能分析调优

    作为PHP程序员我们或多或少都了解或使用过xdebug.此文章记录安装和配置xdebug,以及如何使用它来分析php程序. 我的机器环境: mac, php 安装 xdebug 推荐使用 pecl 安 ...

  3. alpine安装sshd/ssh server

    1.下载alpine镜像 1 2 3 4 5 6 7 8 9 10 [root@docker43 ~]# docker pull alpine Using default tag: latest Tr ...

  4. Spring Boot 使用 JWT 进行身份和权限验证

    上周写了一个 适合初学者入门 Spring Security With JWT 的 Demo,这篇文章主要是对代码中涉及到的比较重要的知识点的说明. 适合初学者入门 Spring Security W ...

  5. http头字段

    HTTP头字段总结 本节摘自https://www.cnblogs.com/skynet/archive/2010/12/11/1903347.html. 1. Accept:告诉WEB服务器自己接受 ...

  6. 【知识点】SPU&SKU

    SPU:标准化产品单元 SPU = Standard Product Unit (标准化产品单元),SPU是商品信息聚合的最小单位,是一组可复用.易检索的标准化信息的集合,该集合描述了一个产品的特性. ...

  7. PHP生成唯一用户标识GUID

    代码如下: //生成唯一用户标识id function create_guid() { $charid = strtoupper(md5(uniqid(mt_rand(), true))); $hyp ...

  8. Android调用系统相机和相册并解决data为空,OOM,图片角度不对的问题

    最近公司项目用到手机拍照的问题,好不容易在网上copy了一些代码,但是运行起来一大堆bug,先是三星手机上运行程序直接崩掉,debug了一下原来是onActivityResult中data返回为空,找 ...

  9. svn更换repos时保留svn log

    两种情况 1. 直接移动库 问题:svn如何把A服务器上的reposA上传到B服务器的reposB并保留各种上传更新记录? 这个问题要感想敢干,直接复制改名即可 #登录到B服务器 scp -r cmo ...

  10. 分布式异步框架celery

    Celery 1.什么是Clelery Celery是一个简单.灵活且可靠的,处理大量消息的分布式系统 专注于实时处理的异步任务队列 同时也支持任务调度 Celery架构 Celery的架构由三部分组 ...