Stream

Stream 是 Java 8 引入的数据处理管道,对集合进行函数式风格的链式操作,不修改源数据,支持串行和并行两种模式。

创建 Stream

// 从集合
List<String> list = List.of("a", "b", "c");
Stream<String> s1 = list.stream();
Stream<String> s2 = list.parallelStream();   // 并行流
 
// 直接创建
Stream<Integer> s3 = Stream.of(1, 2, 3);
 
// 空流
Stream<String> empty = Stream.empty();
 
// 数组
Stream<String> s4 = Arrays.stream(new String[]{"a", "b"});
 
// 无限流
Stream<Integer> iterate  = Stream.iterate(0, n -> n + 2);    // 0,2,4,6...
Stream<Double>  generate = Stream.generate(Math::random);
 
// 数值专用流(避免装箱开销)
IntStream    ints    = IntStream.range(1, 6);      // 1~5
IntStream    ints2   = IntStream.rangeClosed(1, 5); // 1~5
LongStream   longs   = LongStream.of(1L, 2L, 3L);
DoubleStream doubles = DoubleStream.of(1.0, 2.0);

中间操作(惰性,返回新 Stream)

过滤与截断

Stream.of(1, 2, 3, 4, 5)
    .filter(n -> n % 2 == 0)   // [2, 4]
    .limit(1)                   // [2]
    .skip(0);                   // [2]
 
// takeWhile / dropWhile(Java 9+,有序流)
Stream.of(1, 2, 3, 4, 5)
    .takeWhile(n -> n < 4)      // [1, 2, 3],遇到不满足立即停止
 
Stream.of(1, 2, 3, 4, 5)
    .dropWhile(n -> n < 4);     // [4, 5],跳过满足条件的前缀

转换

// map:一对一转换
Stream.of("hello", "world")
    .map(String::toUpperCase);          // ["HELLO", "WORLD"]
 
// mapToInt / mapToLong / mapToDouble:转为数值流
Stream.of("1", "2", "3")
    .mapToInt(Integer::parseInt)
    .sum();                             // 6
 
// flatMap:一对多,展平嵌套
Stream.of(List.of(1, 2), List.of(3, 4))
    .flatMap(Collection::stream);       // [1, 2, 3, 4]
 
Stream.of("hello world", "foo bar")
    .flatMap(s -> Arrays.stream(s.split(" ")));
    // ["hello", "world", "foo", "bar"]

排序与去重

Stream.of(3, 1, 4, 1, 5, 9)
    .distinct()                              // [3, 1, 4, 5, 9]
    .sorted()                                // [1, 3, 4, 5, 9]
    .sorted(Comparator.reverseOrder());      // [9, 5, 4, 3, 1]
 
// 按对象属性排序
users.stream()
    .sorted(Comparator.comparing(User::getAge))
    .sorted(Comparator.comparing(User::getAge).reversed())
    .sorted(Comparator.comparing(User::getAge)
                      .thenComparing(User::getName));

调试

Stream.of(1, 2, 3)
    .peek(n -> System.out.println("before: " + n))
    .map(n -> n * 2)
    .peek(n -> System.out.println("after: " + n))
    .collect(Collectors.toList());

终止操作(触发执行)

收集

List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());
 
// Java 16+
List<String> names2 = users.stream()
    .map(User::getName)
    .toList();                              // 不可变 List
 
Set<String> nameSet = users.stream()
    .map(User::getName)
    .collect(Collectors.toSet());
 
// 指定具体实现类
LinkedList<String> linked = users.stream()
    .collect(Collectors.toCollection(LinkedList::new));
 
// 拼接字符串
String joined = Stream.of("a", "b", "c")
    .collect(Collectors.joining(", ", "[", "]")); // "[a, b, c]"

分组与分区

// 按部门分组
Map<String, List<User>> byDept = users.stream()
    .collect(Collectors.groupingBy(User::getDept));
 
// 分组后统计数量
Map<String, Long> countByDept = users.stream()
    .collect(Collectors.groupingBy(User::getDept, Collectors.counting()));
 
// 分组后取名字列表
Map<String, List<String>> namesByDept = users.stream()
    .collect(Collectors.groupingBy(
        User::getDept,
        Collectors.mapping(User::getName, Collectors.toList())));
 
