Java Stream API详解
Java Stream API详解
什么是Stream API?
Stream API是Java 8引入的一个新特性,它提供了一种声明式的方式来处理集合数据。Stream API允许我们以函数式编程的风格对集合进行操作,包括过滤、映射、排序、归约等。
Stream API的核心概念
1. Stream
Stream是一个数据流,它可以从集合、数组或其他数据源生成。Stream不是数据结构,它只是一个操作管道,用于处理数据。
2. 操作类型
Stream API的操作分为两类:
- 中间操作:返回一个新的Stream,可以链式调用多个中间操作
- 终端操作:返回一个结果或副作用,触发实际的计算
3. 惰性求值
Stream的中间操作是惰性求值的,只有当执行终端操作时,才会触发实际的计算。
4. 无状态和有状态操作
- 无状态操作:每个元素的处理不依赖于之前的元素,如
map、filter - 有状态操作:每个元素的处理依赖于之前的元素,如
sorted、distinct
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();
// 并行StreamStream<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()
// 生成无限StreamStream<Double> randomStream = Stream.generate(Math::random);
// 生成有限StreamStream<Double> limitedStream = randomStream.limit(5);1.6 使用Stream.iterate()
// 生成无限StreamStream<Integer> evenStream = Stream.iterate(0, n -> n + 2);
// 生成有限StreamStream<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// Charlie3.2 collect
将Stream转换为集合或其他数据结构。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 转换为ListList<String> collectedList = names.stream() .filter(name -> name.length() > 3) .collect(Collectors.toList());
// 转换为SetSet<String> collectedSet = names.stream() .filter(name -> name.length() > 3) .collect(Collectors.toSet());
// 转换为MapMap<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, Charlie3.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); // 输出: 33.5 anyMatch
检查是否有任何元素满足条件。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");boolean hasBob = names.stream() .anyMatch(name -> name.equals("Bob"));System.out.println(hasBob); // 输出: true3.6 allMatch
检查是否所有元素都满足条件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);boolean allPositive = numbers.stream() .allMatch(n -> n > 0);System.out.println(allPositive); // 输出: true3.7 noneMatch
检查是否没有元素满足条件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);boolean noneNegative = numbers.stream() .noneMatch(n -> n < 0);System.out.println(noneNegative); // 输出: true3.8 findFirst
返回第一个元素。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");Optional<String> first = names.stream() .findFirst();System.out.println(first.orElse("No element")); // 输出: Alice3.9 findAny
返回任意一个元素(在并行Stream中可能更高效)。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");Optional<String> any = names.stream() .findAny();System.out.println(any.orElse("No element")); // 输出: Alice3.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)); // 输出: 13.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接口来创建自定义收集器。
// 自定义收集器,收集到StringBuilderCollector<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。
练习
-
编写一个程序,使用Stream API过滤出列表中的偶数。
-
编写一个程序,使用Stream API将列表中的字符串转换为大写。
-
编写一个程序,使用Stream API计算列表中数字的总和。
-
编写一个程序,使用Stream API找到列表中的最大值。
-
编写一个程序,使用Stream API对列表中的元素进行排序。
-
编写一个程序,使用Stream API将列表中的元素去重。
-
编写一个程序,使用Stream API将列表中的元素分组。
-
编写一个程序,使用Stream API将列表中的元素分区。
-
编写一个程序,使用Stream API创建一个并行Stream并处理数据。
-
编写一个程序,使用Stream API从文件中读取行并处理。
通过这些练习,你将更加熟悉Stream API的使用,为后续的学习做好准备。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!