Java 8 Strem基本操作
本文提供了有关Java 8 Stream的深入概述。当我第一次读到的Stream API,我感到很困惑,因为它听起来类似Java I/O的InputStream,OutputStream。但Java 8 Stream是完全不同的东西。Streams是Monads,因此在为Java提供函数式编程方面发挥了重要作用:
在函数式编程中,monad是表示定义为步骤序列的计算的结构。具有monad结构的类型定义链操作的含义,或将该类型的函数嵌套在一起。
本文详解如何使用Java 8 Stream以及如何使用不同类型的可用流操作。您将了解处理顺序以及流操作的顺序如何影响运行时性能。并对更强大的reduce,collect,flatMap流操作详细介绍。
如果您还不熟悉Java 8 lambda表达式,函数接口和方法引用,那么您可能需要了解Java 8。
Stram如何工作
Stream表示一系列元素,并支持不同类型的操作以对这些元素执行计算:
List<String> streams =Arrays.asList("a1", "a2", "b1", "c2", "c1");streams.stream().filter(s -> s.startsWith("c")).map(String::toUpperCase).sorted().forEach(System.out::println);
复制代码
以上代码的产出:
C1
C2
复制代码
Stream操作是中间操作或终端操作。中间操作返回一个流,因此我们可以链接多个中间操作而不使用分号。终端操作无效或返回非流结果。在上述例子中filter,map和sorted是中间操作,而forEach是一个终端的操作。有关所有可用流操作的完整列表,请参阅Stream Javadoc。如上例中所见的这种流操作链也称为操作管道。
大多数流操作都接受某种lambda表达式参数,这是一个指定操作的确切行为的功能接口。大多数这些操作必须是不受干扰和无状态。
当函数不修改流的基础数据源时,该函数是不受干扰的,例如在上面的示例中,没有lambda表达式通过从集合中添加或删除元素来修改streams。
当操作的执行是确定性的时,函数是无状态的,例如在上面的示例中,没有lambda表达式依赖于任何可变变量或来自外部作用域的状态,其可能在执行期间改变。
不同种类的Stream
可以从各种数据源创建流,尤其是集合。Lists和Sets支持新的方法stream()
和parallelStream()
来创建顺序流或并行流。并行流能够在多个线程上操作,后面的部分将对此进行介绍。我们现在关注的是顺序流:
Arrays.asList("a1", "a2", "a3").stream().findFirst().ifPresent(System.out::println);
复制代码
以上代码的产出:
a1
复制代码
在对象列表上调用stream()
方法将返回常规对象流。但是我们不必创建集合以便使用流,就像我们在下一个代码示例中看到的那样:
Stream.of("a1", "a2", "a3").findFirst().ifPresent(System.out::println);
复制代码
以上代码的产出:
a1
复制代码
只是用来Stream.of()
从一堆对象引用创建一个流。
除了常规对象流之外,Java 8还附带了特殊类型的流,用于处理原始数据类型int,long以及double。你可能已经猜到了IntStream
,LongStream
,DoubleStream
。
IntStreams可以使用IntStream.range()
方法替换常规for循环:
IntStream.range(1, 4).forEach(System.out::println);
复制代码
以上代码的产出:
1
2
3
复制代码
所有这些原始流都像常规对象流一样工作,但有以下不同之处:原始流使用专门的lambda表达式,例如IntFunction代替Function或IntPredicate代替Predicate。原始流支持额外的终端聚合操作,sum()
,average()
:
Arrays.stream(new int[] {1, 2, 3}).map(n -> 2 * n + 1).average().ifPresent(System.out::println);
复制代码
以上代码的产出:
5.0
复制代码
有时将常规对象流转换为基本流是有用的,反之亦然。为此,对象流支持特殊的映射操作mapToInt()
,mapToLong()
,mapToDouble
:
Stream.of("a1", "a2", "a3").map(s -> s.substring(1)).mapToInt(Integer::parseInt).max().ifPresent(System.out::println);
复制代码
以上代码的产出:
3
复制代码
可以通过mapToObj()
方式将原始流转换为对象流:
IntStream.range(1, 4).mapToObj(i -> "a" + i).forEach(System.out::println);
复制代码
以上代码的产出:
a1
a2
a3
复制代码
下面是一个组合示例:双精度流首先映射到int流,然后映射到字符串的对象流:
Stream.of(1.0, 2.0, 3.0).mapToInt(Double::intValue).mapToObj(i -> "a" + i).forEach(System.out::println);
复制代码
以上代码的产出:
a1
a2
a3
复制代码
处理过程
现在我们已经学会了如何创建和使用不同类型的流,让我们深入了解如何在流程下处理流操作。
中间操作的一个重要特征是懒惰。查看缺少终端操作的示例:
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {System.out.println("filter: " + s);return true;});
复制代码
执行此代码段时,不会向控制台打印任何内容。这是因为只有在存在终端操作时才执行中间操作。
让我们通过forEach
终端操作扩展上面的例子:
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {System.out.println("filter: " + s);return true;}).forEach(s -> System.out.println("forEach: " + s));
复制代码
执行此代码段会在控制台上产生所需的输出:
filter: d2
forEach: d2
filter: a2
forEach: a2
filter: b1
forEach: b1
filter: b3
forEach: b3
filter: c
forEach: c
复制代码
结果的顺序可能会令人惊讶。默认认为是在流的所有元素上一个接一个地水平执行操作。但相反,每个元素都沿着链垂直移动。第一个字符串“d2”通过filter,然后forEach,然后处理第二个字符串“a2”。
此行为可以减少对每个元素执行的实际操作数,如下一个示例所示:
Stream.of("d2", "a2", "b1", "b3", "c").map(s -> {System.out.println("map: " + s);return s.toUpperCase();}).anyMatch(s -> {System.out.println("anyMatch: " + s);return s.startsWith("A");});
复制代码
代码产出
map: d2
anyMatch: D2
map: a2
anyMatch: A2
复制代码
一旦谓词应用于给定的输入元素,anyMatch
操作将返回true。这对于传递给“A2”的第二个元素是正确的。由于流链的垂直执行,map
在这种情况下映射只需执行两次。因此,不是映射流的所有元素,而是map
尽可能少地调用。
复杂的处理过程
下一个示例包括两个map
,filter
中间操作和forEach
终端操作。让我们再次检查这些操作是如何执行的:
Stream.of("d2", "a2", "b1", "b3", "c").map(s -> {System.out.println("map: " + s);return s.toUpperCase();}).filter(s -> {System.out.println("filter: " + s);return s.startsWith("A");}).forEach(s -> System.out.println("forEach: " + s));
复制代码
代码产出:
map: d2
filter: D2
map: a2
filter: A2
forEach: A2
map: b1
filter: B1
map: b3
filter: B3
map: c
filter: C
复制代码
正如您可能已经猜到的,对于底层集合中的每个字符串,map和filter都被调用5次,而forEach只被调用一次。
如果我们改变操作的顺序,移动filter到链的开头,我们可以大大减少实际的执行次数:
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {System.out.println("filter: " + s);return s.startsWith("a");}).map(s -> {System.out.println("map: " + s);return s.toUpperCase();}).forEach(s -> System.out.println("forEach: " + s));
复制代码
代码产出:
filter: d2
filter: a2
map: a2
forEach: A2
filter: b1
filter: b3
filter: c
复制代码
现在,map只调用一次,因此操作管道对大量输入元素的执行速度要快得多。在编写复杂的方法链时要记住这一点。
让我们通过一个sorted
额外的操作来扩展上面的例子:
Stream.of("d2", "a2", "b1", "b3", "c").sorted((s1, s2) -> {System.out.printf("sort: %s; %s\n", s1, s2);return s1.compareTo(s2);}).filter(s -> {System.out.println("filter: " + s);return s.startsWith("a");}).map(s -> {System.out.println("map: " + s);return s.toUpperCase();}).forEach(s -> System.out.println("forEach: " + s));
复制代码
排序是一种特殊的中间操作。这是一个所谓的有状态操作,因为为了对在排序期间必须维护状态的元素集合进行排序。
执行此示例将导致以下控制台输出:
sort: a2; d2
sort: b1; a2
sort: b1; d2
sort: b1; a2
sort: b3; b1
sort: b3; d2
sort: c; b3
sort: c; d2
filter: a2
map: a2
forEach: A2
filter: b1
filter: b3
filter: c
filter: d2
复制代码
首先,对整个输入集合执行排序操作。换句话说,sorted是水平执行的。因此,在这种情况下sorted,对输入集合中的每个元素的多个组合调用八次。
我们可以通过重新排序链来优化性能:
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {System.out.println("filter: " + s);return s.startsWith("a");}).sorted((s1, s2) -> {System.out.printf("sort: %s; %s\n", s1, s2);return s1.compareTo(s2);}).map(s -> {System.out.println("map: " + s);return s.toUpperCase();}).forEach(s -> System.out.println("forEach: " + s));
复制代码
代码产出
filter: d2
filter: a2
filter: b1
filter: b3
filter: c
map: a2
forEach: A2
复制代码
在此示例sorted从未被调用过,因为filter将输入集合减少到只有一个元素。因此,对于较大的输入集合,性能会大大提高。
重用Stream
Java 8 Stream无法重用。只要您调用任何终端操作,流就会关闭:
Stream<String> stream =Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("a"));stream.anyMatch(s -> true); // ok
stream.noneMatch(s -> true); // exception
复制代码
在同一流上的anyMatch
之后调用noneMatch
会导致以下异常:
java.lang.IllegalStateException: stream has already been operated upon or closedat java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)at com.winterbe.java8.Streams5.test7(Streams5.java:38)at com.winterbe.java8.Streams5.main(Streams5.java:28)
复制代码
为了克服这个限制,我们必须为我们想要执行的每个终端操作创建一个新的流链,例如我们可以创建一个流供应商来构建一个新的流,其中已经设置了所有中间操作:
Supplier<Stream<String>> streamSupplier =() -> Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("a"));streamSupplier.get().anyMatch(s -> true); // ok
streamSupplier.get().noneMatch(s -> true); // ok
复制代码
每次调用get()构造一个我们保存的新流,以调用所需的终端操作。
Java 8 Strem基本操作相关推荐
- JAVA I/O基本操作
JAVA I/O基本操作 JAVA文件操作 JAVA字节流 JAVA字符流 JAVA缓存流 JAVA对象流 JAVA数据流 本文主要借鉴以下博客和网站: how2j.cn 深入理解java中的I/O ...
- map操作 java,Java中Map基本操作
Java中Map基本操作 void:clear() 清空集合中所有的对 boolean:containsKey(Object key) 判断是否包含指定的键 boolean:containsValue ...
- 使用mac学习java的一些基本操作
使用mac学习java的一些基本操作 本文主要讲一下MacOS与windows的不同 iTerm2 使用mac的同学是不需要安装虚拟机来学习linux命令的.只需要使用iTerm2[下载地址]+zsh ...
- Java File类基本操作
我们可以利用Java.io.File类对文件进行操作,基本操作如下: 1)创建文件: public boolean createNewFile() throws IOException 2)删除文件: ...
- JAVA的输入输出基本操作样例
这些类的继承关系有些类似,弄一个作为样例,理解一下其中的机制. package cc.openhome;import java.io.*;public class Member {private St ...
- 小学生四则运算出题程序 无操作界面java版 简单的运用java中一些基本操作
这是本学期java课中实验大纲里的第一个实验,这里简单做了一个无用户界面版本. import java.util.Random; import java.util.Scanner;public cla ...
- Java 8 Strem高级操作
Streams支持大量不同的操作.我们已经了解了最重要的操作,如filter,map.发现所有其他可用的操作(参见Stream Javadoc).我们深入研究更复杂的操作collect,flatMap ...
- Java流Strem
目录 1 体验Stream流[理解] 2 Stream流的常见生成方式[应用] 3 Stream流中间操作方法[应用] 4 Stream流终结操作方法[应用] 5 Stream流综合练习[应用] 6 ...
- JAVA文件的基本操作-------------------------------------破晓
Java基本的文件操作 文章目录 Java基本的文件操作 前言 1.File类的基本用法 2.File类的升级用法 2.1得知一个文件的大小(单位:字节) 2.2得知一个目录的子目录或者子文件 2.3 ...
- java 数组的基本操作
1 遍历数组 遍历数组就是获取数组中的每个元素,通常遍历数组使用for循环 例如: int [] a ={1,2,3,4};for (int i=0;i<a.length;i++){Syste ...
最新文章
- C++ Primer 5th笔记(9)chapter9 顺序容器 string
- 在Linux/Centos下用wondershaper限速
- Android NDK学习笔记6:异常处理
- 关于静态库与动态库【Linux 】【C】
- Myeclipse编辑器简单使用整理
- java map clone_Java中HashMap的clone()方法: java.util.HashMap.clone() - Break易站
- 阿里云刘伟光:金融核心系统将步入分布式智能化的时代
- Android系统的若干关键词大汇总
- Solarwinds实现MSN报警
- 使用VisualSVN Server搭建SVN版本控制服务器
- Linux 多线程编程 (典藏、含代码)
- 微信小程序开发入门教程 含视频 有源码
- http,https,spdy,http2等协议的主要区别详解
- 最常用20000英语单词表_受够加班煎熬,我整理出10条职场人士最常用的透视表技巧!(下篇)...
- 卫星高度角和方位角的计算
- aps是什么意思_轿车里面的APS是什么意思 ?
- 13.tornado操作之增加用户喜欢的图片展示页+同时展示用户上传的所有图片增加展示图片有多少用户喜欢的功能
- 超全 | 基于纯视觉Multi-Camera的3D感知方法汇总!
- 欢迎来到wxWindows
- 2020考研计算机专业课,2020计算机考研大纲原文
热门文章
- ServletContextListener 的应用
- Flink + TiDB,体验实时数仓之美
- Flink CDC 系列 - 实现 MySQL 数据实时写入 Apache Doris
- Android Q (Android 10.0)系统新特性
- 刀塔霸业android如何更新_战斗麻将!V社自走棋《刀塔霸业》APP端上线了
- zookeeper 限制本机ip访问_解Bug之路-dubbo应用无法重连zookeeper
- 阿里java工具包_阿里开源的Java诊断工具Arthas(阿尔萨斯)
- lua如何打印行号_【Lua与C#交互⑤】Lua中的注册表和引用系统
- 自行车测速器怎么调_怎么测甲醛?
- 带存储功能的计算器是什么样的_19年中级会计考生能不能带计算器考试?今天统一回复!...