Java深入解析篇九之JavaStream API详解

Java Stream API 详解


目录

  1. 概述与历史
  2. 流与集合的区别
  3. 创建流的七种方式
  4. 中间操作详解
  5. 终端操作详解
  6. Collector与自定义收集器
  7. 原始类型特化流
  8. 并行流原理与陷阱
  9. Stream内部执行机制
  10. 实际应用案例
  11. 最佳实践与陷阱
  12. 版本演进

1. 概述与历史

1.1 什么是Stream

Stream(流)是 JDK 8(JSR 335) 引入的数据处理抽象,位于 java.util.stream 包。它表示从数据源产生的元素序列,并支持对其进行函数式、聚合式操作。

三个关键定位:

  • 不是集合:流不存储数据,只描述对数据的计算;
  • 不是 IO 流 :与 InputStream/OutputStream 无关;
  • 惰性管道:中间操作只是登记,终端操作才触发实际计算。
java 复制代码
import java.util.List;
import java.util.stream.Collectors;

public class StreamHello {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Anna");

        // 命令式写法(对照)
        // List<String> r = new ArrayList<>();
        // for (String n : names) if (n.startsWith("A")) r.add(n.toUpperCase());

        // 声明式:过滤以A开头 → 转大写 → 收集
        List<String> result = names.stream()
                .filter(name -> name.startsWith("A"))
                .map(String::toUpperCase)
                .collect(Collectors.toList());

        System.out.println(result); // [ALICE, ANNA]
    }
}

1.2 设计动机

JDK 8 之前,集合处理要手写循环:易错、啰嗦、难以并行。Stream 带来:

能力 收益
声明式 代码表达"做什么",可读性高
链式/组合 多个操作自由拼装
惰性+短路 按需计算,减少无用功
透明并行 parallel() 一行并行,无需手写线程

2. 流与集合的区别

维度 集合(Collection) 流(Stream)
数据存储 全部在内存中 不存储,按需计算
修改 可增删改元素 不可修改,操作产生新流
遍历次数 可多次 只能消费一次
求值时机 立即(全部就绪) 惰性(终端操作触发)
元素数量 必须有限 可无限(配合 limit)
并行 需手写同步 parallel() 内建
java 复制代码
import java.util.List;
import java.util.stream.Stream;

public class StreamOnceDemo {
    public static void main(String[] args) {
        Stream<Integer> stream = List.of(1, 2, 3).stream();
        stream.forEach(System.out::println);      // 第一次消费:正常
        // stream.forEach(System.out::println);   // 第二次:抛 IllegalStateException
        // "stream has already been operated upon or closed"

        // 惰性证明:中间操作不执行,直到终端操作
        Stream<Integer> lazy = List.of(1, 2, 3).stream()
                .peek(x -> System.out.println("处理: " + x)); // 此刻没有任何输出
        System.out.println("--- 现在才真正执行 ---");
        lazy.count(); // 触发执行,输出三行"处理: x"
    }
}

3. 创建流的七种方式

java 复制代码
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.*;

