Java8 Stream新特性详解及实战
Java8 Stream新特性详解及实战
背景介绍
在阅读Spring Boot源代码时,发现Java 8的新特性已经被广泛使用,如果再不学习Java8的新特性并灵活应用,你可能真的要out了。为此,针对Java8的新特性,会更新一系列的文章,欢迎大家持续关注。
首先,我们来看一下Spring Boot源代码ConfigFileApplicationListener类中的一段代码:
private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) {
return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new)
.filter((profile) -> !activatedViaProperty.contains(profile))
.collect(Collectors.toList());
}
这段代码怎么?够简洁明快吧,如果不使用Java8的新特性,想象一下得多少行代码才能实现?但如果没掌握或不了解Java8的新特性,这段代码读起来是不是很酸爽?
Java 8的API中新增了一个处理集合的抽象概念:Stream,中文称作“流”。它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用起来就像使用SQL语句来对数据库执行查询操作一样。可以让你如行云流水一般写出简单、高效、干劲的代码。
什么是Stream
Stream 中文称为 “流”,通过将集合转换为这么一种叫做 “流” 的元素序列(注意是抽象概念),通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。通俗来说就是你只用告诉“流”你需要什么,便在出口处等待结果接口。

上图为Steam操作的基本流程,在后面的学习过程中可反复与具体的代码进行对照,加深学习印象。
Stream相关概念
Stream操作的过程中涉及到一些相关概念,先了解一下,方便后面统一称谓。
- 元素:特定类型的对象,比如List里面放置的对象,会形成一个队列。Stream不会存储元素,只是按需计算。
- 数据源:流的来源,对照上图中的集合,数组,I/O channel, 产生器generator等。
- 聚合操作:类似SQL语句的各种过滤操作,对照上图中的filter、sorted、map等。
- Pipelining:中文词义“流水线”,中间操作会返回流本身,跟我们之前所说的流式(fluent)编程一个概念,这样可对操作进行优化,比如延迟执行(laziness)和短路(short-circuiting)。
- 内部迭代:传统遍历方式是通过Iterator或For-Each来完成,这是外部迭代。而Stream通过访问者模式(Visitor)实现了内部迭代。
需要注意的是在整个操作的过程中,聚合操作部分可以执行多次操作,但每次操作并不是像传统的集合遍历对集合里面的元素进行转换,而是将操作函数放入一个操作集合中,只有到最后一步(比如for-each打印)时才会一次性执行。
而流和迭代器类似,只能迭代一次。比如,当调用完collect方法之后,流便不能再使用了。
stream操作方法分类
中间聚合操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered。
最终输出操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator。
短路操作:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit。
生成流
在 Java 8 中, 生成流有多种方法:Stream接口的静态工厂方法、集合提供的生成方法和其他特殊的生成方法。
of方法
Stream接口的静态工厂方法主要通过重载的of方法:
public static<T> Stream<T> of(T... values);
public static<T> Stream<T> of(T t)
of方法,其生成的Stream是有限长度的,Stream的长度为其内的元素个数。使用示例代码:
Stream<String> stringStream = Stream.of("公众号");
Stream<String> stringsStream = Stream.of("关注","公众号", "程序新视界");
generator方法
与of方法对应的generator方法生成的是无限长度的Stream,其元素是由Supplier接口提供的。
public static<T> Stream<T> generate(Supplier<T> s)
使用generate方法生成的Stream通常用于随机数和常量,或者需要前后元素间维持着某种状态信息的场景。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。
示例代码如下:
Stream<Double> generateDouble = Stream.generate(Math::random);
Stream<String> generateString = Stream.generate(new Supplier<String>() {
@Override
public String get() {
return "公众号:程序新视界";
}
});
其实上面两种写法的效果是一样的,只不过第一种采用了Lambda表达式,简化了代码。
iterate方法
iterate方法生成的也是无限长度的Stream,是通过函数f迭代对给指定的元素种子而产生无限连续有序Stream,其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环。示例代码如下:
Stream.iterate(1,i -> i +1).limit(10).forEach(System.out::println);
打印结果为1,2,3,4,5,6,7,8,9,10。
上面的方法可以认为种子(seed)为1,f(seed)为在1的基础上“+1”,依次循环下去,直到达到limit的限制,最后生成对应的Stream。
empty方法
empty方法生成一个空的Stream,不包含任何元素。
Collection接口和数组的默认方法
Collection接口和数组中都提供了默认的生成Stream的方法。直接看源代码:
// Collection中
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
// 并行流操作
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
// Arrays中
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}
示例代码:
List<String> list = new ArrayList<>();
list.add("欢迎关注");
list.add("微信公众号");
list.add("程序新视界");
list.stream().forEach(System.out::println);
int[] nums = new int[]{1, 2, 3, 4, 5};
Arrays.stream(nums).forEach(System.out::println);
其他生成方法
关于其他生成方法就不详细举例了,比如:Random.ints()、BitSet.stream()、JarFile.stream()、Pattern.splitAsStream(java.lang.CharSequence)、Files.lines(java.nio.file
.Path)等。
操作方法使用
关于操作方法就不进行详细的讲解,更多的以示例的形式展示如何使用。
concat方法
合并两个Stream。如果输入Stream有序,则新Stream有序;如果其中一个Stream为并行,则新Stream为并行;如果关闭新Stream,原Stream都将执行关闭。
Stream.concat(Stream.of("欢迎","关注"),Stream.of("程序新视界")).forEach(System.out::println);
distinct
Stream中元素去重。
Stream.of(1,1,1).distinct().forEach(System.out::println);
打印结果为1。
filter
根据指定条件进行筛选过滤,留下满足条件的元素。
Stream.of(1, 2, 3, 4, 5).filter(i -> i >= 3).forEach(System.out::println);
打印结果为3,4,5。
map
将Stream中的元素进行映射转换,比如将“a”转为“A”,期间生产了新的Stream。同时为了提升效率,官方也提供了封装好的方法:mapToDouble,mapToInt,mapToLong。
Stream.of("a","b","c").map(item -> item.toUpperCase()).forEach(System.out::println);
Stream.of("a","b","c").map(String::toUpperCase).forEach(System.out::println);
打印结果为A,B,C。
flatMap
将流中的每一个元素映射为一个流,再把每一个流连接成为一个流。期间原有的Stream的元素会被逐一替换。官方提供了三种原始类型的变种方法:flatMapToInt,flatMapToLong和flatMapToDouble。
Stream.of(1, 2, 3).flatMap(i -> Stream.of(i * 10)).forEach(System.out::println);
打印结果为10,20,30。
peek
生成一个相同的Stream,并提供一个消费函数,当新Stream中的元素被消费(执行操作)时,该消费函数会在此之前先执行。
Stream.of(1, 2).peek(i -> System.out.println("peekCall:" + i)).forEach(System.out::println);
打印结果依次为:peekCall:1,1,peekCall:2,2。
ship
跳过前N个元素,取剩余元素,如果没有则为空Stream。
Stream.of(1, 2, 3).skip(2).forEach(System.out::println);
打印结果为3。
sorted
对Stream元素进行排序,可采用默认的sorted()方法进行排序,也可通过sorted(Comparator)方法自定义比较器来进行排序,前者默认调用equals方法来进行比较,
Stream.of(1, 3, 2).sorted().forEach(System.out::println);
打印结果:1,2,3。
limit
限制返回前N个元素,与SQL中的limit相似。
Stream.of(1, 2, 3).limit(2).forEach(System.out::println);
打印结果为:1,2。
collect
收集方法,实现了很多归约操作,比如将流转换成集合和聚合元素等。
Stream.of(1, 2, 3).collect(Collectors.toList());
Stream.of(1, 2, 3).collect(Collectors.toSet());
除了以上的集合转换,还有类似joining字符串拼接的方法,具体可查看Collectors中的实现。
count
返回Stream中元素个数。
Stream.of(1, 2, 3).count();
forEach
遍历Stream中所有元素。示例参考以上设计到的。
forEachOrder
遍历Stream中所有元素,如果Stream设置了顺序,则按照顺序执行(Stream是无序的),默认为元素的插入顺序。
max
根据指定的比较器(Comparator),返回Stream中最大元素的Optional对象,Optional中的value便是最大值。
Optional可以代表一个值或不存在,主要是为了规避返回值为null,而抛出NullPointerException的问题,也是由Java8引入的。但当调用其get()方法时,如果当前值不存在则会抛出异常。
Optional<Integer> max = Stream.of(1, 2, 3).max(Comparator.comparingInt(o -> o));
System.out.println("max:" + max.get());
打印结果:max:3。
min
与max操作相同,功能相反,取最小值。
Optional<Integer> min = Stream.of(1, 2, 3).min(Comparator.comparingInt(o -> o));
System.out.println("min:" + min.get());
打印结果:min:1。
reduce
reduce可实现根据指定的规则从Stream中生成一个值,比如之前提到的count,max和min方法是因为常用而被纳入标准库中。实际上,这些方法都是reduce的操作。
Stream.of(1, 2, 3).reduce(Integer::sum);
Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);
以上两个方法都是对结果进行求和,不同的是第一个方法调用的是reduce的reduce((T, T) -> T)方法,而第二个调用的是reduce(T, (T, T) -> T)。其中第二个方法的第一个参数0,表示从第0个值开始操作。
allMatch
判断Stream中的所有元素是否满足指定条件。全部满足返回true,否则返回false。
boolean result = Stream.of(1, 2, 3).allMatch(i -> i > 0);
System.out.println(result);
返回结果:true。
anyMatch
判断Stream中的元素至少有一个满足指定条件。如果至少有一个满足则返回true,否则返回false。
boolean anyResult = Stream.of(1, 2, 3).anyMatch(i -> i > 2);
System.out.println(anyResult);
返回结果:true。
findAny
获得其中一个元素(使用stream()时找到的是第一个元素;使用parallelStream()并行时找到的是其中一个元素)。如果Stream为空,则返回一个为空的Optional。
Optional<String> any = Stream.of("A", "B", "C").findAny();
System.out.println(any.get());
返回结果:A。
findFirst
获得第一个元素。如果Stream为空,则返回一个为空的Optional。
Optional<String> first = Stream.of("A", "B", "C").findFirst();
System.out.println(first.get());
返回结果:A。
noneMatch
判断Stream中是否所有元素都不满足指定条件。都不满足则返回true,否则false。
boolean noneMatch = Stream.of(1, 2, 3).noneMatch(i -> i > 5);
System.out.println(noneMatch);
返回结果:true。
统计
通过summaryStatistics方法可获得Stream的一些统计信息。
IntSummaryStatistics summaryStatistics = Stream.of(1, 2, 3).mapToInt((i) -> i).summaryStatistics();
System.out.println("max:" + summaryStatistics.getMax());
System.out.println("min:" + summaryStatistics.getMin());
System.out.println("sum:" + summaryStatistics.getSum());
System.out.println("average:" + summaryStatistics.getAverage());
这里用到了流与数值流直接的转换mapToInt,类似的方法还有mapToDouble、mapToLong。对应获得的数值流还提供了一些额外的方法,就像上面获取不同统计信息的方法一样。
鸟瞰Stream
最后,我们再来看一下Stream的主要接口类关系图。

