1. 创建流(7 种常用入口)

    Stream<String> s1 = Stream.of("a", "b", "c");
    Stream<String> s2 = Arrays.stream(arr);
    Stream<String> s3 = list.stream();          // 集合
    Stream<String> s4 = Files.lines(Paths.get("a.txt")); // IO
    Stream<Integer> s5 = Stream.iterate(0, n -> n + 2);  // 无限流
    Stream<Integer> s6 = Stream.generate(() -> 1);       // 无限流
    IntStream s7 = IntStream.range(1, 100);   // 基本类型
  2. 中间操作(lazy,链式)

    stream
        .filter(x -> x % 2 == 0)            // 过滤
        .map(String::valueOf)               // 1:1 转换
        .flatMap(x -> Arrays.stream(x.split(","))) // 1:N 打平
        .distinct()                         // 去重
        .sorted(Comparator.comparing(User::getAge).reversed())
        .peek(System.out::println)          // 调试打印,不会触发消费
        .limit(5)                           // 截断
        .skip(3)                            // 跳过
        .takeWhile(x -> x < 50)   // Java9+ 遇到第一个 false 停止
        .dropWhile(x -> x < 50);  // Java9+ 丢掉前缀
  3. 终端操作(触发执行)

    // 3.1 遍历/消费
    forEach(System.out::println);
    
    // 3.2 聚合
    long cnt = stream.count();
    Optional<Integer> max = stream.max(Integer::compare);
    Integer sum = stream.mapToInt(Integer::intValue).sum();
    OptionalDouble avg = stream.mapToInt(Integer::intValue).average();
    
    // 3.3 规约
    Integer total = stream.reduce(0, Integer::sum);
    String joined = stream.reduce("", (a, b) -> a + "," + b);
    
    // 3.4 收集(最灵活)
    List<String> list = stream.collect(Collectors.toList());
    Set<String> set = stream.collect(Collectors.toSet());
    Map<String, User> map = stream.collect(Collectors.toMap(User::getId, u -> u));
    String csv = stream.collect(Collectors.joining(";"));
    Map<Integer, List<User>> group = stream.collect(Collectors.groupingBy(User::getAge));
    Map<Boolean, List<User>> part = stream.collect(Collectors.partitioningBy(u -> u.getAge() >= 18));
    
    // 3.5 匹配/查找
    boolean ok1 = stream.allMatch(x -> x > 0);
    boolean ok2 = stream.anyMatch(x -> x > 0);
    boolean ok3 = stream.noneMatch(x -> x > 0);
    Optional<Integer> first = stream.findFirst();
    Optional<Integer> any = stream.findAny();
    
    // 3.6 转数组/基本类型流
    int[] arr = stream.mapToInt(Integer::intValue).toArray();
  4. 基本类型特化流(IntStream / LongStream / DoubleStream)

    IntStream.rangeClosed(1, 100)
             .filter(n -> n % 7 == 0)
             .average()
             .ifPresent(System.out::println);
  5. 并行流(parallel)与并发注意

    long c = LongStream.rangeClosed(1, 1_0000_0000L)
                       .parallel()   // 拆分成多段 ForkJoin
                       .filter(n -> n % 103 == 0)
                       .count();

    注意:

  • 数据量小别用,线程切换反而慢

  • 避免有状态 lambda、外部共享变量

  • Collectors.toConcurrentMap 可进一步减少锁

6.自定义收集器(Collector 接口)

Collector<Integer, StringJoiner, String> myJoin =
    Collector.of(
        () -> new StringJoiner("、"),
        (j, s) -> j.add(s.toString()),
        (j1, j2) -> j1.merge(j2),
        StringJoiner::toString);

String r = Stream.of(1, 2, 3).collect(myJoin); // 1、2、3

7.与 Optional 联动

stream.map(User::getAddress)
      .filter(Objects::nonNull)
      .findFirst()
      .ifPresent(addr -> System.out.println("找到地址:" + addr));

8.实战 10 例(一行流)

// 1. 去重并按字典序取前 3
List<String> top3 = list.stream().distinct().sorted().limit(3).toList();

// 2. 把两个 List 合并后去重
List<String> union = Stream.concat(list1.stream(), list2.stream())
                           .distinct()
                           .toList();

// 3. 统计每个单词出现次数
Map<String, Long> freq = words.stream()
                              .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

// 4. 找到工资最高的员工
Optional<Employee> richest = emps.stream()
                                 .max(Comparator.comparingDouble(Employee::getSalary));

// 5. 按部门分组后,取每组工资最高的
Map<Department, Optional<Employee>> topInDept =
    emps.stream()
        .collect(Collectors.groupingBy(Employee::getDept,
                 Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary))));

// 6. 把 List<User> 转 Map<id, name>
Map<Long, String> id2Name = users.stream()
                                 .collect(Collectors.toMap(User::getId, User::getName));

// 7. 将字符串列表转整型,跳过非数字
List<Integer> nums = strList.stream()
                            .filter(s -> s.matches("\\d+"))
                            .map(Integer::valueOf)
                            .toList();

// 8. 把多行配置按“键=值”解析成 Map
Map<String, String> cfg = lines.stream()
                               .filter(l -> l.contains("="))
                               .map(l -> l.split("=", 2))
                               .collect(Collectors.toMap(a -> a[0], a -> a[1]));

// 9. 分页:跳过前 (page-1)*size 条,取 size 条
List<Row> page = rows.stream()
                     .skip((pageNo - 1) * pageSize)
                     .limit(pageSize)
                     .toList();

// 10. 递归列出目录下所有 .java 文件
try (Stream<Path> walk = Files.walk(Paths.get("src"))) {
    List<File> javaFiles = walk.filter(p -> p.toString().endsWith(".java"))
                               .map(Path::toFile)
                               .toList();
}

9.常见坑

  • stream 只能消费一次,再调会抛 IllegalStateException

  • peek 里别改外面状态,调试完就删。

  • parallel 不一定快,先度量。

  • toMap 遇到重复 key 默认抛异常,用 (v1,v2)->v2 指定冲突策略。

  • sorted 会缓存整个流,大数据慎用。

开发中经常遇到stream的问题,就整理了一份,希望对大家有帮助!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