public class StreamCreationDemo {
    public static void main(String[] args) throws IOException {
        // ① 从集合
        Stream<String> s1 = List.of("a", "b").stream();
        Stream<String> s1p = List.of("a", "b").parallelStream(); // 并行流

        // ② 从数组 / 可变参数
        Stream<String> s2 = Stream.of("x", "y", "z");
        int[] arr = {1, 2, 3, 4};
        IntStream s2b = Arrays.stream(arr);            // 原始类型数组→IntStream
        IntStream s2c = Arrays.stream(arr, 1, 3);      // 截取片段[1,3) → 2,3

        // ③ 静态工厂
        Stream<Object> empty = Stream.empty();                          // 空流
        Stream<Double> randoms = Stream.generate(Math::random).limit(3); // 无限流+limit
        Stream<Integer> powers = Stream.iterate(1, n -> n * 2).limit(5); // 1,2,4,8,16
        Stream<Integer> finite = Stream.iterate(1, n -> n < 100, n -> n * 2); // JDK9:1,2,4,...,64
        Stream<String> joined = Stream.concat(Stream.of("a"), Stream.of("b")); // a,b
        Stream<String> maybe = Stream.ofNullable(getNullable());        // JDK9:null→空流

        // ④ 数值流
        IntStream range = IntStream.range(0, 5);        // 0..4
        IntStream closed = IntStream.rangeClosed(1, 5); // 1..5
        LongStream longs = LongStream.of(1L, 2L, 3L);
        DoubleStream doubles = DoubleStream.generate(Math::random).limit(2);

        // ⑤ IO / 文件
        try (Stream<String> lines = Files.lines(Path.of("data.txt"))) { // 逐行懒读
            lines.filter(l -> !l.isBlank()).forEach(System.out::println);
        }
        try (Stream<Path> walk = Files.walk(Path.of("src"), 3)) { // 递归3层
            walk.filter(p -> p.toString().endsWith(".java")).count();
        }

        // ⑥ 正则拆分
        Stream<String> words = Pattern.compile(",\\s*").splitAsStream("a, b, c"); // a,b,c

        // ⑦ 其他
        Stream<Character> chars = "hello".chars().mapToObj(c -> (char) c); // JDK9的chars()返回IntStream
        Stream<Integer> fromOpt = Optional.of(42).stream();                 // JDK9:0或1元素
        Stream<String> built = Stream.<String>builder().add("m").add("n").build(); // Builder

        // 演示输出
        System.out.println(powers.toList());  // [1, 2, 4, 8, 16](JDK16 toList)
        System.out.println(finite.toList());  // [1, 2, 4, 8, 16, 32, 64]
        System.out.println(joined.toList());  // [a, b]
    }

    private static String getNullable() {
        return Math.random() > 0.5 ? "有值" : null;
    }
}

4. 中间操作详解

4.1 无状态操作:filter / map / flatMap / peek

java 复制代码
import java.util.List;
import java.util.stream.Stream;

public class StatelessOpsDemo {
    record Order(String customer, List<String> items, double amount) {}

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("张三", List.of("键盘", "鼠标"), 299.0),
                new Order("李四", List.of("显示器"), 1999.0),
                new Order("王五", List.of("U盘", "数据线", "转接头"), 128.5));

        // filter:保留满足谓词的元素
        List<String> bigOrders = orders.stream()
                .filter(o -> o.amount() > 200)
                .map(Order::customer)
                .toList(); // JDK16
        System.out.println(bigOrders); // [李四]

        // map:一对一转换(类型可变)
        List<Integer> lengths = Stream.of("java", "stream", "api")
                .map(String::length)
                .toList();
        System.out.println(lengths); // [4, 6, 3]

        // flatMap:一对多拍平。每个订单的商品拍平成一个大流
        List<String> allItems = orders.stream()
                .flatMap(o -> o.items().stream()) // Stream<Stream<String>> → Stream<String>
                .toList();
        System.out.println(allItems); // [键盘, 鼠标, 显示器, U盘, 数据线, 转接头]

        // peek:调试观察,不改变元素(生产慎用,见最佳实践)
        long count = Stream.of(1, 2, 3, 4)
                .peek(x -> System.out.println("原始: " + x))
                .filter(x -> x % 2 == 0)
                .peek(x -> System.out.println("过滤后: " + x))
                .count();
        System.out.println("数量: " + count); // 2
    }
}

4.2 有状态操作:distinct / sorted / limit / skip

java 复制代码
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;

public class StatefulOpsDemo {
    public static void main(String[] args) {
        // distinct:依赖hashCode/equals去重
        List<Integer> unique = Stream.of(3, 1, 4, 1, 5, 9, 2, 6, 5)
                .distinct()
                .toList();
        System.out.println(unique); // [3, 1, 4, 5, 9, 2, 6]

        // sorted:自然序 / 定制序
        List<String> sorted = Stream.of("banana", "apple", "cherry")
                .sorted()                          // 自然序
                .toList();
        List<String> byLength = Stream.of("banana", "fig", "apple")
                .sorted(Comparator.comparingInt(String::length)) // 按长度
                .toList();
        System.out.println(sorted);    // [apple, banana, cherry]
        System.out.println(byLength);  // [fig, apple, banana]

        // limit:取前N个(短路:处理到N个即停)
        List<Integer> first3 = Stream.iterate(1, n -> n + 1).limit(3).toList(); // [1,2,3]

        // skip:跳过前N个(配合limit实现分页)
        List<Integer> page2 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
                .skip(3).limit(3) // 第2页,每页3条
                .toList();
        System.out.println(page2); // [4, 5, 6]
    }
}

