Java---Stream入门
由于本文需要有一定的Lambda基础,所以如果不懂什么是Lambda的同学请移步:Java---Lambda
学习Stream的目的
函数式编程渐渐变成主流,而Stream是函数式编程的重点。
相对于传统的编程方式,代码更为简洁清晰易懂。
使得并发编程变得如此简单。
有效的避免了代码嵌套地狱。(见样例)
if (条件1) {
if (条件2) {
if (条件3) {
// 再嵌套下去都快见到Diablo了。
}
}
}
Stream的特点
- 不修改数据源:任何对于Stream对象的操作都不会修改数据源的数据。
- 延迟执行:中间操作只是对于一系列操作细节的定义,而非执行。只有在终端操作被调用的同时执行中间操作。
- 可消费:任何一个Stream对象执行了终端操作后都将不可再利用。只能操作由数据源生成的新的Stream。
Stream的分类
串行流:单项环境下的Stream,基础。
List.of().stream(); // 获取串行流
并行流:多线程环境下的Stream,重点难点。
List.of().parallelStream(); // 获取并行流
Stream对象的创建
总共有三种方式:
经由集合对象创建
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); // 创建串行流
Stream<String> stream = list.parallelStream(); // 创建并行流
经由数组对象创建(两种)
String[] strs = new String[10];
// 将数组所有元素装入stream
Stream<String> stream1 = Arrays.stream(strs);
// 将数组指定区间的元素装入stream
Stream<String> stream2 = Arrays.stream(strs, 1, 7);
使用Stream的静态方法创建(三种)
// 由单个元素创建Stream,元素不允许为null
Stream<String> stream1 = Stream.of("Test");
// 由单个元素创建stream,元素允许为null
Stream<String> stream2 = Stream.ofNullable(null);
// 由多个元素创建stream,内部其实调用的是Arrays.stream(T[] array)
Stream<String> stream3 = Stream.of("Test1", "Test2", "Test3");
方法的分类
- 中间操作:根据调用的方法,返回各种各样的stream对象。传入的各种Lambda只是修改了该对象中对应方法的定义,而非执行。无并行流专用的方法。
- 终端操作:执行终端操作的方法,并且其间也执行中间操作对应的方法。有并行流专用的方法。
中间操作
distinct
方法签名:Stream<T> distinct()
作用:返回一个去重后的Stream。
List<String> list = List.of("1", "1");
list.stream().distinct()
.forEach(t -> System.out.println(t)); // 输出:1
filter
方法签名:Stream<T> filter(Predicate<? super T> predicate)
作用:返回一个由满足predicate条件的元素构成的Stream。
List<Integer> list = List.of(1, 3, 5);
list.stream().filter(t -> t >= 3)
.forEach(t -> System.out.println(t)); // 输出:3, 5
sorted
因为sorted存在两种重载,并且在jdk源码的实现并不相同,所以我们分开讨论。
方法签名:Stream<T> sorted()
作用:通过调用T类型重写Comparable接口的compareTo方法排序,返回排序后的Stream。
// 此处省略了一些java文件定义的结构,请着眼于一下核心代码
// NG例, 不实现Comparable接口
class MyInteger {
int value;
MyInteger(int value) {
this.value = value;
}
}
List<MyInteger> list = new ArrayList<>();
list.add(new MyInteger(4));
list.add(new MyInteger(1));
list.add(new MyInteger(3));
list.stream().sorted() // 不报错
.forEach(t -> System.out.println(t.value)); // ClassCastException:不能被强转成Comparable类型
// OK例, 实现Comparable接口(Integer实现了Comparable接口)
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().sorted()
.forEach(t -> System.out.println(t)); // 输出:1 3 4
方法签名:Stream<T> sorted(Comparator<? super T> comparator)
作用:通过调用传入的comparator的compare方法排序,返回排序后的Stream。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().sorted((o1, o2) -> o2 - o1) // 传入一个比较器的实现
.forEach(t -> System.out.println(t.value)); // 输出:4 3 1
skip
方法签名:Stream<T> skip(long n)
作用:返回一个不包含前n项的Stream。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().skip(2) // 跳过前两个元素
.forEach(t -> System.out.println(t.value)); // 输出:3
limit
方法签名:Stream<T> limit(long n)
作用:返回一个包含前n项的Stream。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().limit(2) // 跳过前两个元素
.forEach(t -> System.out.println(t.value)); // 输出:4 1
peek
方法签名:Stream<T> peek(Consumer<? super T> action)
作用:执行消费操作,返回原Stream。虽然有消费操作,但是Stream的状态并不会改变。并不会真正消费Stream这一特点是的peek方法常用于调试。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Stream<Integer> stream = list.stream();
stream.peek(t -> System.out.println(t)); // 输出:4 1 3, 并且不消费stream
stream.forEach(t -> System.out.println(t)); // 输出:4 1 3, 并且消费stream(消费后stream不可再次使用)
stream.forEach(t -> System.out.println(t)); // IllegalStateException:stream已经被操作或关闭
map
方法签名:<R> Stream<R> map(Function<? super T, ? extends R> mapper)
作用:将原Stream中的所有元素类型从T转化为R(不是强转,是通过一些操作得到R类型),返回封装R类型元素的Stream。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().map(t -> String.valueOf(t)) // 将元素都转换成String
.forEach(t -> System.out.println(t)); // 输出:4 1 3
flatMap
方法签名:<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
作用:将原Stream中的所有元素类型从T转化为R,返回Stream<R>。与map的主要区别在于,适配一对多的数据类型。比如,类型T中存在一个List<String>,使用map返回一个Stream<List<String>>,而是用flatMap则返回一个Stream<String>,实现一对多的映射。
List<Integer[]> list = new ArrayList<>();
list.add(new Integer[] {1, 2, 3});
list.add(new Integer[] {4, 5, 3});
list.stream().flatMap(t -> Stream.of(t)) // 将各个数组中的元素都放入stream, 实现 1 → n 的转换
.forEach(t -> System.out.println(t)); // 输出:1 2 3 4 5 3
终端操作
forEach
方法签名:void forEach(Consumer<? super T> action)
作用:迭代消费Stream里的所有元素,比如打印,写入文件,写入DB等。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().forEach(t -> System.out.println(t)); // 输出:4 1 3
toArray
方法签名:Object[] toArray()
作用:将Stream中的所有元素封装成Object[]返回。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Object[] objects = list.stream().toArray(); // 与元素类型无关, 固定返回Object[]
Integer[] ints = (Integer[])objects; // 使用时你可能需要类型强转
count
方法签名:long count()
作用:返回Stream中的元素个数。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
long count = list.stream().count(); // 返回stream中的元素个数, 当前为3
findFirst
方法签名:Optional<T> findFirst()
作用:Stream的第一个元素封装在Optional中返回。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> first = list.stream().findFirst(); // 返回stream中的第一个元素, 当前为4
/*
Optional类内容比较多所以现在不做赘述, 大家姑且就认为是个只能存放一个元素的容器就好,以后会开一个新的博文详细为 大家讲解用法
*/
anyMatch
方法签名:boolean anyMatch(Predicate<? super T> predicate)
作用:当存在符合predicate条件的元素时返回true,否则返回false。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
boolean bool = list.stream().anyMatch(t -> t <= 1); // 当一个元素小于等于1时就返回true, 当前true
allMatch
方法签名:boolean allMatch(Predicate<? super T> predicate)
作用:当所有元素都符合predicate条件时返回true,否则返回false。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
boolean bool = list.stream().allMatch(t -> t <= 1); // 当所有元素小于等于1时才返回true, 当前false
noneMatch
方法签名:boolean noneMatch(Predicate<? super T> predicate)
作用:当所有元素都不符合predicate条件时返回true,否则返回false。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
boolean bool = list.stream().noneMatch(t -> t <= 1); // 当所有元素都不小于等于1时才返回true, 当前false
min
方法签名:Optional<T> min(Comparator<? super T> comparator)
作用:按照传入的比较器将最小的元素封装在Optional中返回。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> min = list.stream().min((o1, o2) -> o2 - o1); // 根据传入的比较器实现返回最小值, 当前4
/*
这个方法可以认为stream按照传入的比较器排序, 返回排序后的第一个元素
*/
max
方法签名:Optional<T> max(Comparator<? super T> comparator)
作用:按照传入的比较器将最大的元素封装在Optional中返回。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> max = list.stream().max((o1, o2) -> o2 - o1); // 根据传入的比较器实现返回最大值
/*
这个方法可以认为stream按照传入的比较器排序, 返回排序后的最后一个元素
*/
collect
由于有两种重载,所有分开说。
方法签名:<R, A> R collect(Collector<? super T, A, R> collector)
作用:迭代消费Stream里的所有元素,返回一个R类型的容器。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Set<Integer> set = list.stream().collect(Collectors.toSet()); // 将所有元素封装成Set返回, 元素类型不变
/*
由于Collector并不是一个函数式接口,所以我们在使用过程中必须传入一个实现类或者匿名内部类(不推荐,代码太长),而不能使用Lambda表达式的形式。
而常用的Colloector的实现类中就有Collectors,这是个提供类型转换功能的类,可以将元素转换为各种Collection或Map。
*/
方法签名:<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
作用:开启多个线程,各线程分别累加,最后合并各线程结果。
推荐使用并行流调用本方法。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
List<Integer> collect = list.parallelStream() // 由于线程合并操作涉及到多线程所以使用并行流,否则将不执行线程合并操作
.collect(() -> new ArrayList<Integer>(), // 创建一个ArrayList<Integer>对象, 每个线程都会执行
(r, t) -> r.add(t), // 向上面创建的List里追加t(原本list中的元素), 每个线程都会执行
(r1, r2) -> r1.addAll(r2)); // 将两个线程的List合并到一起,返回。
/*
这里开启线程与合并线程结果的逻辑与归并排序非常类似
由于原数组中有三个元素, 所以最终开启三个线程
线程1: [4]
线程2: [1]
线程3: [3]
接下来是线程合并:
第一轮:
线程1: 线程1的结果 + 线程2的结果 -> [4]
线程2: 线程2的结果 + 线程3的结果 -> [1, 3]
第二轮:
线程1: 线程1的结果 + 线程2的结果 -> [4, 1, 3]
返回线程1的结果
*/
在第二种重载中如果使用的是串行流则实际的执行流程与第一种重载相同并不会执行线程合并的操作,所以如果一定要用串行流则非常不建议使用第二种重载方法,使用第一种即可。
reduce
由于有三种重载,所有分开说。
与sql中的聚合函数比较相似,都是计算多个数据返回一个结果,实现 n → 1。
方法签名:T reduce(T identity, BinaryOperator<T> accumulator)
作用:初始值identity与所有的元素都计算一遍,返回最终结果T。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Integer sum = list.stream().reduce(10, (t1, t2) -> t1 + t2); // 在初始值10的基础上累加没有元素的值并返回, 当前18
方法签名:Optional<T> reduce(BinaryOperator<T> accumulator)
作用:无初始值,对所有的元素都计算一遍,将最终结果的T封装在Optional中返回。
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> sum = list.stream().reduce((t1, t2) -> t1 + t2); // 返回所有元素的累加, 当前8
方法签名:<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
作用:开启多个线程,各线程分别累加,最后合并各线程结果。
推荐使用并行流调用本方法。
List<String> list = new ArrayList<>();
list.add("4");
list.add("1");
list.add("5");
list.add("2");
Integer sum = list.parallelStream() // 由于线程合并操作涉及到多线程所以使用并行流,否则将不执行线程合并操作
.reduce(10, // 定义初始值, 此初始值各线程独立存在
(u, t) -> u + Integer.valueOf(t), // u: 初始值, t: 原list的元素值
(u1, u2) -> u1 + u2); // 合并各线程的结果, 返回, 当前52
/*
这里开启线程与合并线程结果的逻辑与归并排序非常类似
由于原数组中有四个元素, 所以最终开启四个线程
线程1: 10 + 4 -> 14
线程2: 10 + 1 -> 11
线程3: 10 + 5 -> 15
线程4: 10 + 2 -> 12
上面这一步省略了各线程累加的过程, 实际就是在初始值的基础上累加
接下来是线程合并:
第一轮:
线程1: 线程1的结果 + 线程2的结果 -> 14 + 11 -> 25
线程3: 线程3的结果 + 线程4的结果 -> 15 + 12 -> 27
第二轮:
线程1: 线程1的结果 + 线程3的结果 -> 25 + 27 -> 52
返回线程1的结果
*/
在第三种重载中如果使用的是串行流则实际的执行流程与第一种重载相同并不会执行线程合并的操作,所以如果一定要用串行流则非常不建议使用第三种重载方法,使用第一种即可。
对于Lambda的细节还不熟悉的同学请参考此链接: Java---Lambda
本文只讨论了串行流与并行流的基本使用方法,对于Stream的延迟处理、并发处理等进阶的内容请参考 Java -> Stream进阶(尚未更新)
Java---Stream入门的更多相关文章
- 一文带你入门Java Stream流,太强了
两个星期以前,就有读者强烈要求我写一篇 Java Stream 流的文章,我说市面上不是已经有很多了吗,结果你猜他怎么说:"就想看你写的啊!"你看你看,多么苍白的喜欢啊.那就&qu ...
- java stream collector
Java Stream API进阶篇 本文github地址 上一节介绍了部分Stream常见接口方法,理解起来并不困难,但Stream的用法不止于此,本节我们将仍然以Stream为例,介绍流的规约操作 ...
- 史上最强Java NIO入门:担心从入门到放弃的,请读这篇!
本文原题“<NIO 入门>,作者为“Gregory M. Travis”,他是<JDK 1.4 Tutorial>等书籍的作者. 1.引言 Java NIO是Java 1.4版 ...
- 自学 Java 怎么入门
自学 Java 怎么入门? 595赞同反对,不会显示你的姓名 给你推荐一个写得非常用心的Java基础教程:java-basic | 天码营 这个教程将Java的入门基础知识贯穿在一个实例中,逐 ...
- 《JAVA 从入门到精通》 - 正式走向JAVA项目开发的路
以前很多时候会开玩笑,说什么,三天学会PHP,七天精通Nodejs,xx天学会xx ... 一般来说,这样子说的多半都带有一点讽刺的意味,我也基本上从不相信什么快速入门.我以前在学校的时候自觉过很多门 ...
- Java NIO入门(二):缓冲区内部细节
Java NIO 入门(二)缓冲区内部细节 概述 本文将介绍 NIO 中两个重要的缓冲区组件:状态变量和访问方法 (accessor). 状态变量是前一文中提到的"内部统计机制"的 ...
- 完成《Java编程入门》初稿
Java编程入门 现在的运维工程师不但要懂得集合网络.系统管理而且要和开发人员一起调试系统,社会上也需要"复合性"的运维人员,所以需要做运维的也要懂一些开发,知道软件系统接口的调试 ...
- 三、Android NDK编程预备之Java jni入门创建C/C++共享库
转自: http://www.eoeandroid.com/thread-264971-1-1.html 应网友回复,答应在两天前要出一篇创建C/C++共享库的,但由于清明节假期,跟朋友出去游玩,丢手 ...
- 二、Android NDK编程预备之Java jni入门Hello World
转自: http://www.eoeandroid.com/forum.php?mod=viewthread&tid=264543&fromuid=588695 昨天已经简要介绍了J ...
- Java Stream 使用详解
Stream是 Java 8新增加的类,用来补充集合类. Stream代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的. Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的 ...
随机推荐
- RPA应用场景-自动轮询汇总报表
场景概述 自动轮询汇总报表 所涉系统名称 券商披露网站 人工操作(时间/次) 36小时 所涉人工数量 1 操作频率 每月 场景流程 1.每月初机器人自动登录网站轮询36家券商披露的财务报告,并下载 2 ...
- CF989C A Mist of Florescence 题解
因为 \(1 \leq a,b,c,d \leq 100\) 所以每一个颜色都有属于自己的联通块. 考虑 \(a = b=c=d=1\) 的情况. AAAAAAAAAAAAAAAAAAAAAAAAAA ...
- Vmware虚拟机硬件兼容性
All virtual machines have a hardware version. The hardware version indicates which virtual hardware ...
- 栈(Stack)和队列
栈(Stack)和队列 栈是一个后进先出的线性表,它要求只在表尾进行删除和插入操作. 所谓的栈,其实就是一个特殊的线性表.表尾称为栈顶(Top),相应的表头称为栈底(Bottom). 栈的插入(Pus ...
- (数据科学学习手札140)详解geopandas中基于pyogrio的矢量读写引擎
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,前不久我在一篇文章中给大家分享 ...
- super详解
1.super和this的区别 super调用的是父类的属性或方法,this是调用当前类的属性或者方法. (1)super和this关于属性的调用 (2)super和this关于方法的 ...
- JAVA解压.Z及.ZIP文件
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-compress --> <dependency ...
- Apache Hudi数据跳过技术加速查询高达50倍
介绍 在 Hudi 0.10 中,我们引入了对高级数据布局优化技术的支持,例如 Z-order和希尔伯特空间填充曲线(作为新的聚类算法),即使在经常使用过滤器查询大表的复杂场景中,也可以在多个列而非单 ...
- 什么是FastAPI异步框架?(全面了解)
一:FastAPI框架 1.FastAPI是应该用于构建API的现代,快速(高性能)的 web 框架,使用Python 3.6+ 并基于标准的 Python 类型提示. 关键性: 快速: 可与Node ...
- dfs-1756:八皇后及1700:八皇后问题
总时间限制: 1000ms 内存限制: 65536kB 描述 会下国际象棋的人都很清楚:皇后可以在横.竖.斜线上不限步数地吃掉其他棋子.如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被 ...