2839 字
14 分钟

Java Stream API详解

2026-02-03
浏览量 加载中...

Java Stream API详解#

什么是Stream API?#

Stream API是Java 8引入的一个新特性,它提供了一种声明式的方式来处理集合数据。Stream API允许我们以函数式编程的风格对集合进行操作,包括过滤、映射、排序、归约等。

Stream API的核心概念#

1. Stream#

Stream是一个数据流,它可以从集合、数组或其他数据源生成。Stream不是数据结构,它只是一个操作管道,用于处理数据。

2. 操作类型#

Stream API的操作分为两类:

  • 中间操作:返回一个新的Stream,可以链式调用多个中间操作
  • 终端操作:返回一个结果或副作用,触发实际的计算

3. 惰性求值#

Stream的中间操作是惰性求值的,只有当执行终端操作时,才会触发实际的计算。

4. 无状态和有状态操作#

  • 无状态操作:每个元素的处理不依赖于之前的元素,如mapfilter
  • 有状态操作:每个元素的处理依赖于之前的元素,如sorteddistinct

5. 并行Stream#

Stream API支持并行处理,可以通过parallel()方法将Stream转换为并行Stream。

Stream API的基本操作#

1. 创建Stream#

1.1 从集合创建#

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
// 并行Stream
Stream<String> parallelStream = list.parallelStream();

1.2 从数组创建#

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
// 从数组的指定范围创建
Stream<String> streamRange = Arrays.stream(array, 0, 2); // 从索引0到2(不包括2)

1.3 使用Stream.of()#

Stream<String> stream = Stream.of("a", "b", "c");
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

1.4 创建空Stream#

Stream<String> emptyStream = Stream.empty();

1.5 使用Stream.generate()#

// 生成无限Stream
Stream<Double> randomStream = Stream.generate(Math::random);
// 生成有限Stream
Stream<Double> limitedStream = randomStream.limit(5);

1.6 使用Stream.iterate()#

// 生成无限Stream
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2);
// 生成有限Stream
Stream<Integer> limitedStream = evenStream.limit(5);

1.7 从文件创建#

try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}

2. 中间操作#

2.1 filter#

过滤元素,只保留满足条件的元素。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> filteredNames = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
System.out.println(filteredNames); // 输出: [Alice, Charlie, David]

2.2 map#

将每个元素映射到另一个元素。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(nameLengths); // 输出: [5, 3, 7]

2.3 flatMap#

将每个元素映射到一个Stream,然后将所有Stream连接成一个Stream。

List<List<String>> lists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d"),
Arrays.asList("e", "f")
);
List<String> flattenedList = lists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flattenedList); // 输出: [a, b, c, d, e, f]

2.4 distinct#

去除重复元素。

List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 1, 4, 5, 4);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNumbers); // 输出: [1, 2, 3, 4, 5]

2.5 sorted#

排序元素。

List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出: [1, 2, 3, 4, 5]
// 自定义排序
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> sortedNames = names.stream()
.sorted((s1, s2) -> s2.compareTo(s1)) // 降序
.collect(Collectors.toList());
System.out.println(sortedNames); // 输出: [Charlie, Bob, Alice]

2.6 peek#

对每个元素执行操作,但不修改Stream。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> processedNumbers = numbers.stream()
.peek(n -> System.out.println("Processing: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("Processed: " + n))
.collect(Collectors.toList());
System.out.println(processedNumbers); // 输出: [2, 4, 6, 8, 10]

2.7 limit#

限制Stream的大小。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> limitedNumbers = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println(limitedNumbers); // 输出: [1, 2, 3, 4, 5]

2.8 skip#

跳过前n个元素。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> skippedNumbers = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println(skippedNumbers); // 输出: [6, 7, 8, 9, 10]

3. 终端操作#

3.1 forEach#

遍历每个元素。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(System.out::println);
// 输出:
// Alice
// Bob
// Charlie

3.2 collect#

将Stream转换为集合或其他数据结构。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 转换为List
List<String> collectedList = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
// 转换为Set
Set<String> collectedSet = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toSet());
// 转换为Map
Map<String, Integer> nameLengthMap = names.stream()
.collect(Collectors.toMap(name -> name, String::length));
// 转换为特定的集合
LinkedList<String> linkedList = names.stream()
.collect(Collectors.toCollection(LinkedList::new));

3.3 reduce#

将Stream中的元素归约为一个值。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b);
System.out.println(sum.orElse(0)); // 输出: 15
// 求和,指定初始值
int sumWithInitial = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(sumWithInitial); // 输出: 15
// 求最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
System.out.println(max.orElse(0)); // 输出: 5
// 连接字符串
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String joinedNames = names.stream()
.reduce("", (a, b) -> a + ", " + b);
System.out.println(joinedNames); // 输出: , Alice, Bob, Charlie
// 更有效的字符串连接
String joinedNames2 = names.stream()
.collect(Collectors.joining(", "));
System.out.println(joinedNames2); // 输出: Alice, Bob, Charlie