4.3 JDK9:takeWhile 与 dropWhile

java 复制代码
import java.util.List;
import java.util.stream.Stream;

public class TakeDropDemo {
    public static void main(String[] args) {
        // takeWhile:从头开始保留满足谓词的元素,一旦不满足立即停止
        List<Integer> taken = Stream.of(2, 4, 6, 7, 8, 10)
                .takeWhile(n -> n % 2 == 0)
                .toList();
        System.out.println(taken); // [2, 4, 6](到7就停,后面的8、10不要)

        // dropWhile:从头丢弃满足谓词的元素,一旦不满足,保留余下全部
        List<Integer> dropped = Stream.of(2, 4, 6, 7, 8, 10)
                .dropWhile(n -> n % 2 == 0)
                .toList();
        System.out.println(dropped); // [7, 8, 10]

        // 对比filter:filter是全局过滤,不看位置
        List<Integer> filtered = Stream.of(2, 4, 6, 7, 8, 10)
                .filter(n -> n % 2 == 0)
                .toList();
        System.out.println(filtered); // [2, 4, 6, 8, 10]
    }
}

5. 终端操作详解

5.1 归约:reduce

java 复制代码
import java.util.List;
import java.util.Optional;

public class ReduceDemo {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5);

        // 形式1:无初始值 → Optional(流可能为空)
        Optional<Integer> sum = nums.stream().reduce(Integer::sum);
        System.out.println(sum.orElse(0)); // 15

        // 形式2:有初始值(identity)
        int product = nums.stream().reduce(1, (a, b) -> a * b); // 1*1*2*3*4*5
        System.out.println(product); // 120

        // 形式3:并行三参版(combiner合并各线程分片结果)
        // 此处把每个字符串长度求和:accumulator算单片,combiner合并片
        int totalLen = List.of("ab", "cde", "f").stream()
                .reduce(0,
                        (subtotal, s) -> subtotal + s.length(),   // 累加器
                        Integer::sum);                            // 合并器
        System.out.println(totalLen); // 6

        // 归约求最大
        Optional<Integer> max = nums.stream().reduce(Integer::max);
        System.out.println(max.get()); // 5
    }
}

5.2 匹配与查找(短路)

java 复制代码
import java.util.List;
import java.util.Optional;

public class MatchFindDemo {
    public static void main(String[] args) {
        List<Integer> nums = List.of(2, 4, 6, 8, 10);

        // anyMatch:存在一个满足即true(短路)
        boolean hasBig = nums.stream().anyMatch(n -> n > 9);   // true
        // allMatch:全部满足才true(空流返回true)
        boolean allEven = nums.stream().allMatch(n -> n % 2 == 0); // true
        // noneMatch:全不满足才true
        boolean noOdd = nums.stream().noneMatch(n -> n % 2 == 1);  // true
        System.out.println(hasBig + " " + allEven + " " + noOdd);

        // findFirst:保序场景的第一个
        Optional<Integer> first = nums.stream().filter(n -> n > 5).findFirst();
        System.out.println(first.get()); // 6

        // findAny:任意一个(并行流性能更好,结果不确定)
        Optional<Integer> any = nums.parallelStream().filter(n -> n > 5).findAny();
        System.out.println("任意: " + any.get()); // 6/8/10之一

        // 与Optional链式组合
        String msg = nums.stream()
                .filter(n -> n > 100)
                .findFirst()
                .map(String::valueOf)
                .orElse("没有超过100的数");
        System.out.println(msg);
    }
}

5.3 collect 与常用收集器

java 复制代码
import java.util.*;
import java.util.stream.Collectors;

public class CollectDemo {
    record Employee(String name, String dept, int salary) {}

