一、Stream 核心概念
Stream 是 Java 8 引入的用于处理集合数据的抽象 API,支持 函数式编程 和 链式操作。
核心特点:
-
无存储:不存储数据,仅通过操作管道处理数据源(集合、数组等)。
-
惰性求值:中间操作(Intermediate Operations)延迟执行,终端操作(Terminal Operations)触发实际计算。
-
不可重用:流一旦被消费(终端操作执行),不可重复使用。
-
并行处理:可通过
parallel()
方法启用并行流,利用多核CPU加速计算。
二、Stream 生命周期
-
创建流:从数据源(集合、数组等)生成流。
-
中间操作:对流进行过滤、转换等操作,返回新流。
-
终端操作:触发实际计算,生成最终结果或副作用。
三、Stream 核心 API 详解
3.1. 创建流的 API
方法 | 说明 |
---|---|
Collection.stream() | 从集合创建顺序流 |
Collection.parallelStream() | 从集合创建并行流 |
Arrays.stream(T[] array) | 从数组创建流 |
Stream.of(T... values) | 从一组元素创建流(支持可变参数) |
Stream.iterate(T seed, UnaryOperator<T> f) | 生成无限流(迭代生成元素,如 0, 2, 4, 6... ) |
Stream.generate(Supplier<T> s) | 生成无限流(通过 Supplier 不断生成元素,如随机数) |
Stream.empty() | 创建空流 |
Stream.concat(Stream a, Stream b) | 合并两个流 |
IntStream.range(int startInclusive, int endExclusive) | 生成整数范围流(不包含结束值) |
Files.lines(Path path) | 从文件按行生成流 |
// 从集合创建顺序流 打印集合中的元素
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
stream1.forEach(System.out::println);
// 创建并行流 输出所有元素 parallelStream主要用于利用cpu的多个核心。通常,任何Java代码都有一个处理流,按顺序执行(向上面一样)。
// 然而,通过使用并行流,我们可以将代码分成多个流,这些流在不同的内核上并行执行,最终的结果是各个结果的组合。然而,处理的顺序不在我们的控制之下。
//因此,建议在以下情况下使用并行流:无论执行顺序如何,结果不受影响,一个元素的状态不影响另一个元素,并且数据源也不受影响。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> parallelStream = numbers.parallelStream();
parallelStream.forEach(n -> System.out.println(Thread.currentThread().getName() + ": " + n));
//从数组创建流
String[] array = {"Java", "Python", "C++"};
Stream<String> stream = Arrays.stream(array); // 从数组创建流
stream.map(String::toUpperCase).forEach(System.out::println); // 转为大写输出
//从一组元素直接创建流
Stream<String> directStreams = Stream.of("One", "Two", "Three"); // 直接传入元素 接收T... 即多个类型为T的元素
directStreams.filter(s -> s.length() > 3).forEach(System.out::println); // 输出长度>3的字符串
// 生成 0, 2, 4, 6, 8(限制5个元素)
//iterator参数
//Params:
//T seed – 初始元素
//UnaryOperator<T> f – UnaryOperator<T> 是一个泛型接口 继承自 Function<T, T>是一种特殊的 Function,其输入和输出类型相同。
Stream.iterate(0, n -> n + 2)
.limit(5)
.forEach(System.out::println);
//Stream.generate():生成无限流(Supplier)
// 生成3个随机数
Stream.generate(new Random()::nextDouble)
.limit(3)
.forEach(System.out::println);
//Stream.empty():创建空流
//顾名思义,就是空的,啥也没有就像空数组,但是这并不是null。流操作过滤、映射等最后没有符合条件的就会返回空流,保证不会出现null操作
//Stream.concat():合并两个流
Stream<String> strStream1 = Stream.of("A", "B");
Stream<String> strStream2 = Stream.of("X", "Y");
Stream<String> mergedStream = Stream.concat(strStream1, strStream2); // 合并流
mergedStream.forEach(System.out::println); // 输出 A, B, X, Y
//IntStream.range():生成整数范围流
// 生成 1~4(不包含5)
IntStream.range(1, 5)
.forEach(n -> System.out.print(n + " ")); // 输出 1 2 3 4
//Files.lines():从文件按行生成流
// 读取文件内容(需处理 IOException)
try (Stream<String> lines = Files.lines(Paths.get("demo.txt"))) {
lines.filter(line -> line.contains("Java"))
.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
-
集合与数组:优先使用
Collection.stream()
或Arrays.stream()
。 -
动态元素:使用
Stream.of()
或Stream.iterate()
/Stream.generate()
。 -
合并与空流:
Stream.concat()
和Stream.empty()
用于特殊场景。 -
文件处理:
Files.lines()
适合逐行处理大文件(自动关闭资源)。
3.2 重点
重点:在Java中实现了Collection接口或者是其子接口的对象均能调用.stream()方法,能够将对象中的元素进行流式处理。因为
stream()
方法是java.util.Collection
接口中的默认方法。这一部分才是我们常用的。正常的List、Set、Queue、Deque接口均继承自Collection接口,所以可以直接使用stream()函数,Map<K,V>接口是没有继承Collection接口的,这是不能直接使用流操作的。Array数组虽然没有直接实现Collection接口,但是可以通过上面的Arrays.stream()创建流。下面是Collection接口源码截图。
3.2 中间操作(Intermediate Operations)
中间操作返回新流,支持链式调用,不触发实际计算。
过滤与切片
方法 | 说明 |
---|---|
filter(Predicate<T> predicate) | 过滤元素,保留满足条件的元素 |
distinct() | 去重(依赖 equals() 和 hashCode() ) |
limit(long maxSize) | 截取前 N 个元素 |
skip(long n) | 跳过前 N 个元素 |
takeWhile(Predicate<T> predicate) (Java 9+) | 取元素直到条件不满足为止(类似 break ) |
dropWhile(Predicate<T> predicate) (Java 9+) | 丢弃元素直到条件不满足为止 |
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream()
.filter(n -> n % 2 == 0) // 保留偶数:2,4,6
.distinct() // 去重(此处无重复)
.limit(2) // 取前2个:2,4
.skip(1) // 跳过1个:4
.forEach(System.out::println);
映射与转换
方法 | 说明 |
---|---|
map(Function<T, R> mapper) | 将元素转换为另一种类型(1:1 转换) |
flatMap(Function<T, Stream<R>> mapper) | 将每个元素转换为流后合并(1:N 转换) |
mapToInt(ToIntFunction<T> mapper) | 转换为 IntStream (原始类型流,避免装箱开销) |
mapToLong(ToLongFunction<T> mapper) | 转换为 LongStream |
mapToDouble(ToDoubleFunction<T> mapper) | 转换为 DoubleStream |
// map:将字符串转为大写
List<String> words = Arrays.asList("apple", "banana");
words.stream()
.map(String::toUpperCase) // APPLE, BANANA
.forEach(System.out::println);
// flatMap:拆分字符串
List<String> lines = Arrays.asList("Hello World", "Java Stream");
lines.stream()
.flatMap(line -> Arrays.stream(line.split(" "))) // Hello, World, Java, Stream
.forEach(System.out::println);
排序与去重
方法 | 说明 |
---|---|
sorted() | 自然排序(元素需实现 Comparable接口 ) |
sorted(Comparator<T> comparator) | 自定义排序 |
List<String> names = Arrays.asList("Tom", "我Alice", "Bob");
names.stream()
// 自然排序的逆序:Tom, Bob, Alice 自然排序是由对象自身的 compareTo 方法施加的排序。
//String对象的自然排序是按照字符的Unicode值升序排列的
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
其他中间操作
方法 | 说明 |
---|---|
peek(Consumer<T> action) | 查看流中元素(调试用,不影响流) |
Stream.of("one", "two", "three")
.filter(s -> s.length() > 3)
.peek(s -> System.out.println("Filtered: " + s)) // 输出符合条件的元素
.map(String::toUpperCase)
.forEach(System.out::println);
3.2 终端操作(Terminal Operations)
终端操作触发流的实际计算,生成结果或副作用,执行后流被关闭。
遍历与匹配
方法 | 说明 |
---|---|
forEach(Consumer<T> action) | 遍历每个元素(无顺序保证,并行流中无序) |
forEachOrdered(Consumer<T> action) | 按流顺序遍历元素(保证顺序,并行流中性能较低) |
anyMatch(Predicate<T> predicate) | 是否至少有一个元素匹配条件(短路操作) |
allMatch(Predicate<T> predicate) | 是否所有元素匹配条件(短路操作) |
noneMatch(Predicate<T> predicate) | 是否没有元素匹配条件(短路操作) |
List<Integer> nums = Arrays.asList(1, 3, 5, 7, 9);
boolean hasEven = nums.stream().anyMatch(n -> n % 2 == 0); // false
boolean allLess10 = nums.stream().allMatch(n -> n < 10); // true
归约与聚合
方法 | 说明 |
---|---|
reduce(T identity, BinaryOperator<T> accumulator) | 归约操作(初始值 + 累积函数) |
reduce(BinaryOperator<T> accumulator) | 归约操作(无初始值,返回 Optional<T> ) |
count() | 返回元素个数 |
max(Comparator<T> comparator) | 返回最大元素(Optional<T> ) |
min(Comparator<T> comparator) | 返回最小元素(Optional<T> ) |
// 求和
int sum = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); // 10
// 找最大值 //不理解Optional<T>接口的,可以看参考中的第二篇文章
Optional<Integer> max = Stream.of(5, 3, 8).max(Integer::compare);
max.ifPresent(System.out::println); // 8
收集结果
方法 | 说明 |
---|---|
collect(Collector<T, A, R> collector) | 使用收集器将流转换为集合或其他结构(最强大的终端操作,十分常用) |
常用 Collectors
工具类方法:
Stream<String> stream = Stream.of("Apple", "Banana", "Cherry", "Apple");
// 转换为 List(允许重复)
List<String> list = stream.collect(Collectors.toList());
System.out.println("List: " + list); // [Apple, Banana, Cherry, Apple]
// 重新创建流(流只能消费一次)
stream = Stream.of("Apple", "Banana", "Cherry", "Apple");
// 转换为 Set(自动去重)
Set<String> set = stream.collect(Collectors.toSet());
System.out.println("Set: " + set); // [Apple, Cherry, Banana]
stream = Stream.of("Apple", "Banana", "Cherry");
// 转换为 Map:键=字符串,值=长度
Map<String, Integer> map = stream.collect(
Collectors.toMap(
s -> s, // 键生成规则
String::length // 值生成规则
)
);
System.out.println("Map: " + map); // {Apple=5, Banana=6, Cherry=6}
stream = Stream.of("Apple", "Banana", "Cherry", "Date", "Elderberry");
// 按字符串长度分组
Map<Integer, List<String>> groupByLength = stream.collect(
Collectors.groupingBy(String::length)
);
System.out.println("Group by length: " + groupByLength);
// 输出: {4=[Date], 5=[Apple], 6=[Banana, Cherry], 10=[Elderberry]}
stream = Stream.of("Apple", "Banana", "Cherry", "Date", "Fig");
// 按长度是否大于5分区
Map<Boolean, List<String>> partition = stream.collect(
Collectors.partitioningBy(s -> s.length() > 5)
);
System.out.println("Partition: " + partition);
// 输出: {false=[Apple, Date, Fig], true=[Banana, Cherry]}
stream = Stream.of("Apple", "Banana", "Cherry");
// 用逗号和空格连接字符串
String joined = stream.collect(Collectors.joining(", "));
System.out.println("Joined: " + joined); // Apple, Banana, Cherry
stream = Stream.of("Apple", "Banana", "Cherry", "Date");
// 统计字符串长度的汇总信息
IntSummaryStatistics stats = stream.collect(
Collectors.summarizingInt(String::length)
);
System.out.println("平均值: " + stats.getAverage()); // 5.25
System.out.println("总数: " + stats.getCount()); // 4
System.out.println("最大值: " + stats.getMax()); // 6
System.out.println("最小值: " + stats.getMin()); // 4
System.out.println("总和: " + stats.getSum()); // 21
其他终端操作
方法 | 说明 |
---|---|
findFirst() | 返回第一个元素(Optional<T> ) |
findAny() | 返回任意元素(并行流中效率更高) |
toArray() | 将流转换为数组 |
Optional<String> first = Stream.of("a", "b", "c").findFirst();
String[] array = Stream.of("a", "b", "c").toArray(String[]::new);
4. 原始类型特化流
为减少装箱开销,Java 提供了针对原始类型的流:
-
IntStream
:处理int
类型数据。 -
LongStream
:处理long
类型数据。 -
DoubleStream
:处理double
类型数据。
IntStream.range(1, 5).forEach(System.out::println); // 1,2,3,4
DoubleStream.of(1.1, 2.2).sum(); // 3.3
5. 并行流与性能优化
通过 parallel()
将顺序流转为并行流:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
.mapToInt(n -> n * 2)
.sum();
注意事项:
-
线程安全:确保操作无状态且不修改共享数据。
-
避免有状态操作:如
sorted()
在并行流中性能可能下降。 -
评估开销:数据量小时可能得不偿失。
6. Stream API 最佳实践
-
优先使用方法引用和 Lambda:简化代码。
-
复用
Collectors
:利用预定义的收集器减少代码量。 -
谨慎使用并行流:仅在数据量大且无状态时使用。
-
组合操作:链式调用保持简洁。
Java Stream API 通过声明式编程极大简化了集合操作,核心方法包括:
-
创建流:
stream()
,Arrays.stream()
,Stream.of()
-
中间操作:
filter
,map
,flatMap
,sorted
,distinct
-
终端操作:
collect
,forEach
,reduce
,count
结合 Collectors
工具类,可以实现复杂的数据转换和聚合。合理使用 Stream API 能提升代码简洁性和可维护性,但在性能敏感场景需谨慎选择并行流。
7.参考
Java进阶-Java Stream API详解与使用-CSDN博客
JAVA 中的Optional类理解、学习与使用_java的optional类-CSDN博客
......