JDK1.8-Stream API使用
一、引言
二、Stream结构及构建
1 2 3 | public interface Stream<T> extends BaseStream<T, Stream<T>> public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable { { |
可以看到,Stream继承自BaseStream接口,而BaseStream又继承自AutoCloseable接口,顾名思义,AutoCloseable负责流的自动关闭。
我们这里来了解下生成Stream的几种常用方式:
1 2 3 4 5 6 7 8 9 | // 1. 借助Stream的of方法 Stream stream = Stream.of( "a" , "b" , "c" ); String [] strArray = new String[] { "a" , "b" , "c" }; // 2. 通过数组生成Stream stream = Stream.of(strArray); stream = Arrays.stream(strArray); // 3. 通过集合来生成Stream List<String> list = Arrays.asList(strArray); stream = list.stream(); |
IntStream
,LongStream
,DoubleStrem
,当然我们也可以使用 Stream<Integer>
、Stream<Long>
、Stream<Double>
,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。三、Stream使用
- Intermediate,所谓的中间操作,就是说每次调用做一些处理之后会返回一个新的Stream,这类操作都是惰性的,也就是说并没有真正开始流的遍历。这些操作包括:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel等;
- Terminal,一个Stream只能执行一次结束操作,而且只能是最后一个操作,执行terminal之后,Stream被消费掉了,并且产生了一个结果,这些操作包括:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny等;
1 2 3 | List<String> list = Arrays.asList( "stream" , "map" ); Stream<String> stream = list.stream(); List<String> newList = stream.map(input -> input.toUpperCase()).collect(Collectors.toList()); |
因为Stream只能使用一次,如果我们再操作的话就是抛出异常:
1 2 | List<String> newList = stream.map(input -> input.toUpperCase()).collect(Collectors.toList()); newList = stream.map(input -> input.toLowerCase()).collect(Collectors.toList()); |
异常结果:
1 2 3 4 5 6 | Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203) at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94) at java.util.stream.ReferencePipeline$StatelessOp.<init>(ReferencePipeline.java:618) at java.util.stream.ReferencePipeline$3.<init>(ReferencePipeline.java:187) at java.util.stream.ReferencePipeline.map(ReferencePipeline.java:186) |
3. mapToInt/mapToLong/mapToDouble方法
1 2 | List<Integer> list = Arrays.asList(100, 200); IntStream intStream = list.stream().mapToInt(input -> input); |
4. flatMap方法
前面说过的map方法是一对一的输入输出,而flatMap方法则是一种一对多的映射关系。
1 2 3 4 5 6 7 8 | Stream<List<Integer>> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) ); Stream<Integer> outputStream = inputStream.flatMap((childList) -> childList.stream()); List<Integer> list = outputStream.collect(Collectors.toList()); System. out .println(list); |
结果:
1 | [1,2,3,4,5,6] |
flatMap 是对input Stream 中的层级进行结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面都是单个的数字。我们再来简单看下map方法的对应实现:
1 2 3 4 | // inputStream不变 Stream<Stream<Integer>> stream = inputStream.map(childList -> childList.stream()); List<List<Integer>> list = stream.map(input -> input.collect(Collectors.toList())).collect(Collectors.toList()); System. out .println(list); |
结果:
1 | [[1], [2, 3], [4, 5, 6]] |
从这里可以大致看出它们的区别,对于flatMap来说,它的输入输出大致如下:
1 | {{1,2},{3,4},{5,6}} -> flatMap -> {1,2,3,4,5,6} |
而对map方法来说,则是:
1 | {{1,2},{3,4},{5,6}} -> map -> {1,2}, {3,4},{5,6} |
5. filter方法
该方法用于对Stream中的元素按照某些条件进行过滤,过滤后的元素生成一个新的元素,比如过滤数组中的偶数:
1 2 | Integer[] sixNums = {1, 2, 3, 4, 5, 6}; Stream.of(sixNums).filter(n -> (n % 2 == 0)).forEach(num -> System. out .print(num + " " )); |
结果:
1 | 2 4 6 |
6. foreach方法
类似于for循环,用于遍历Stream中的每个元素,比较简单,可能需要注意的是,forEach 不能修改自己包含的本地变量值,也不能使用 break/return 之类的关键字提前结束循环:
1 2 3 4 5 | Stream<String> stream = Stream.of( "hello" , "world" ); // 方式1 stream.forEach(num -> System. out .print(num)); // 方式2 stream.forEach(System. out ::print); |
7. findFirst
返回Stream对象的第一个元素,由于返回的是Optional
,所以返回的值有可能为空:
1 2 3 4 | Stream<String> stream = Stream.of( "hello" , "world" ); Optional<String> optional = stream.findFirst(); String name = optional.map(String::toLowerCase).orElse( "" ); System. out .println(name); |
Optional是jdk8提供的一种用于优雅的解决 NullPointExecption 的方式,等再写文章我们来学习一下。
8. reduce方法
这个方法的作用主要是把Stream中的元素组合起来,比如说字符串拼接,数值类型的求和等都是特殊的reduce操作,并且我们可以根据重载方法选择是否有初始值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 字符串连接,concat = "ABCD" String concat = Stream.of( "A" , "B" , "C" , "D" ).reduce( "" , String::concat); // 求最小值,minValue = -3.0 double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); // 求和,sumValue = 10, 有起始值 int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); // 求和的另一种形式 int sum = Stream.of(1, 2, 3, 4).reduce(0, (a,b) -> a+b); // 求和,sumValue = 10, 无起始值 sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum). get (); // 过滤,字符串连接,concat = "ace" concat = Stream.of( "a" , "B" , "c" , "D" , "e" , "F" ) .filter(x -> x.compareTo( "Z" ) > 0) .reduce( "" , String::concat); |
9. limit/skip方法
1 2 3 4 | List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); list.stream().limit(8).forEach(System. out ::print); System. out .println(); list.stream().limit(8).skip(3).forEach(System. out ::print); |
结果:
1 2 | 12345678 45678 |
10. sorted
sorted方法是用于对Stream元素进行排序的,我们可以按照默认的自然排序规则进行排序, 也可以指定具体的比较器来进行排序:
1 2 | Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator); |
Stream的sorted方法比数组的排序更强之处在于,你可以首先对 Stream 进行各类 map、filter、limit、skip操作之后再进行排序:
1 2 3 4 | List<Integer> list = Arrays.asList(5, 7, 1, 4, 2, 6, 3, 8, 9, 10); list.stream().limit(5).sorted().forEach(System. out ::print); System. out .println(); list.stream().limit(5).sorted(Comparator.reverseOrder()).forEach(System. out ::print); |
结果:
1 2 | 12457 75421 |
11. min/max/distinct方法
1 2 3 4 5 | List<Integer> list = Arrays.asList(5, 7, 1, 4, 2, 6, 3, 8, 9, 10); // 通过Stream的min方法 Integer min2 = list.stream().min(Comparator.naturalOrder()). get (); // 通过IntStrem的min方法 Integer min1 = list.stream().mapToInt(input -> input).min().getAsInt(); |
而distinct方法是用于过滤重复数据的:
1 2 | List<Integer> list = Arrays.asList(5, 7, 1, 7, 2, 6, 3, 9, 9, 10); list.stream().distinct().forEach(System. out ::print); |
结果:
1 | 571263910 |
12. allMatch/anyMatch/noneMatch方法
- allMatch, 对Stream中的所有元素进行判断,全部满足条件的时候返回true,只要有一个不符合条件就返回false;
- anyMatch,Stream中只要有一个满足条件,就返回true;
- noneMatch,Stream中没有一个元素满足条件,这时返回true;
1 2 3 4 5 | List<Integer> list = Arrays.asList(5, 7, 1, 7, 2, 6, 3, 9, 9, 10); boolean isAllRight = list.stream().allMatch(input -> (input > 0)); boolean isAnyRight = list.stream().anyMatch(input -> (input > 2)); boolean isNoneRight = list.stream().noneMatch(input -> (input < 0)); System. out .println( "isAllRight:" + isAllRight + " isAnyRight:" + isAnyRight + " isNoneRight:" + isNoneRight); |
结果:
1 | isAllRight: true isAnyRight: true isNoneRight: true |
13. peek方法
1 2 3 4 5 6 | Stream.of( "one" , "two" , "three" , "four" ) .filter(e -> e.length() > 3) .peek(e -> System. out .println( "Filtered value: " + e)) .map(String::toUpperCase) .peek(e -> System. out .println( "Mapped value: " + e)) .collect(Collectors.toList()); |
结果:
1 2 3 4 | Filtered value: three Mapped value: THREE Filtered value: four Mapped value: FOUR |
如上,我们可以在遍历列表的时候,先打印字符串,再将该字符串转成大写再打印出来。
另外,根据API的说明,该方法主要用于调试,方便debug查看Stream内进行处理的每个元素。
14. forEachOrdered方法forEachOrdered
方法和 forEach 方法功能一样,都是用于遍历Stream,不同的地方在于并行流的处理上。并行的时候 forEach 方法为了效率,它的顺序和Stream元素的顺序不一定完全一样,而forEachOrdered 方法的顺序则是和Stream元素的顺序是一样的。1 2 3 4 5 6 7 8 9 10 11 12 | List<String> list = Arrays.asList( "x" , "y" , "z" ); list.parallelStream().forEach(x -> System. out .print( " " + x)); System. out .println(); list.parallelStream().forEachOrdered(x -> System. out .print( " " + x)); System. out .println(); //输出的顺序不一定(效率更高) Stream.of( "AAA" , "BBB" , "CCC" ).parallel().forEach(s -> System. out .print( " " + s)); System. out .println(); //输出的顺序与元素的顺序严格一致 Stream.of( "AAA" , "BBB" , "CCC" ).parallel().forEachOrdered(s -> System. out .print( " " + s)); |
结果:
1 2 3 4 | y x z x y z BBB CCC AAA AAA BBB CCC |
15. toArray方法
1 2 | Object[] toArray(); <A> A[] toArray(IntFunction<A[]> generator); |
对应例子:
1 2 3 | List<String> list = Arrays.asList( "x" , "y" , "z" ); Object[] objects = list.stream().toArray(); Integer[] arrays = list.stream().toArray(Integer[]:: new ); |
16. count方法
count方法表示获取Stream流中元素的数量,返回long类型:1 2 3 4 | // 打印 4 long num = Stream.of(1, 2, 3, 4).count(); // 打印 3 long num = Stream.of(1, 2, 3, 4).limit(3).count(); |
17. findAny方法
findAny方法表示从流中随便选择一个元素,该方法返回的值是不稳定的:
1 | Integer num = Stream.of(1, 2, 3, 4).findAny(). get (); |
18. collect方法
collect方法我们前面已经接触过,有两个方法,我们先看一下简单的那个:
1 | <R, A> R collect(Collector<? super T, A, R> collector); |
在前文中,我们使用map方法对流进行处理之后,返回的还是一个Stream,而此时我们是无法我们的集合操作的,这时候就需要将流重新转换为集合框架中对应的集合,那么这时候我们就可以通过该方法来实现:
1 | List<String> list = Arrays.asList( "hello" , "world" ).stream().collect(Collectors.toList()); |
该方法接收一个Collector类型的参数,但幸运的是Java8给我们提供了Collector的工具类:Collectors,这其中已经定义了一些静态工厂方法,比如Collectors.toCollection()
生成集合,Collectors.toList()
生成List,Collectors.toSet()
生成Set等,Collectors是个很好的工具类,封装了许多操作,后续我们再来介绍。
接下来,再简单看下该方法的另一个重载方法:
1 2 3 | <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); |
1 2 3 4 5 | List<Integer> nums = Arrays.asList(1, 1, null , 2, 3, 4, null , 5, 6, 7, 8, 9, 10); List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null ). collect(() -> new ArrayList<Integer>(), (list, item) -> list.add(item), (list1, list2) -> list1.addAll(list2)); |
使用方法引用来优化下该例子:
1 2 3 | List<Integer> nums = Arrays.asList(1, 1, null , 2, 3, 4, null , 5, 6, 7, 8, 9, 10); List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null ). collect(ArrayList:: new , ArrayList::add, ArrayList::addAll); |
接下来,说下该方法:该方法是将一个Integer类型的List,先过滤掉为null的元素,然后把剩下的元素放到新的List中。再来看一下这些参数:
- 第一个函数生成一个新的ArrayList实例;
- 第二个函数接受两个参数,第一个是前面生成的ArrayList对象,第二个是stream中包含的元素,函数体就是把stream中的元素加入ArrayList对象中。第二个函数被反复调用直到原stream的元素被消费毕;
- 第三个函数也是接受两个参数,这两个都是ArrayList类型的,函数体就是把第二个ArrayList全部加入到第一个中;
1 2 | public static <T> Stream<T> of(T t) public static <T> Stream<T> of(T... values) |
比如说:
1 2 | Stream stream = Stream.of( "a" ); IntStream intStream= IntStream.of(1, 2, 3); |
2. builder方法
通过使用Stream.builder方法生成Builder对象,Builder对象是Stream的可变构造器,也称为流构造器,该对象允许单独生成元素并添加到构造器,然后来生成流,来避免使用ArrayList作为临时缓冲区产生的复制开销。流构建器有一个生命周期,它从一个构建阶段开始,在这个阶段中可以添加元素,然后过渡到一个构建阶段,在这个阶段之后,可能不会添加元素。构建阶段从调用build()方法开始,该方法创建一个有序流,其元素是按照添加到流构建器的顺序添加到流构建器的元素。
1 2 3 4 5 6 7 8 | Stream.Builder builder = Stream.builder(); builder.accept( "hello" ); builder.add( "world" ); Stream stream = builder.build(); stream.forEach(input -> System. out .print(input + " " )); //或者 Stream<String> streamBuilder = Stream.<String>builder().add( "hello" ).add( "world" ).build(); |
结果:
1 | hello world |
3. empty方法
创建一个不包含任何元素的有序的Stream流:
1 | Stream<Integer> stream = Stream.empty(); |
4. iterate方法
Stream的iterate方法和reduce方法有点像,接受一个种子值,和一个 UnaryOperator(例如 f),然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推:
1 2 | // 比如生成等差数列 0 3 6 9 12 15 18 21 24 27 Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System. out .print(x + " " )); |
同样,iterate也是无限的,在进行iterate的时候,必须要有limit这样的操作来限制大小,但iterate生成的Stream是连续且有序的。
5. generate方法
1 2 3 4 | // 生成10个随机数 Stream.generate( new Random()::nextInt).limit(10).forEach(System. out ::println); //另外一种方式 IntStream.generate(() -> ( int ) (System.nanoTime() % 100)).limit(10).forEach(System. out ::println); |
6. concat方法
返回两个Stream流连接的流:1 | public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) |
1 2 3 | Stream<Integer> stream1 = Stream.of(1, 2, 3, 4); Stream<Integer> stream2 = Stream.of(5, 6, 7, 8); Stream.concat(stream1, stream2).forEach(System. out ::print); |
四、IntStream LongStream DoubleStream补充
因为这三个Stream中的操作属于数值操作,所以它们中有些方法Stream中并没有,我们也来简单介绍下。由于这三个Stream都差不多,我们就以IntStream来进行举例。
1. sum/min/max/count/average方法
min,max,count方法Stream中都有,只不过在IntStream中这些方法的参数和返回值可能和Stream方法的返回值有稍许的不同,不过功能是一样的。
1 2 3 4 5 6 7 | // 计算min IntStream.of(1, 2, 3, 4).min().getAsInt(); IntStream.of(1, 2, 3, 4).reduce(0, Integer::min); // 计算max IntStream.of(1, 2, 3, 4).max().getAsInt(); IntStream.of(1, 2, 3, 4).reduce(0, Integer::max); |
sum方法的话,就是用于计算Stream中元素值的和,同样也可也使用reduce方法来代替:
1 2 | int sum = IntStream.of(1, 2, 3, 4).sum(); int sum = IntStream.of(1, 2, 3, 4).reduce(0, Integer::sum); |
而average方法是用于计算平均值的,返回的是OptionalDouble类型:
1 2 | // 计算平均值 double average = IntStream.of(1, 2, 3, 4).average().getAsDouble(); |
2. summaryStatistics方法
该方法用于获取Stream流的各项汇总数据,我们直接看例子就明白了:
1 2 3 4 5 6 7 8 | // 各种计算值的汇总数据 IntSummaryStatistics summaryStatistics = IntStream.of(1, 2, 3, 4).summaryStatistics(); // 平均值,元素个数,最大值,最小值,总和 System. out .println(summaryStatistics.getAverage()); System. out .println(summaryStatistics.getCount()); System. out .println(summaryStatistics.getMax()); System. out .println(summaryStatistics.getMin()); System. out .println(summaryStatistics.getSum()); |
3. asLongStream/asDoubleStream方法
这两个方法比较简单,就是转为对应的LongStream流和DoubleStream流。
4. boxed方法
基础类型的装箱操作,比如将int类型装箱称为Integer类型:
1 | Stream<Integer> stream = IntStream.of(1, 2, 3, 4).boxed(); |
5. range方法
range方法是IntStream中的静态方法,用于构建某段范围的IntStream流:
1 | public static IntStream range( int startInclusive, int endExclusive) |
创建的Stream流包含开始值 startInclusive(inclusive),但不包含结束值 endExclusive(exclusive):
1 2 3 | IntStream intStream = IntStream.range(1, 5); // 1 2 3 4 intStream.forEach(x -> System. out .print(x + " " )); |
6. rangeClosed方法
rangeClosed方法和range方法唯一的不同就是,创建的Stream流既包含开始值,又包含结束值,这点从参数命名上就可以知道。不得不说,该方法参数的命名很规范,值得我们学习:
1 | public static IntStream rangeClosed( int startInclusive, int endInclusive) |
对应的实例:
1 2 3 | IntStream intStream = IntStream.rangeClosed(1, 5); // 1 2 3 4 5 intStream.forEach(x -> System. out .print(x + " " )); |
五、BaseStream中的方法
上面忘了说了,BaseStream作为Stream的底层接口,有几个方法值得了解一下:
1. parallel方法
返回一个并行的且等效流,可能返回该流本身,因为该Stream已经是并行的,或者该Stream的底层状态被修改为了并行。
2. isParallel方法
判断该Stream是否是并行的:
1 2 3 4 5 | IntStream intStream = IntStream.rangeClosed(1, 5); // false boolean isParallel = intStream.isParallel(); // true isParallel = intStream.parallel().isParallel(); |
3. iterator/spliterator方法
这两个方法就比较简单了,iterator就是返回迭代器对象,而spliterator则是返回一个并行的迭代器对象;
4. unordered方法
返回一个无序的等效的Stream,可能返回的是Stream本身,因为该Stream已经是无序的,或者该Stream的底层状态被修改为了无序。当不考虑流的顺序时,可以使用无序的Stream来进行操作,这样可以加快一些方法的执行速度,提高一些性能,一般用于并行的时候。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具