    public static void main(String[] args) {
        List<Employee> employees = List.of(
                new Employee("张三", "研发", 15000),
                new Employee("李四", "研发", 20000),
                new Employee("王五", "测试", 12000),
                new Employee("赵六", "测试", 13000),
                new Employee("钱七", "运维", 11000));

        // 1) 归约为容器
        List<String> names = employees.stream()
                .map(Employee::name)
                .collect(Collectors.toList());
        Set<String> depts = employees.stream()
                .map(Employee::dept)
                .collect(Collectors.toSet()); // 自动去重
        System.out.println(names); // [张三, 李四, 王五, 赵六, 钱七]
        System.out.println(depts); // [研发, 测试, 运维]

        // 2) toMap:key重复必须给合并函数,否则IllegalStateException
        Map<String, Integer> salaryMap = employees.stream()
                .collect(Collectors.toMap(Employee::name, Employee::salary));
        Map<String, Integer> maxByDept0 = employees.stream()
                .collect(Collectors.toMap(
                        Employee::dept, Employee::salary, Integer::max)); // 冲突取大
        System.out.println(maxByDept0); // {研发=20000, 测试=13000, 运维=11000}

        // 3) groupingBy:按部门分组
        Map<String, List<Employee>> byDept = employees.stream()
                .collect(Collectors.groupingBy(Employee::dept));
        // 分组+下游:每组求平均薪资
        Map<String, Double> avgByDept = employees.stream()
                .collect(Collectors.groupingBy(Employee::dept,
                        Collectors.averagingDouble(Employee::salary)));
        System.out.println(avgByDept); // {研发=17500.0, 测试=12500.0, 运维=11000.0}

        // 多级分组:部门 → 薪资高低(>=15000为高)
        Map<String, Map<Boolean, List<Employee>>> multi = employees.stream()
                .collect(Collectors.groupingBy(Employee::dept,
                        Collectors.partitioningBy(e -> e.salary() >= 15000)));

        // 4) joining:字符串拼接
        String csv = employees.stream()
                .map(Employee::name)
                .collect(Collectors.joining(", ", "[", "]"));
        System.out.println(csv); // [张三, 李四, 王五, 赵六, 钱七]

        // 5) 统计:summarizingInt一次给出count/sum/min/avg/max
        IntSummaryStatistics stats = employees.stream()
                .collect(Collectors.summarizingInt(Employee::salary));
        System.out.println(stats); // IntSummaryStatistics{count=5, sum=71000, ...}

        // 6) minBy / maxBy
        Optional<Employee> top = employees.stream()
                .collect(Collectors.maxBy(Comparator.comparingInt(Employee::salary)));
        top.ifPresent(e -> System.out.println("最高薪: " + e.name())); // 李四

        // 7) teeing(JDK12):一份数据,两路收集,合并结果
        // 同时求平均值和最大值
        record AvgMax(double avg, int max) {}
        AvgMax am = employees.stream().collect(Collectors.teeing(
                Collectors.averagingDouble(Employee::salary),
                Collectors.maxBy(Comparator.comparingInt(Employee::salary)),
                (avg, maxOpt) -> new AvgMax(avg, maxOpt.map(Employee::salary).orElse(0))));
        System.out.println(am); // AvgMax[avg=14200.0, max=20000]
    }
}

6. Collector与自定义收集器

6.1 Collector五组件

复制代码
Collector<T, A, R>
├── supplier()        :() -> A              创建累加容器
├── accumulator()     :(A, T) -> void       把元素放入容器
├── combiner()        :(A, A) -> A          并行时合并两个容器
├── finisher()        :A -> R               最终转换
└── characteristics() :IDENTITY_FINISH(A即R)/ CONCURRENT / UNORDERED

6.2 自定义Collector:收集为不可变字符串

java 复制代码
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

/**
 * 自定义收集器:把字符串用" | "连接并包上边框
 * T=String(输入),A=StringJoiner(累加容器),R=String(结果)
 */
public class BannerCollector implements Collector<String, StringJoiner, String> {

    @Override
    public Supplier<StringJoiner> supplier() {
        return () -> new StringJoiner(" | "); // 创建累加器
    }

    @Override
    public BiConsumer<StringJoiner, String> accumulator() {
        return StringJoiner::add; // 逐个加入
    }

    @Override
    public BinaryOperator<StringJoiner> combiner() {
        return StringJoiner::merge; // 并行分片合并
    }