// 多级分组
Map<String, Map<Integer, List<User>>> grouped = users.stream()
    .collect(Collectors.groupingBy(User::getDept,
             Collectors.groupingBy(User::getAge)));
 
// 分区(按 true/false 分为两组)
Map<Boolean, List<User>> partitioned = users.stream()
    .collect(Collectors.partitioningBy(u -> u.getAge() >= 18));

转为 Map

// toMap(key 冲突时抛异常)
Map<Long, User> userMap = users.stream()
    .collect(Collectors.toMap(User::getId, u -> u));
 
// key 冲突时保留后者
Map<String, User> byName = users.stream()
    .collect(Collectors.toMap(
        User::getName,
        u -> u,
        (existing, replacement) -> replacement));
 
// 保持插入顺序
Map<Long, String> ordered = users.stream()
    .collect(Collectors.toMap(
        User::getId,
        User::getName,
        (a, b) -> a,
        LinkedHashMap::new));

规约与统计

Stream<Integer> nums = Stream.of(1, 2, 3, 4, 5);
 
// reduce
Optional<Integer> sum  = nums.reduce(Integer::sum);    // Optional<Integer>
int sum2 = nums.reduce(0, Integer::sum);                // 有初始值,返回 int
 
// 数值流统计
IntSummaryStatistics stats = IntStream.range(1, 6).summaryStatistics();
stats.getSum();     // 15
stats.getAverage(); // 3.0
stats.getMax();     // 5
stats.getMin();     // 1
stats.getCount();   // 5
 
// 快捷方法(数值流)
IntStream.range(1, 6).sum();
IntStream.range(1, 6).average();  // OptionalDouble
IntStream.range(1, 6).max();      // OptionalInt

查找与匹配

Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
 
s.anyMatch(n -> n > 3);    // true(任一满足)
s.allMatch(n -> n > 0);    // true(全部满足)
s.noneMatch(n -> n > 10);  // true(全不满足)
 
s.findFirst();             // Optional<Integer> 第一个
s.findAny();               // Optional<Integer> 任意(并行流中更快)
 
s.count();                 // 5L

遍历

users.stream().forEach(System.out::println);
 
// 有序遍历(并行流中也保证顺序,性能略低)
users.parallelStream().forEachOrdered(System.out::println);

并行流

// 开启并行
list.parallelStream()
    .filter(...)
    .map(...)
    .collect(Collectors.toList());
 
// 或在中间切换
stream.parallel()
stream.sequential()

注意事项

  • 并行流使用 ForkJoinPool.commonPool(),默认线程数 = CPU 核心数 - 1
  • 数据量小时并行反而更慢(线程调度开销)
  • forEach 在并行流中顺序不定,需要有序时用 forEachOrdered
  • 共享可变状态会导致线程安全问题,收集时用线程安全的 Collector

自定义 Collector

// 示例:收集到不可变 Map
Collector<User, ?, Map<Long, String>> toUnmodifiableMap =
    Collectors.collectingAndThen(
        Collectors.toMap(User::getId, User::getName),
        Collections::unmodifiableMap);
 
Map<Long, String> result = users.stream().collect(toUnmodifiableMap);

常用链式示例

// 过滤 + 转换 + 收集
List<String> adultNames = users.stream()
    .filter(u -> u.getAge() >= 18)
    .map(User::getName)
    .sorted()
    .collect(Collectors.toList());
 
// 求平均年龄
OptionalDouble avgAge = users.stream()
    .mapToInt(User::getAge)
    .average();
 
// 按条件分组统计
users.stream()
    .collect(Collectors.groupingBy(
        u -> u.getAge() >= 18 ? "成年" : "未成年",
        Collectors.counting()));
 
// 找出重复元素
Set<String> seen = new HashSet<>();
List<String> duplicates = list.stream()
    .filter(s -> !seen.add(s))
    .distinct()
    .collect(Collectors.toList());
 
// 扁平化 + 去重 + 排序
List<String> tags = articles.stream()
    .flatMap(a -> a.getTags().stream())
    .distinct()
    .sorted()
    .collect(Collectors.toList());

相关链接