其中,BaseStream定义了Stream的基本接口,Stream中定义了map、filter、flatMap等常用操作。IntStream、LongStream、DoubleStream是针对基本类型提供了便捷和特化操作。以上接口构建了Java8中流体系的根基。AbstractPipeline是流水线(Pipeline)的核心抽象类,用于构建和管理流水线。
另外,关于Stream的效率问题网上也有很多资料提到,下一篇文章,我们将重点说说Stream的性能问题,关注公众号“程序新视界”,敬请期待。
原文链接:《Java8 Stream新特性详解及实战》
Java8 Stream新特性详解及实战的更多相关文章
- java8的新特性详解-----------Lamda表达式
java8最大的亮点就是引入了Lamda表达式 , 函数式编程的概念 具体啥意思我也不知道.只管用就行了,非常的强大,简洁,一个表达式相当于以前的十几行代码 因为之前要实现这种效果全靠if el ...
- Java基础学习总结(33)——Java8 十大新特性详解
Java8 十大新特性详解 本教程将Java8的新特新逐一列出,并将使用简单的代码示例来指导你如何使用默认接口方法,lambda表达式,方法引用以及多重Annotation,之后你将会学到最新的API ...
- Java9 新特性 详解
作者:木九天 < Java9 新特性 详解 > Java9 新特性 详解 摘要: 1.目录结构 2.repl工具 jShell命令 3.模块化 4.多版本兼容jar包 5.接口方 ...
- Atitit.jdk java8的语法特性详解 attilax 总结
Atitit.jdk java8的语法特性详解 attilax 总结 1.1. 类型推断这个特别有趣的.鲜为人知的特性1 2. Lambda1 2.1. 内部迭代意味着改由Java类库来进行迭代,而不 ...
- 点击--》java9 新特性 详解
引言: 点击-->java9 新特性 详解 点击-->java8 新特性 详解 正题: 1.局部变量var 将前端思想var关键字引入java后段,自动检测所属于类型,一种情况除外,不能为 ...
- java10 新特性 详解
引言: 点击-->java9 新特性 详解 点击-->java8 新特性 详解 正题: 1.局部变量var 将前端思想var关键字引入java后段,自动检测所属于类型,一种情况除外,不能为 ...
- ES6,ES2105核心功能一览,js新特性详解
ES6,ES2105核心功能一览,js新特性详解 过去几年 JavaScript 发生了很大的变化.ES6(ECMAScript 6.ES2105)是 JavaScript 语言的新标准,2015 年 ...
- 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高
第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...
- Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高 ...
随机推荐
- 【转】开发一个这样的 APP 要多长时间?
作者:蒋国刚 www.cnblogs.com/guogangj/p/4676836.html 呵呵. 这是一个“如有雷同,纯属巧合”的故事,外加一些废话,大家请勿对号入座.开始了…… 我有些尴尬地拿着 ...
- Redis开发与运维:特性
Redis 特性 速度快 内存数据库 L1 cache reference 读取CPU的一级缓存 0.5 ns Branch mispredict (转移.分支预测) 5 ns L2 cache re ...
- dotnet core 调用electron来开发UI的探索
先上仓库地址 https://github.com/lightszero/webwindow.netcore dotnet core 很喜欢,问题dotnet core 不包含GUI,经过一些尝试,觉 ...
- devops--django+ldap
首先,什么是Ldap,官方解释 轻型目录访问协议(英文:Lightweight Directory Access Protocol,缩写:LDAP,/ˈɛldæp/)是一个开放的,中立的,工业标准的应 ...
- SpringSession header/cookie/attribute存放 session id
SpringSession header/cookie/attribute存放 SessionID(死磕) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长 ...
- javascript中的定时器入门
JavaScript提供定时器(timer)的功能,可以延期执行或重复执行函数或代码段. window对象提供了三个方法来实现定时器的效果,分别是setTimeout().setInternal()和 ...
- Maven学习 --- <distributionManagement>
在使用maven过程中,我们在开发阶段经常性的会有很多公共库处于不稳定状态,随时需要修改并发布,可能一天就要发布一次,遇到bug时,甚至一天要发布N次.我们知道,maven的依赖管理是基于版本管理的, ...
- FCC---Use the CSS Transform scale Property to Change the Size of an Element
To change the scale of an element, CSS has the transform property, along with its scale() function. ...
- MySQL基础之数据管理【5】
子查询的使用 select 字段名称 from tbl_name where col_name=(select col_name from tbl_name); --内层语句查询的结果可以作为外层语句 ...
- JS运动---运动基础(匀速运动)
[一]运动基础 (2)基础运动案例 <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...