    @Override
    public Function<StringJoiner, String> finisher() {
        return sj -> "┌─── " + sj + " ───┐"; // 收尾包装
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED));
    }

    public static void main(String[] args) {
        String banner = Stream.of("Java", "Stream", "Collector")
                .collect(new BannerCollector());
        System.out.println(banner); // ┌─── Java | Stream | Collector ───┐

        // 更简洁的方式:Collector.of 静态工厂
        Collector<String, StringJoiner, String> simple = Collector.of(
                () -> new StringJoiner(", "),      // supplier
                StringJoiner::add,                 // accumulator
                StringJoiner::merge,               // combiner
                StringJoiner::toString);           // finisher
        System.out.println(Stream.of("a", "b", "c").collect(simple)); // a, b, c
    }
}

6.3 自定义收集器典型场景:分组后取Top N

java 复制代码
import java.util.*;
import java.util.stream.Collectors;

public class TopNDemo {
    record Sale(String region, int amount) {}

    public static void main(String[] args) {
        List<Sale> sales = List.of(
                new Sale("华东", 100), new Sale("华东", 300), new Sale("华东", 200),
                new Sale("华北", 150), new Sale("华北", 400));

        // 每个区域销售额前2名:groupingBy + collectingAndThen 排序截断
        Map<String, List<Sale>> top2 = sales.stream()
                .collect(Collectors.groupingBy(Sale::region,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                list -> list.stream()
                                        .sorted(Comparator.comparingInt(Sale::amount).reversed())
                                        .limit(2)
                                        .toList())));
        System.out.println(top2);
        // {华东=[Sale[华东,300], Sale[华东,200]], 华北=[Sale[华北,400], Sale[华北,150]]}
    }
}

7. 原始类型特化流

IntStream/LongStream/DoubleStream 避免了装箱拆箱,并提供数值专用聚合:

java 复制代码
import java.util.OptionalDouble;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class PrimitiveStreamDemo {
    public static void main(String[] args) {
        // sum/average 是数值流专属(Stream<Integer>没有sum)
        int sum = IntStream.rangeClosed(1, 100).sum();           // 5050
        OptionalDouble avg = IntStream.of(80, 90, 100).average(); // 90.0
        System.out.println(sum + ", " + avg.getAsDouble());

        // 对象流 → 数值流:mapToInt / mapToLong / mapToDouble
        int totalChars = Stream.of("java", "stream")
                .mapToInt(String::length)   // Stream<String> → IntStream
                .sum();                     // 4 + 6
        System.out.println(totalChars);

        // 数值流 → 对象流:mapToObj / boxed
        Stream<String> strs = IntStream.rangeClosed(1, 3)
                .mapToObj(i -> "编号" + i);  // 编号1, 编号2, 编号3
        Stream<Integer> boxed = IntStream.of(1, 2).boxed();

        // summaryStatistics 一次拿到全部统计
        System.out.println(IntStream.of(3, 1, 4, 1, 5).summaryStatistics());
    }
}

8. 并行流原理与陷阱

8.1 执行模型

复制代码
collection.parallelStream()
        │
        ▼
ForkJoinPool.commonPool()(默认并行度 = CPU核数 - 1)
        │
        ▼
Spliterator.trySplit() 递归对半拆分
   [0..1000000] → [0..500000][500000..1000000] → ...
        │
        ▼
每个分片独立执行管道 → combiner 合并结果(collect/reduce)
        │
        ▼
work-stealing:空闲线程窃取其他线程的任务,负载均衡

8.2 并行收益实测模型

java 复制代码
import java.util.List;
import java.util.stream.LongStream;

public class ParallelDemo {
    public static void main(String[] args) {
        // 计算量大的场景:并行有明显收益
        long n = 20_000_000L;

        long t1 = System.currentTimeMillis();
        long seqSum = LongStream.rangeClosed(1, n).sum();
        long seqTime = System.currentTimeMillis() - t1;

        long t2 = System.currentTimeMillis();
        long parSum = LongStream.rangeClosed(1, n).parallel().sum();
        long parTime = System.currentTimeMillis() - t2;

        System.out.println("顺序: " + seqSum + " 耗时" + seqTime + "ms");
        System.out.println("并行: " + parSum + " 耗时" + parTime + "ms");
    }
}