3.4 count#

计算Stream中元素的数量。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
long count = names.stream()
.filter(name -> name.length() > 3)
.count();
System.out.println(count); // 输出: 3

3.5 anyMatch#

检查是否有任何元素满足条件。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
boolean hasBob = names.stream()
.anyMatch(name -> name.equals("Bob"));
System.out.println(hasBob); // 输出: true

3.6 allMatch#

检查是否所有元素都满足条件。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean allPositive = numbers.stream()
.allMatch(n -> n > 0);
System.out.println(allPositive); // 输出: true

3.7 noneMatch#

检查是否没有元素满足条件。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean noneNegative = numbers.stream()
.noneMatch(n -> n < 0);
System.out.println(noneNegative); // 输出: true

3.8 findFirst#

返回第一个元素。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> first = names.stream()
.findFirst();
System.out.println(first.orElse("No element")); // 输出: Alice

3.9 findAny#

返回任意一个元素(在并行Stream中可能更高效)。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> any = names.stream()
.findAny();
System.out.println(any.orElse("No element")); // 输出: Alice

3.10 min#

返回最小元素。

List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2);
Optional<Integer> min = numbers.stream()
.min(Integer::compare);
System.out.println(min.orElse(0)); // 输出: 1

3.11 max#

返回最大元素。

List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2);
Optional<Integer> max = numbers.stream()
.max(Integer::compare);
System.out.println(max.orElse(0)); // 输出: 5

高级操作#

1. 分组和分区#

1.1 groupingBy#

按指定条件分组。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 按长度分组
Map<Integer, List<String>> namesByLength = names.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println(namesByLength);
// 输出: {3=[Bob, Eve], 4=[David], 5=[Alice], 7=[Charlie]}
// 按长度分组,然后统计数量
Map<Integer, Long> countByLength = names.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
System.out.println(countByLength);
// 输出: {3=2, 4=1, 5=1, 7=1}
// 按长度分组,然后将名字连接起来
Map<Integer, String> joinedByLength = names.stream()
.collect(Collectors.groupingBy(String::length,
Collectors.joining(", ")));
System.out.println(joinedByLength);
// 输出: {3=Bob, Eve, 4=David, 5=Alice, 7=Charlie}

1.2 partitioningBy#

按布尔条件分区,结果是一个只有两个键(true和false)的Map。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 按奇偶分区
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(partitioned);
// 输出: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}

2. 映射和收集#

2.1 mapping#

在收集之前对元素进行映射。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 收集名字的长度
List<Integer> nameLengths = names.stream()
.collect(Collectors.mapping(String::length, Collectors.toList()));
System.out.println(nameLengths); // 输出: [5, 3, 7]

2.2 collectingAndThen#

在收集之后对结果进行操作。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 收集并转换为不可变列表
List<String> immutableList = names.stream()
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));

3. 并行Stream#

3.1 创建并行Stream#

// 从集合创建
List<String> list = Arrays.asList("a", "b", "c", "d", "e");
Stream<String> parallelStream = list.parallelStream();
// 从普通Stream转换
Stream<String> stream = list.stream();
Stream<String> parallelStream2 = stream.parallel();

3.2 并行Stream的使用#

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 并行求和
int sum = numbers.parallelStream()
.reduce(0, Integer::sum);
System.out.println(sum); // 输出: 55
// 并行排序
List<Integer> sortedNumbers = numbers.parallelStream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

3.3 并行Stream的注意事项#

  • 并行Stream不保证元素的处理顺序
  • 并行Stream中的操作应该是无副作用的
  • 并行Stream可能会增加开销,对于小数据集可能不如串行Stream高效
  • 并行Stream使用的是Fork/Join框架,默认使用的线程数是CPU核心数

4. 自定义收集器#

可以通过实现Collector接口来创建自定义收集器。

// 自定义收集器,收集到StringBuilder
Collector<String, StringBuilder, String> stringBuilderCollector = Collector.of(
StringBuilder::new, // 供应者
(sb, str) -> sb.append(str).append(", "), // 累加器
(sb1, sb2) -> sb1.append(sb2), // 组合器
sb -> sb.toString() // 完成器
);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String result = names.stream()
.collect(stringBuilderCollector);
System.out.println(result); // 输出: Alice, Bob, Charlie,

