Java8 流式 API(`java.util.stream`)
熟悉 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:
- 使用
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());
目前我遇到的操作大致就这些,之后遇到实际的例子会继续添加到本文。
Java8 流式 API(`java.util.stream`)的更多相关文章
- java8 流式编程
为什么需要流式操作 集合API是Java API中最重要的部分.基本上每一个java程序都离不开集合.尽管很重要,但是现有的集合处理在很多方面都无法满足需要. 一个原因是,许多其他的语言或者类库以声明 ...
- [零]java8 函数式编程入门官方文档中文版 java.util.stream 中文版 流处理的相关概念
前言 本文为java.util.stream 包文档的译文 极其个别部分可能为了更好理解,陈述略有改动,与原文几乎一致 原文可参考在线API文档 https://docs.oracle.com/jav ...
- java.util.stream 库简介
Java Stream简介 Java SE 8 中主要的新语言特性是拉姆达表达式.可以将拉姆达表达式想作一种匿名方法:像方法一样,拉姆达表达式具有带类型的参数.主体和返回类型.但真正的亮点不是拉姆达表 ...
- Java8 新特性之Stream----java.util.stream
这个包主要提供元素的streams函数操作,比如对collections的map,reduce. 例如: int sum = widgets.stream() .filter(b -> b.ge ...
- jackson 流式API
http://www.cnblogs.com/lee0oo0/articles/2652528.html Jackson提供了三种可选的JSON处理方法 1.流式API com.fasterx ...
- 流式计算新贵Kafka Stream设计详解--转
原文地址:https://mp.weixin.qq.com/s?__biz=MzA5NzkxMzg1Nw==&mid=2653162822&idx=1&sn=8c4611436 ...
- Hadoop_11_HDFS的流式 API 操作
对于MapReduce等框架来说,需要有一套更底层的API来获取某个指定文件中的一部分数据,而不是一整个文件 因此使用流的方式来操作 HDFS上的文件,可以实现读取指定偏移量范围的数据 1.客户端测试 ...
- java8流式编程(一)
传送门 <JAVA8开发指南>为什么你需要关注 JAVA8 <Java8开发指南>翻译邀请 Java8初体验(一)lambda表达式语法 Java8初体验(二)Stream语法 ...
- 深入分析Java的内置日志API(java.util.logging)(一)
简介 任何的软件系统,日志都是非常重要的一部分.良好统一的日志规范会大大提高应用程序的可维护性.可靠性,并进而提高开发效率,指导业务.在早期,Java工程师往往都是利用 System.err.pr ...
随机推荐
- 03 .NET CORE 2.2 使用OCELOT -- Docker中的Consul
部署consul-docker镜像 先搜索consul的docker镜像 docker search consul 然后选择了第一个,也就是官方镜像 下载镜像 docker pull consul 然 ...
- $.fn.extend 与 $.extend的区别
今天看到别人写的jquery 代码都是这样的 $.fn.extend 所以查询了一下,因为自己不是前端开发,看到这样写的,感觉很牛逼.从百度上搜到的感觉解释的还是挺好的,作为记录,方便以后查找. 搜索 ...
- 企业安全之APT攻击防护
现在针对企业APT[1]攻击越来越多了,企业安全也受到了严重的威胁,由于APT攻击比较隐匿的特性[2],攻击并不能被检测到,所以往往可以在企业内部网络潜伏很长时间. APT的攻击方式多种多样,导致企业 ...
- URL&HTTP协议
一般来讲,URL地址有五个部分组成,协议,域名,端口,路径,URL地址参数,通常“//'之前的部分就是协议 常用的协议有: http 超文本传输协议 htttps http+ssl ssh 用来实现远 ...
- 安装php源码包内的扩展
本地环境 PHP 7.0.4 (cli) (built: Mar 13 2016 21:50:22) ( NTS ) 安装 进入源码包中的ext文件夹中 [root@test etc]# cd /us ...
- sftp常用命令
help 查看sftp支持哪些命令 ls 查看当前目录下文件 cd 指定目录 lcd 更改和/或打印本地工作目录 pwd 查看当前目录 lpwd 打印本地工作目录 get xxx.txt 下载xxx ...
- 字符串比较==和equals的区别
<Stack Overflow 上 370万浏览量的一个问题:如何比较 Java 的字符串?> 比较详细的比较了==和equals方法的区别. 那借此机会,我就来梳理一下 Stack Ov ...
- frame标签和frameset
框架: 属性 值 描述 frameborder 0 1 规定是否显示框架周围的边框. longdesc URL 规定一个包含有关框架内容的长描述的页面. marginheight pixels 定义框 ...
- Latex 调整断字,连接符,取消断词/断字
latex使用了处理断字的算法去自动的找断字的地方,而且latex会调整单词间距,使得文章看起来不会显得疏密不一致.大多数情况下,这些算法都工作得很好.但是因为断字的算法是根据某种规则来处理单词的断字 ...
- Linux——查找占用磁盘体积最大的前10个文件
前言 服务器上传文件失败了,才开始没考虑到磁盘原因还以为是自己的scrt的问题,还好df -h看了下,最后发现磁盘满了,真是.... 查找 find / -type f -print0 | xargs ...