8.3 五大陷阱与规避

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ParallelPitfalls {
    public static void main(String[] args) throws Exception {
        // 陷阱1:共享可变状态 → 结果丢失/错乱
        // ❌ 错误写法:多线程并发add到ArrayList(非线程安全)
        List<Integer> unsafe = new ArrayList<>();
        IntStream.range(0, 1000).parallel().forEach(unsafe::add); // 可能少元素甚至异常!

        // ✅ 正确写法:用collect,天然线程安全
        List<Integer> safe = IntStream.range(0, 1000).parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println("安全收集: " + safe.size()); // 稳定1000

        // 陷阱2:forEach顺序不保证
        IntStream.range(0, 5).parallel().forEachOrdered(System.out::println); // 保序0..4

        // 陷阱3:parallel()/sequential() 是全局开关,最后一次调用决定整条管道
        // 并非"分段生效",混用时以最后一个为准
        IntStream.range(0, 10).parallel().filter(x -> true).sequential().forEach(x -> {});
        // ↑ 整条管道最终是顺序的

        // 陷阱4:commonPool被阻塞操作污染
        // 自定义并行度/隔离:自建ForkJoinPool
        ForkJoinPool customPool = new ForkJoinPool(4);
        int customResult = customPool.submit(() ->
                IntStream.range(0, 100).parallel().sum()).get();
        System.out.println("自定义池: " + customResult);
        customPool.shutdown();

        // 陷阱5:有状态操作(sorted/distinct)在并行下需要屏障同步
        // 数据量小时并行反而更慢;小集合不要盲目parallel()
    }
}

适用性判断表

条件 是否适合并行
数据量 > 万级,纯CPU计算
数据源为 ArrayList / 数组
数据源为 LinkedList / IO ❌ 拆分代价高
有共享可变状态 ❌ 先改无副作用
单元素处理含阻塞/锁 ❌ 污染公共池
数据量小(<1000) ❌ 调度开销大于收益

9. Stream内部执行机制

复制代码
源码视角(java.util.stream.AbstractPipeline)
├── Head:源阶段,持有Spliterator数据源
├── 中间操作:每次调用生成新的Stage节点,挂成双向链表
│   └── 记录操作标志(无状态/有状态/短路)
├── 终端操作:触发 wrapAndCopyInto / evaluate
│   ├── 把所有Stage包装成一条Sink链(责任链,垂直融合)
│   ├── 从最后一个Sink开始,逐元素拉动源数据
│   └── 短路操作:Sink.cancel()标志提前终止
└── Spliterator:数据源迭代器
    ├── tryAdvance():消费一个元素
    ├── trySplit():拆分(并行基础)
    └── 特征位:ORDERED/SIZED/SORTED → 优化(如SIZED+SORTED跳过sorted)

垂直融合的意义filter→map→limit 融合后,元素逐个穿过三道工序,limit(3) 满足即停,后面的元素根本不会进入 filter------这就是惰性+短路的高效所在。


10. 实际应用案例

10.1 订单统计报表

java 复制代码
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

public class OrderReportDemo {
    record Order(long id, String customer, String product,
                 int qty, double price, LocalDate date) {
        double total() { return qty * price; }
    }

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order(1, "张三", "键盘", 2, 200, LocalDate.of(2024, 5, 1)),
                new Order(2, "张三", "鼠标", 1, 100, LocalDate.of(2024, 5, 2)),
                new Order(3, "李四", "键盘", 3, 200, LocalDate.of(2024, 5, 2)),
                new Order(4, "王五", "显示器", 1, 1999, LocalDate.of(2024, 5, 3)));

        // 需求1:按客户统计总金额,降序排列
        Map<String, Double> byCustomer = orders.stream()
                .collect(Collectors.groupingBy(Order::customer,
                        Collectors.summingDouble(Order::total)))
                .entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (a, b) -> a, LinkedHashMap::new)); // 保持排序
        System.out.println("客户消费: " + byCustomer);
        // 客户消费: {王五=1999.0, 张三=500.0, 李四=600.0}...(按金额降序)

        // 需求2:最畅销商品(按数量)
        String hottest = orders.stream()
                .collect(Collectors.groupingBy(Order::product,
                        Collectors.summingInt(Order::qty)))
                .entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElse("无");
        System.out.println("最畅销: " + hottest); // 键盘(5件)

        // 需求3:报表行拼接
        String report = orders.stream()
                .map(o -> "#%d %s 购买%s×%d = %.1f元".formatted(
                        o.id(), o.customer(), o.product(), o.qty(), o.total()))
                .collect(Collectors.joining("\n", "=== 订单报表 ===\n", "\n=== 结束 ==="));
        System.out.println(report);
    }
}