Stream API的最佳实践#

1. 链式调用#

Stream API支持链式调用,应该将多个操作链接在一起,提高代码的可读性。

// 好的做法
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
// 不好的做法
Stream<String> stream = names.stream();
Stream<String> filteredStream = stream.filter(name -> name.length() > 3);
Stream<String> mappedStream = filteredStream.map(String::toUpperCase);
Stream<String> sortedStream = mappedStream.sorted();
List<String> result = sortedStream.collect(Collectors.toList());

2. 使用方法引用#

对于简单的lambda表达式,应该使用方法引用,提高代码的可读性。

// 好的做法
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// 不好的做法
List<Integer> nameLengths = names.stream()
.map(name -> name.length())
.collect(Collectors.toList());

3. 避免副作用#

Stream的操作应该是无副作用的,不应该修改外部状态。

// 不好的做法
List<Integer> result = new ArrayList<>();
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(result::add); // 有副作用
// 好的做法
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList()); // 无副作用

4. 选择合适的操作#

应该选择合适的Stream操作,避免不必要的计算。

// 好的做法(使用anyMatch,找到第一个匹配项就停止)
boolean hasLongName = names.stream()
.anyMatch(name -> name.length() > 10);
// 不好的做法(遍历所有元素)
boolean hasLongName = names.stream()
.filter(name -> name.length() > 10)
.count() > 0;

5. 注意并行Stream的使用#

  • 对于小数据集,串行Stream可能更高效
  • 对于计算密集型任务,并行Stream可能更高效
  • 对于IO密集型任务,并行Stream可能会增加开销
  • 并行Stream中的操作应该是线程安全的

6. 处理Optional#

当使用可能返回Optional的操作时,应该适当处理Optional。

// 好的做法
Optional<String> first = names.stream()
.filter(name -> name.length() > 3)
.findFirst();
String result = first.orElse("No element");
// 或者
first.ifPresent(System.out::println);
// 不好的做法
Optional<String> first = names.stream()
.filter(name -> name.length() > 3)
.findFirst();
if (first.isPresent()) {
String result = first.get();
System.out.println(result);
}

常见陷阱#

1. 重复使用Stream#

Stream只能使用一次,使用后会被关闭。

// 错误的做法
Stream<String> stream = names.stream();
stream.filter(name -> name.length() > 3);
stream.map(String::toUpperCase); // 会抛出IllegalStateException
// 正确的做法
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());

2. 副作用#

Stream的操作应该是无副作用的,否则可能会导致意外的结果。

3. 并行Stream的线程安全#

并行Stream中的操作应该是线程安全的,否则可能会导致并发问题。

4. 性能问题#

  • 对于小数据集,并行Stream可能不如串行Stream高效
  • 过度使用Stream API可能会导致代码可读性下降

5. 内存泄漏#

无限Stream如果不使用limit等操作限制大小,可能会导致内存泄漏。

6. 错误的收集器#

使用错误的收集器可能会导致意外的结果。

总结#

Stream API是Java 8引入的一个强大特性,它提供了一种声明式的方式来处理集合数据。Stream API支持函数式编程风格,包括过滤、映射、排序、归约等操作。Stream API的中间操作是惰性求值的,只有当执行终端操作时,才会触发实际的计算。

本文介绍了Stream API的基本概念、核心操作和最佳实践。希望本文能够帮助你更好地理解和使用Stream API。

练习#

  1. 编写一个程序,使用Stream API过滤出列表中的偶数。

  2. 编写一个程序,使用Stream API将列表中的字符串转换为大写。

  3. 编写一个程序,使用Stream API计算列表中数字的总和。

  4. 编写一个程序,使用Stream API找到列表中的最大值。

  5. 编写一个程序,使用Stream API对列表中的元素进行排序。

  6. 编写一个程序,使用Stream API将列表中的元素去重。

  7. 编写一个程序,使用Stream API将列表中的元素分组。

  8. 编写一个程序,使用Stream API将列表中的元素分区。

  9. 编写一个程序,使用Stream API创建一个并行Stream并处理数据。

  10. 编写一个程序,使用Stream API从文件中读取行并处理。

通过这些练习,你将更加熟悉Stream API的使用,为后续的学习做好准备。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Java Stream API详解
https://blog.vanilla.net.cn/posts/2026-02-05-java-stream-api/
作者
鹁鸪
发布于
2026-02-03
许可协议
CC BY-NC-SA 4.0

评论区

目录