Java 8 Stream API 详细使用指南
一、Stream 概述
Stream 是 Java 8 中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找、过滤和映射数据等操作。Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream 的特点:
- 不是数据结构:不存储数据,只是从数据源(集合、数组等)获取数据
- 不修改源数据:对Stream的操作会产生新Stream,不会修改原始数据源
- 惰性执行:中间操作是惰性的,只有终端操作才会触发实际计算
- 可消费性:Stream只能被消费一次,终端操作后就不能再次使用
二、Stream 操作分类
Stream 操作分为两类:
- **中间操作(Intermediate Operations)**:返回一个新的Stream,可以链式调用
- **终端操作(Terminal Operations)**:返回具体结果或产生副作用,执行后Stream就不能再使用
三、创建 Stream
1. 从集合创建
1 2 3
| List<String> list = Arrays.asList("a", "b", "c"); Stream<String> stream = list.stream(); Stream<String> parallelStream = list.parallelStream();
|
2. 从数组创建
1 2
| String[] array = {"a", "b", "c"}; Stream<String> stream = Arrays.stream(array);
|
3. 使用Stream.of()静态方法
1
| Stream<String> stream = Stream.of("a", "b", "c");
|
4. 使用Stream.generate()创建无限流
1
| Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
|
5. 使用Stream.iterate()创建无限流
1 2
| Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(5);
|
6. 其他创建方式
1 2 3 4 5
| Stream<String> lines = Files.lines(Paths.get("data.txt"));
IntStream charsStream = "abcde".chars();
|
四、中间操作详解
中间操作会返回一个新的Stream,可以链式调用多个中间操作。
1. filter(Predicate) - 过滤
1 2 3 4
| List<String> list = Arrays.asList("apple", "banana", "orange", "grape"); list.stream() .filter(s -> s.length() > 5) .forEach(System.out::println);
|
2. map(Function<T,R>) - 映射
1 2 3 4 5 6 7 8 9 10
| List<String> list = Arrays.asList("a", "b", "c"); list.stream() .map(String::toUpperCase) .forEach(System.out::println);
List<Person> persons = ...; persons.stream() .map(Person::getName) .forEach(System.out::println);
|
3. flatMap(Function<T,Stream>) - 扁平化映射
1 2 3 4 5 6 7 8
| List<List<String>> listOfLists = Arrays.asList( Arrays.asList("a", "b"), Arrays.asList("c", "d") );
List<String> flatList = listOfLists.stream() .flatMap(List::stream) .collect(Collectors.toList());
|
4. distinct() - 去重
1 2 3 4
| List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream() .distinct() .forEach(System.out::println);
|
5. sorted() / sorted(Comparator) - 排序
1 2 3 4 5 6 7 8 9 10 11
| List<String> words = Arrays.asList("banana", "apple", "pear", "orange");
words.stream() .sorted() .forEach(System.out::println);
words.stream() .sorted((s1, s2) -> s1.length() - s2.length()) .forEach(System.out::println);
|
6. peek(Consumer) - 查看元素
1 2 3 4 5 6
| Stream.of("one", "two", "three") .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());
|
7. limit(long) - 限制数量
1 2 3
| Stream.iterate(1, n -> n + 1) .limit(5) .forEach(System.out::println);
|
8. skip(long) - 跳过元素
1 2 3
| Stream.of(1, 2, 3, 4, 5) .skip(2) .forEach(System.out::println);
|
五、终端操作详解
终端操作会触发实际计算,执行后Stream就不能再使用。
1. forEach(Consumer) - 遍历
1 2
| List<String> list = Arrays.asList("a", "b", "c"); list.stream().forEach(System.out::println);
|
2. toArray() - 转换为数组
1
| String[] array = Stream.of("a", "b", "c").toArray(String[]::new);
|
3. collect(Collector<T,A,R>) - 收集为集合
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
Set<String> set = Stream.of("a", "b", "a").collect(Collectors.toSet());
Map<String, Integer> map = Stream.of("apple", "banana", "orange") .collect(Collectors.toMap(Function.identity(), String::length));
String joined = Stream.of("a", "b", "c").collect(Collectors.joining(", "));
|
4. reduce(BinaryOperator) - 归约
1 2 3 4 5 6 7 8 9 10 11
| Optional<Integer> sum = Stream.of(1, 2, 3, 4).reduce(Integer::sum); System.out.println(sum.get());
Optional<Integer> max = Stream.of(1, 2, 3, 4).reduce(Integer::max); System.out.println(max.get());
Integer total = Stream.of(1, 2, 3, 4).reduce(10, Integer::sum); System.out.println(total);
|
5. min()/max() - 最小/最大值
1 2 3 4 5
| Optional<String> min = Stream.of("a", "bb", "ccc").min(Comparator.comparing(String::length)); System.out.println(min.get());
Optional<String> max = Stream.of("a", "bb", "ccc").max(Comparator.comparing(String::length)); System.out.println(max.get());
|
6. count() - 计数
1 2
| long count = Stream.of("a", "b", "c").count(); System.out.println(count);
|
7. anyMatch()/allMatch()/noneMatch() - 匹配
1 2 3 4 5 6 7 8
| boolean anyStartsWithA = Stream.of("apple", "banana", "orange") .anyMatch(s -> s.startsWith("a"));
boolean allLongerThan3 = Stream.of("apple", "banana", "orange") .allMatch(s -> s.length() > 3);
boolean noneStartsWithZ = Stream.of("apple", "banana", "orange") .noneMatch(s -> s.startsWith("z"));
|
8. findFirst()/findAny() - 查找元素
1 2 3 4 5
| Optional<String> first = Stream.of("a", "b", "c").findFirst(); System.out.println(first.get());
Optional<String> any = Stream.of("a", "b", "c").findAny(); System.out.println(any.get());
|
六、数值流
Stream API 提供了专门的数值流来处理原始类型,避免装箱拆箱开销。
1. IntStream
1 2 3 4 5 6 7 8
| IntStream.range(1, 5).forEach(System.out::println); IntStream.rangeClosed(1, 5).forEach(System.out::println);
IntSummaryStatistics stats = IntStream.of(1, 2, 3).summaryStatistics(); System.out.println(stats.getMax()); System.out.println(stats.getMin()); System.out.println(stats.getAverage());
|
2. LongStream
1
| LongStream.range(1, 5).forEach(System.out::println);
|
3. DoubleStream
1
| DoubleStream.of(1.1, 2.2, 3.3).forEach(System.out::println);
|
4. 对象流与数值流转换
1 2 3 4 5 6
| List<Integer> numbers = Arrays.asList(1, 2, 3); IntStream intStream = numbers.stream().mapToInt(Integer::intValue);
Stream<Integer> boxedStream = intStream.boxed();
|
七、并行流
Stream API 可以很容易地实现并行操作。
1. 创建并行流
1 2 3 4 5 6
| List<String> list = Arrays.asList("a", "b", "c"); Stream<String> parallelStream = list.parallelStream();
Stream<String> parallelStream2 = Stream.of("a", "b", "c").parallel();
|
2. 并行流注意事项
- 确保操作是无状态的
- 确保操作不会对共享变量进行修改
- 确保操作是关联的(不影响结果顺序)
- 数据量较大时使用并行流才有优势
- 某些操作(如limit、findFirst)在并行流中性能可能更差
3. 示例
1 2 3 4 5
| long count = IntStream.range(1, 1_000_000) .parallel() .filter(n -> n % 2 == 0) .count(); System.out.println(count);
|
八、收集器(Collectors)详解
Collectors 类提供了许多静态工厂方法,用于创建常见的收集器。
1. 转换为集合
1 2
| List<String> list = stream.collect(Collectors.toList()); Set<String> set = stream.collect(Collectors.toSet());
|
2. 转换为特定集合
1
| Collection<String> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));
|
3. 连接字符串
1 2 3
| String joined = stream.collect(Collectors.joining()); String joinedWithDelimiter = stream.collect(Collectors.joining(", ")); String joinedWithPrefixSuffix = stream.collect(Collectors.joining(", ", "[", "]"));
|
4. 分组
1 2 3 4 5 6 7 8 9 10 11
| Map<Integer, List<String>> groupByLength = Stream.of("a", "bb", "cc", "d") .collect(Collectors.groupingBy(String::length));
Map<Integer, Map<String, List<String>>> multiGroup = Stream.of("a", "bb", "cc", "d") .collect(Collectors.groupingBy(String::length, Collectors.groupingBy(s -> s.startsWith("c") ? "starts with c" : "others")));
|
5. 分区
1 2 3 4
| Map<Boolean, List<String>> partition = Stream.of("a", "bb", "cc", "d") .collect(Collectors.partitioningBy(s -> s.length() > 1));
|
6. 统计
1 2 3 4 5 6 7 8 9 10 11 12
| int totalLength = Stream.of("a", "bb", "ccc") .collect(Collectors.summingInt(String::length));
Double averageLength = Stream.of("a", "bb", "ccc") .collect(Collectors.averagingInt(String::length));
IntSummaryStatistics stats = Stream.of("a", "bb", "ccc") .collect(Collectors.summarizingInt(String::length));
|
7. 自定义收集器
1 2 3 4 5 6 7 8 9 10
| Collector<String, StringBuilder, String> customCollector = Collector.of( StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2), StringBuilder::toString );
String result = Stream.of("a", "b", "c").collect(customCollector); System.out.println(result);
|
九、实战示例
示例1:处理对象集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Person { private String name; private int age; private String city; }
List<Person> people = Arrays.asList( new Person("Alice", 25, "New York"), new Person("Bob", 30, "London"), new Person("Charlie", 20, "New York"), new Person("David", 35, "London") );
List<Person> olderThan25 = people.stream() .filter(p -> p.getAge() > 25) .collect(Collectors.toList());
Map<String, List<Person>> peopleByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity));
Map<String, Double> avgAgeByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.averagingInt(Person::getAge)));
Set<String> cities = people.stream() .map(Person::getCity) .collect(Collectors.toSet());
Optional<Person> oldest = people.stream() .max(Comparator.comparingInt(Person::getAge));
|
示例2:文件处理
1 2 3 4 5 6 7 8 9 10
| try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) { Map<String, Long> wordCount = lines .flatMap(line -> Arrays.stream(line.split("\\s+"))) .collect(Collectors.groupingBy(String::toLowerCase, Collectors.counting())); wordCount.forEach((word, count) -> System.out.println(word + ": " + count)); } catch (IOException e) { e.printStackTrace(); }
|
十、性能考虑与最佳实践
- 避免在流中修改外部状态:保持操作无状态
- 优先使用方法引用:使代码更简洁
- 避免嵌套流:考虑使用flatMap替代
- 注意自动装箱:对原始类型使用专门的流(IntStream等)
- 谨慎使用并行流:
- 数据量大时考虑使用
- 确保操作可以并行化
- 避免共享可变状态
- 重用流:流一旦被消费就不能再次使用
- 考虑短路操作:findFirst、limit等可以提前终止流处理
Stream API 提供了一种声明式处理数据的方式,使代码更简洁、更易读,同时能够充分利用多核处理器的优势。掌握Stream API可以显著提高Java编程效率和代码质量。