11. 最佳实践与陷阱

11.1 最佳实践

java 复制代码
// ✅ 1. 数值聚合用IntStream,避免装箱
int total = list.stream().mapToInt(Item::qty).sum();

// ✅ 2. 复杂Lambda抽成方法引用,提升可读性
orders.stream().filter(Order::isPaid)      // 好比 o -> o.getStatus() == PAID && ...
        .map(Order::toInvoice)
        .collect(toList());

// ✅ 3. toMap永远提供合并函数,防key冲突异常
map = stream.collect(toMap(k, v, (v1, v2) -> v1));

// ✅ 4. 并行前先确认:数据量大、无共享状态、无阻塞

// ✅ 5. 用JDK16的toList()替代collect(Collectors.toList())(注意结果不可修改)
List<String> list = stream.toList();

// ✅ 6. 需要保序收集用toCollection(LinkedHashMap::new)等

11.2 常见陷阱

陷阱 说明 规避
流复用 消费后再操作抛 IllegalStateException 重新获取流
peek不可靠 可能被优化跳过,不保证执行 只用于调试,勿做业务
flatMap内存 拍平会产生大量中间对象 大数据考虑分块
sorted并行 需收齐全部元素,屏障开销 数据源有序时避免重复排序
toList不可变 Stream.toList() 返回不可修改List 需修改用 collect(toCollection(ArrayList::new))
副作用函数 map/filter里改外部变量,并行必错 保持纯函数
count优化 某些源 count() 直接读size,中间操作可能被短路跳过 勿依赖peek计数

12. 版本演进

版本 年份 Stream相关变化
JDK 8 2014 完整引入 :Stream/IntStream/LongStream/DoubleStream、Collectors、Collector、Spliterator;集合新增 stream()/parallelStream()/forEach 默认方法
JDK 9 2017 takeWhiledropWhileiterate(seed, hasNext, next)(有限)、Stream.ofNullableOptional.stream()Collectors.flatMappingCollectors.filtering
JDK 10 2018 Collectors.toUnmodifiableList/Set/Map
JDK 11 2018 String.lines()(字符串按行转流)、Predicate.not() 便于filter取反
JDK 12 2019 Collectors.teeing(双路收集合并)
JDK 16 2021 Stream.toList() 正式(返回不可修改List)
JDK 17 2021 配合record/模式匹配,Stream处理结构化数据更简洁

总结:Stream API 自 JDK 8 奠定骨架后持续小幅增强,方向是更便捷(toList)、更表达力(teeing)、更函数化(takeWhile)。掌握"创建→中间→终端"三段式心智模型,配合 Collectors 与并行流注意事项,即可应对绝大多数数据处理场景。

相关推荐
一只QAQ21 分钟前
c++项目
java·c++·算法
小蒜学长28 分钟前
基于SpringBoot + Vue的智能健身房管理系统的设计与实现(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端
DolitD29 分钟前
云流技术深度剖析:单服务器下如何实现3D应用的多实例并发?
java·服务器·前端·3d·云原生·云计算
jun_bai34 分钟前
Ecplise中发布项目到配置的tomcat中
java·服务器
liangbo734 分钟前
JVM规范第 6 章:Java 虚拟机指令集
java·jvm
南城以南溫暖如初14739 分钟前
AI 智能商城 APP 定制开发的核心需求
java·人工智能·spring boot·mysql·mybatis·需求分析
摇滚侠40 分钟前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 21
java·spring boot·笔记
Sam_Deep_Thinking1 小时前
聊聊开闭原则,以及它在Spring里的样子
java·后端·spring·程序员·开闭原则
SimonKing1 小时前
开源神器 Navop:数据库+SSH+终端+AI,一个应用全搞定
java·后端·程序员