Stream流

Java Stream流
1.概述
1.1.概念
- Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如,“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
- 而和迭代器又不同的是,Stream可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream的并行操作依赖于Java7中引入的Fork/Join框架(JSR166y)来拆分任务和加速处理过程。
- Stream 的另外一大特点是,数据源本身可以是无限的。
1.2.Stream流的特性
- 不存储数据[按照特定的规则对数据进行计算,一般会输出结果]
- 不会改变数据源[通常情况下会产生一个新的集合或一个值]
- 具有延迟执行特性[只有调用终端操作时,中间操作才会执行]
1.3.Stream流的作用
结合Lambda表达式,简化集合、数组操作,提高代码的效率。
1.4.Stream流的使用步骤
- 获取数据源,将数据源中的数据读取到流中
- 对流中的数据进行各种各样的处理[筛选、过滤……] 中间操作->方法调用完毕后会返回另一个流,还可以继续调用其他方法[建议使用链式编程]
- 对流中的数据进行整合处理[遍历、统计……] 终端操作->方法调用完毕后,流就关闭了,不能再调用其他方法
2.Stream的生成
2.1.Collection.stream()、Collection.parallelStream()
- 通过Collection接口中的stream()方法获取数据源为集合的流对象【同步流】:
Stream<T> stream() = list.stream();
- 通过Collection接口中的parallelStream()方法获取数据源为集合的流对象【并发、异步流】:
Stream<T> parallelStream() = list.parallelStream();
1 | public static void main(String[] args) { |
「stream和parallelStream的简单区分」: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:
2.2.Arrays.stream()
- 通过Arrays工具类中的stream(T[] array)方法获取数据源为数组的流对象:
IntStream stream = Arrays.stream(array);
[除了IntStream,还有LongStream、DoubleStream……]
1 | int[] array={1,3,5,6,8}; |
2.3.Stream的静态方法
- 通过Stream工具类中的of(T… values)方法获取数据源为一堆零散数据的流对象:
Stream<T> stream = Stream.of(array);
- 通过Stream工具类中的iterate(T seed, UnaryOperator f)方法获取数据源为无限的流对象,其中第一个参数是种子值,第二个参数是一个函数,用于生成后续的元素:
Stream<T> stream = Stream.iterate(0, n -> n + 2);
- 通过Stream工具类中的generate(Supplier s)方法获取数据源为无限的流对象,其中参数是一个供应商函数,用于生成元素:
Stream<T> stream = Stream.generate(Math::random);
1 | Stream<Integer> stream = Stream.of(1, 2, 3, 4); |
2.4.文件创建
- BufferedReader.lines()方法获取数据源为文件的流对象:
Stream<String> stream = new BufferedReader(new FileReader(filePath)).lines();
- 通过Files工具类中的lines(Path path, Charset cs)方法获取数据源为文件的流对象:
Stream<String> stream = Files.lines(Paths.get(filePath), Charset.forName(charsetName));
【安全性更强,推荐使用】
BufferedReader.lines() 和 Files.lines() 都是用于从文件中逐行读取数据的方法,但它们的用法和实现细节略有不同。前者需要手动创建 BufferedReader 对象,并在使用完后手动关闭流,而后者更为简洁,因为这个方法是Files工具类的一个静态方法,所以不需要手动创建对象,不需要手动关闭流
1 | public static void main(String[] args) { |
2.5.其他
- Pattrn.splitAsStream(CharSequence input):是Pattern类中的一个方法,用于将字符串根据指定的正则表达式分割为流对象,其中参数为字符串,返回值为流对象。
- JarFile.stream():是JarFile类中的一个方法,用于将Jar文件中的条目(文件)转换为流对象,返回值为流对象。
1 | public static void main(String[] args) { |
3.中间操作
中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
方法 | 作用 |
---|---|
Stream<T> filter(Predicate<? super T> predicate) | 过滤流中的元素,返回符合条件的元素组成的流 |
Stream<T> distinct() | 元素去重,返回不含重复的元素的流 【依赖元素的hashCode()和equals()方法,注意判断是否需要进行重写】 |
Stream<R> map(Function<? super T, ? extends R> mapper) | 将流中的元素映射到另一个流中,返回映射后的流 【第一个类型是流中原本的元素类型,第二个类型是映射后的元素类型】 |
Stream<T> flatMap(Function<? super T, ? extends Stream<? extends T>> mapper) | 将流中的元素映射到另一个流中,返回映射后的流,其中映射后的流可以是多个元素组成的流,而不是单个元素。【第一个类型是流中原本的元素类型,第二个类型是映射后的流中元素的类型】 |
Stream<T> sorted() | 对流中的元素进行排序,返回排序后的流 |
Stream<T> sorted(Comparator<? super T> comparator) | 对流中的元素进行排序,返回排序后的流,其中参数为比较器,用于比较元素大小。【依赖元素的compareTo()方法,注意判断是否需要进行重写】 |
Stream<T> skip(long n) | 跳过前n个元素,返回剩余的元素组成的流 |
Stream<T> limit(long maxSize) | 截取前maxSize个元素,返回截取后的流 |
Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) | 将两个流合并为一个流,返回合并后的流 【如果a和b的类型不同,合并出来的会是他们共同的父类 |
Stream<T> peek(Consumer<? super T> action) | 对流中的元素进行操作,返回操作后的流,但不改变原流。【参数为消费者,用于对元素进行操作】 |
3.1.筛选、去重
filter()和distinct()方法用于筛选和去重流中的元素。
1 | public static void main(String[] args) { |
3.2.映射
3.2.1.map() 方法:
map()
方法接受一个函数作为参数,该函数用于将流中的每个元素映射到另一个值。这个映射后的值可以是任何类型,最后返回的流包含这些映射后的元素。- 对于每个输入元素,
map()
方法都会生成一个对应的输出元素。 map()
方法返回的流与原始流的元素数量相同,但是每个元素都经过了映射函数的转换。
1 | ArrayList<Person> array = new ArrayList<>(); |
3.2.2.flatMap() 方法:
flatMap()
方法也接受一个函数作为参数,但是这个函数的返回类型是一个流。这个函数会将流中的每个元素映射到另一个流。- 与
map()
方法不同,flatMap()
方法会将这些内部流合并成一个单一的流。内部流中的所有元素都会被合并到结果流中,形成一个扁平化的流。 flatMap()
方法可以处理嵌套的流结构,即使每个元素映射后的流可能包含多个元素,最终返回的流也只是一个单一的流。
1 | //首先初始化输入列表 |
看一下具体的执行流程。橙色的是stream的通用执行流程,不管你中间态用哪个方法,这里是不变的,蓝色的是ArrayListSpliterator
分割器。红色的执行流程是flatMap的执行流程。
可以看到ArrayListSpliterator
先取出第一个元素[1]
这个一维数组传递给flatMap
,然后flatMap执行了我们传入的Collection::stream
方法,该方法是初始化一个stream头节点。也就是再生成了一个stream
重点就是这里了。再次把[1]这个一维数组放入了新的stream里面。然后把结果态节点ReduceOps
传递给了新的stream作为新的stream的结果态节点。
这个时候新的stream开始执行ArrayListSpliterator
。从而把[1]一维数组进行for循环,取出了其中的1
这个元素,然后把1传入了同一个ReduceOps
进行处理从而组成了一个结果list->[1]。
把步骤总结如下:
1.取出二维数组的第一个一维数组
2.把一维数组和结果态节点重新创建一个stream
3.执行stream把一维数组的元素循环放入结果态的list
循环二维数组,不断重复上述步骤,就可以把二维数组展开成一维数组了。
3.2.3.map()和flatMap()的区别
总的来说:
map()
方法是一对一的映射,而flatMap()
方法可以处理一对多的映射,可以将多个流合并成一个流。map()
和faltMap()
的参数差别在于,前者传入一个实体返回一个实体,后者则是传入一个实体返回一个Stream流,那既然是流,最好返回值本身就是一个Stream,或者能被转换成Stream的对象!
1 | List<String> list = List.of("Holle world and you world"); |
3.3.排序
sorted()
方法用于对流中的元素进行排序。默认情况下,排序是按照自然顺序进行的,即升序。【其中的元素必须实现 Comparable 接口,否则会抛出 ClassCastException 异常】sorted(Comparator<T> comparator)
方法用于对流中的元素进行排序,并指定排序规则。我们可以使用lambda表达式来创建一个Comparator实例。可以自定义排序规则。
1 | #自然序排序一个list |
Comparator.thenComparing(Comparator<? super T> other)
: 实现多字段排序,如果第一个比较器比较结果相等,则使用第二个比较器进行比较。可以搭配使用Comparator.reverseOrder() 实现降序和升序
1 | // 按年龄升序,如果年龄相等,再按零花钱升序 |
- 自定义排序规则
1 | // 按照名字的长度排序,长的在前面。【如果使用compareTo,是按照字典序排序】 |
3.4.跳过、截取
skip(long n)
:如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。limit(long n)
:参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。
1 | public class Demo10StreamSkip { |
3.5.结合
concat(Stream<? extends T> a, Stream<? extends T> b)
: 合并a和b两个流为一个流 【如果a和b的类型不同,合并出来的会是他们共同的父类】备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
1 | public class Demo12StreamConcat { |
3.6.调试
peek(Comsumer<? super T> action)
:它接受一个 Consumer 函数作为参数,该函数会被应用到流中的每个元素上。【不会销毁流对象】这个方法通常用于调试或记录流中元素的中间状态,或者在调试代码时查看流中元素的值,而不会对流进行实际操作。
3.6.1.对流中的元素进行遍历
1 | List<userInfo> userList = new ArrayList<>(); |
3.6.2.对流中的对象进行修改
在Java的Stream中,它实际上是对原始数据的引用。
- 对于基本数据类型:修改只会作用在
peek()
方法的内部,因为基本数据类型是按值传递的,peek()
方法中的参数是局部变量,对它们的修改不会影响到原始数据。所以,对基本数据类型进行的修改只会作用在peek()方法的内部。 - 对于引用数据类型:
- 对于不可变对象(如
String
):对它们的操作会返回一个新对象,而不会修改原始数据。所以对不可变对象的操作也只会作用在peek()
方法的内部,不会影响流中的数据或原始数据。 - 对于可变对象(如
StringBuilder
、自己创建的类userInfo
等):对它们的操作会直接修改原始数据。因此,对可变对象的操作会影响流中的数据以及原始数据。
- 对于不可变对象(如
对于流中的对象进行修改:
实际上,流中的操作不会直接修改原始数据,而是操作原始数据中的对象。这意味着对流中的对象进行的任何修改都会影响原始数据。然而,需要注意的是,如果流中的对象是不可变对象,对它们进行的修改只会影响到流中的对象,而不会修改原始数据。这种修改只会作用在peek()方法的内部,不会影响到原始数据。
1 | // 流中对象是不可变对象String,对其修改只会作用在peek()方法内部,不会影响流中的数据或原始数据 |
3.6.3.peek()方法的时序图
针对前面的代码在peek()方法后执行forEach()方法时,输出顺序是先运行一次peek()方法,再运行一次forEach()方法,这样子迭代下去的这个问题,我们通过时序图来解释。
1 | //首先初始化输入列表 |
通过时序图我们可以发现,由于中间操作peek()、filter()和flatMap()都是属于无状态,所以在流(Stream)中,对于每个元素,会先执行完整条流中的所有操作,然后才处理下一个元素。这意味着,对于每个元素,先运行完整条流中的操作链,然后再处理下一个元素。而不是等待所有元素的某个操作完成后再进行下一个操作。
3.6.4.peek()方法的坑
坑一:peek() 不影响流的生成和消费
peek()是一个中间操作,它并不会终止流的处理流程,因此如果不跟一个终端操作(如collect(), forEach(), count()等),则peek()中的操作不会被执行,换言之,只有当流被消耗时,peek()里的操作才会真正发生。坑二:peek() 的执行次数取决于下游操作
peek()方法中的动作会在流的每个元素上执行一次,但具体执行多少次取决于下游的终端操作。例如,如果你在排序(sorted())前使用了peek(),而在排序后又使用了一次peek(),则同一个元素可能会被两次peek()。坑三:并发流中的peek()行为
对于并行流,peek()操作的执行顺序没有保证,而且可能会多次执行(取决于JVM的具体调度)。如果你在并行流中依赖peek()的顺序性或唯一性,可能会遇到意想不到的问题。坑四:资源管理
如果在peek()中打开了一些资源(如文件、数据库连接等),但在peek()内部并未妥善关闭它们,可能会导致资源泄露。因为在没有终端操作的情况下,流可能不会立即执行,资源也就无法及时释放。坑五:对流元素的修改可能无效
peek()通常用于读取或打印流元素,而不是修改它们。虽然理论上可以尝试在peek()中修改元素,但由于流的惰性求值和可能的不可变性,这样的修改可能不会反映到源集合或后续流操作中。坑六:对于可变对象的处理可能会影响流中的数据和原始数据
peek()方法通常用于读取或打印流元素,而不是修改它们。然而,如果流中的元素是可变对象,并且在peek()中对其进行了修改,这些修改可能会影响到流中的数据以及原始数据。这是因为可变对象的特性使得对其进行的修改会在流中传递,可能会对后续的操作产生意外的影响。因此,在处理可变对象时,需要格外小心,并确保了解其对流处理的影响。
4.终结操作
结束操作又可以分为短路操作与非短路操作,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。
方法 | 作用 |
---|---|
forEach(Consumer<? super T> action) | 遍历流中的元素,并对每个元素执行指定的操作。 |
findFirst() | 返回流中的第一个元素,如果流为空,则返回一个空的Optional对象。 |
findAny() | 返回流中的任意一个元素,如果流为空,则返回一个空的Optional对象。 |
anyMatch(Predicate<? super T> predicate) | 判断流中是否存在至少一个元素满足指定的条件。返回一个boolean值。 |
allMatch(Predicate<? super T> predicate) | 判断流中是否所有元素都满足指定的条件。返回一个boolean值。 |
noneMatch(Predicate<? super T> predicate) | 判断流中是否没有元素满足指定的条件。返回一个boolean值。 |
reduce(BinaryOperator<T> accumulator) | 将流中的元素按照指定的规则进行合并,返回合并后的结果。如果流为空,返回的 Optional 对象也为空。 |
reduce(T identity, BinaryOperator<T> accumulator) | 对流中的元素进行累积操作,使用指定的初始值,并返回累积结果。如果流为空,返回的是初始值。 |
max(Comparator<? super T> comparator) | 返回流中最大的元素,如果流为空,则返回一个空的Optional对象。 |
min(Comparator<? super T> comparator) | 返回流中最小的元素,如果流为空,则返回一个空的Optional对象。 |
count | 返回流中元素的数量【long类型】。 |
toArray() | 将流中的元素转换为数组,返回一个数组。 |
toList() | 将流中的元素转换为List,返回一个List。 |
collect(Collector<? super T, A, R> collector) | 将流中的元素收集到一个容器中,返回该容器。方法中的参数A 和R 表示中间结果容器的类型和最终结果的类型。 |
4.1.遍历
forEach(Consumer<? super T> action)
:遍历流中的元素,并对每个元素执行指定的操作。【打印、计算、转换……】
1 | // forEach遍历打印 |
4.2.匹配
findFirst()
:返回流中的第一个元素,如果流为空,则返回一个空的Optional对象。findAny()
:返回流中的任意一个元素,如果流为空,则返回一个空的Optional对象。anyMatch(Predicate<? super T> predicate)
:判断流中是否存在至少一个元素满足指定的条件。返回一个boolean值。allMatch(Predicate<? super T> predicate)
:判断流中是否所有元素都满足指定的条件。返回一个boolean值。noneMatch(Predicate<? super T> predicate)
:判断流中是否没有元素满足指定的条件。返回一个boolean值。
1 | List<Integer> list1 = List.of(3, 2, 1, 4, 7, 10); |
4.3.规约
reduce(BinaryOperator<T> accumulator)
:将流中的元素按照指定的规则进行合并,返回合并后的结果。如果流为空,返回的 Optional 对象也为空。- 参数:
BinaryOperator<T> accumulator
,BinaryOperator
继承于BiFunction
, 这里实现BiFunction.apply(param1,param2)
接口即可。支持lambda表达式,形如:(result,item)->{…} 。 - 返回值:返回Optional对象,由于结果存在空指针的情况(当集合为空时)因此需要使用Optional。
1
2
3
4
5
6
7
8
9
10
11
12List<Integer> list=List.of(1,2,3,4,5);
//将数组进行累加求和
//由于返回的是 Optional ,因此需要get()取出值。
Integer total=list.stream().reduce((result,item)->result+item).get();
System.out.println(total)
// 输出:15
List<String> strings = List.of("Hello", " ", "World", "!");
// 使用 reduce 方法将字符串列表中的字符串拼接成一个新的字符串
String result = strings.stream().reduce("", (partialResult, str) -> partialResult + str);
System.out.println(result);
// 输出:Hello World!- 参数:
reduce(T identity, BinaryOperator<T> accumulator)
:对流中的元素进行累积操作,使用指定的初始值,并返回累积结果。如果流为空,返回的是初始值.- 参数1:T identity 为一个初始值(默认值) ,当集合为空时,就返回这个默认值,当集合不为空时,该值也会参与计算。
- 参数2:BinaryOperator
accumulator 这个与一个参数的reduce相同。 - 返回值:并非 Optional,由于有默认值 identity ,因此计算结果不存在空指针的情况。
1
2
3
4
5
6
7List<Integer> list=List.of(1,2,3,4,5);
Integer total=list.stream().reduce(0,(result,item)->result+item);
System.out.println(total);//结果为:15
list=new ArrayList<>();
total=list.stream().reduce(0,(result,item)->result+item);
System.out.println(total);//数组为空时,结果返回默认值0reduce(U identity, BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner)
:这个方法允许更复杂的规约操作,可以用于计算任意类型的累加器值,而不仅仅是原始数据类型。同时,由于存在并行执行的可能性,需要确保累加器函数和组合器函数的实现是线程安全的。- 第一个参数和第二个参数的定义同上,第三个参数比较特殊,后面慢慢讲。
- 可以看到该方法有两个泛型 T 和 U :
(1)泛型T是集合中元素的类型,
(2)泛型U是计算之后返回结果的类型,U的类型由第一个参数 identity 决定。
也就是说,三个参数的reduce()可以返回与集合中的元素不同类型的值,方便我们对复杂对象做计算式和转换。
1
2
3
4
5
6
7
8
9
10
11
12
13List<Person> list= List.of(
new Person("张三",1)
,new Person("李四",2)
,new Person("王五",3)
,new Person("小明",4)
,new Person("小红",5));
Integer total=list.stream()
.reduce(
Integer.valueOf(0) /*初始值 identity*/
,(integer, person)->integer+ person.getAge() /*累加计算 accumulator*/
,(integer1,integer2)->integer1+integer2 /*第三个参数 combiner*/
);
System.out.println(total);//结果:15其实这相当于
1
2Integer total=list.stream().mapToInt(Person::getAge).sum();
System.out.println(total);//结果也是:15
第三个参数 BinaryOperator<U> combiner
是个什么鬼?
这个参数的lambda表达式我是这么写的:(integer1,integer2)->integer1+integer2
现在我将其打印出来:
1 | Integer total=list.stream() |
发现这个参数的lambda表达式根本就没有执行?!
我换了一种方式,换成 parallelStream ,然后把线程id打印出来:
1 | Integer total=list.parallelStream() |
把 stream 换成并行的 parallelStream,
可以看出,有两个线程在执行任务:线程1和线程30 ,
每个线程会分配几个元素做计算,
如上面的线程30分配了元素1和2,线程1分配了3、4、5。
至于线程1为什么会有两个3,是由于线程30执行完后得到的结果为3(1+2),而这个3又会作为后续线程1的入参进行汇总计算。
可以多跑几次,每次执行的结果不一定相同,如果看不出来规律,可以尝试增加集合中的元素个数,数据量大更有利于并行计算发挥作用。
因此,第三个参数 BinaryOperator<U> combiner
的作用为:汇总所有线程的计算结果得到最终结果,
并行计算会启动多个线程执行同一个计算任务,每个线程计算完后会有一个结果,最后要将这些结果汇总得到最终结果。
我们再来看一个有意思的结果,把第一个参数 identity 从0换成1:
1 | Integer total=list.parallelStream() |
预期结果应该是16(初始值1+原来的结果15),但实际结果为20,多加了4次1,猜测是多加了四次初始值,
从打印的结果可以发现:
(1)并行计算时用了5个线程(线程id依次为:30, 1, 32, 33, 31),汇总合并时用了两个线程(线程id为33和31)
(2)并行计算的每一个线程都用了初始值参与计算,因此多加了4次初始值。
总结:
使用 parallelStream 时,初始值 identity 应该设置一个不影响计算结果的值,比如本示例中设置为 0 就不会影响结果。
我觉得这个初始值 identity 有两个作用:确定泛型U的类型
和避免空指针
。
但是如果初始值本身就是一个复杂对象那该怎么办呢?
比如是初始值是一个数组,那么应该设定为一个空数组。如果是其他复杂对象那就得根据你reduce的具体含义来设定初始值了。
1
2
3
4 combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
//combiner.apply(u1,u2) 接收两个相同类型U的参数
//accumulator.apply(u, t) 接收两个不同类型的参数U和T,U是返回值的类型,T是集合中元素的类型
//这个等式恒等,parallelStream计算时就不会产生错误结果
4.4.聚合
max(Comparator<? super T> comparator)
: 返回流中最大的元素,如果流为空,则返回一个空的Optional对象。max(Comparator<? super T> comparator)
: 返回流中最小的元素,如果流为空,则返回一个空的Optional对象。count()
: 返回个数【long类型】
1 | List<Integer> list = List.of(3,2,4,5,1); |
4.5.收集
4.5.1.toArray
toArray()
: 返回一个Object[]数组,其中包含Stream中的所有元素。toArray(IntFunction<A[]> generator)
:可以指定返回数组的类型。参数 generator 是一个数组生成器函数,它根据提供的数组长度创建一个新数组。这使得我们可以在返回的数组中指定元素的类型。
1 | List<Person> list = List.of( |
4.5.2.toList
toList()
: 返回一个包含Stream中所有元素的List。
1 | List<Integer> list = Stream.of(32, 11, 23, 434, 54).toList(); |
4.5.3.collect
collect(Collector<? super T, A, R> collector)
: 将Stream中的元素收集到一个容器中,并返回该容器。
4.5.3.1.统计
Collectors.counting()
: 返回流中元素的个数。Collectors.summingInt(ToIntFunction<? super T> mapper)
: 返回流中元素的和。Collectors.summingDouble(ToDoubleFunction<? super T> mapper)
: 返回流中元素的和。Collectors.summingLong(ToLongFunction<? super T> mapper)
: 返回流中元素的和。Collectors.averagingInt(ToIntFunction<? super T> mapper)
: 返回流中元素的平均值。Collectors.averagingDouble(ToDoubleFunction<? super T> mapper)
: 返回流中元素的平均值。Collectors.averagingLong(ToLongFunction<? super T> mapper)
: 返回流中元素的平均值。
1 | List<Integer> numbers = List.of(1, 2, 3, 4, 5); |
4.5.3.2分组
Collectors.groupingBy(Function<? super T, ? extends K> classifier)
: 根据给定的分类函数对Stream中的元素进行分组,并返回一个Map,其中键是分类函数的结果,值是包含对应分类的元素的List。Collectors.partitioningBy(Predicate<? super T> predicate)
: 根据给定的断言函数对Stream中的元素进行分区,并返回一个Map,其中键是true和false,值是包含对应分类的元素的List。
1 | List<String> words = List.of("apple", "banana", "orange", "grape", "cherry"); |
4.5.3.3连接
Collectors.joining(CharSequence delimiter)
: 将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
1 | List<Person> list = List.of( |
4.5.3.4.规约
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。
Collectors.reducing(BinaryOperator<T> op)
:这是最基本的reducing
方法。它接受一个二元运算符,对流中的所有元素进行归约。该方法将返回一个收集器Optional,它使用提供的运算符对流中的元素进行归约。Collectors.reducing(T identity, BinaryOperator<T> op)
:此方法是reducing
方法的扩展,允许你指定一个初始值。提供的初始值将用作归约操作的起始值。如果流为空,则结果将是提供的初始值。Collectors.reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op)
:这个方法允许你在进行归约之前先将元素映射到另一种类型。你可以指定一个映射函数,并提供一个初始值。然后,它将使用映射函数将元素映射为指定类型,再使用提供的初始值对映射后的结果进行归约。
1 | List<Integer> list = List.of(1, 2, 3, 4); |
4.5.3.5.归集
Collectors.toList()
: 将Stream中的元素收集到一个List中,并返回该List,元素可以重复,有序。Collectors.toSet()
: 将Stream中的元素收集到一个Set中,并返回该Set,元素不能重复,无序Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
: 将Stream中的元素收集到一个Map中,并返回该Map。如果键重复会抛出 IllegalStateException 异常- 参数一表示键的生成规则
- 参数二表示值的生成规则
- Function
- 泛型一:表示流中每一个数据的类型
- 泛型二:表示Map集合中键的数据类型
- 重写方法apply
- 形参:依次表示流里面的每一个数据
- 方法体:生成键/值的代码
- 返回值:已经生成的键/值
Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
: 收集到 Map 集合中,允许指定一个合并函数来处理键冲突。
1 | ArrayList<String> list = new ArrayList<>(); |
5.Java Stream 底层实现
参考文章:深入理解Java Stream流水线
- Title: Stream流
- Author: Lu
- Created at : 2024-05-06 21:22:21
- Updated at : 2025-03-11 12:44:58
- Link: https://lusy.ink/2024/05/06/Stream流/
- License: This work is licensed under CC BY-NC-SA 4.0.