Skip to content
基础

一句话答案

Lambda 是匿名函数简写(函数式接口的实例),Stream 提供声明式集合操作(filter/map/reduce),支持惰性求值和并行处理。

核心要点

Stream 的三段式结构:

数据源 → 中间操作(惰性,不立即执行)→ 终端操作(触发实际计算)

常用中间操作(返回 Stream,惰性求值):

java
stream.filter(x -> x > 10)           // 过滤
      .map(x -> x * 2)               // 映射转换
      .flatMap(list -> list.stream()) // 扁平化(一对多映射)
      .distinct()                     // 去重
      .sorted()                       // 排序
      .peek(System.out::println)      // 调试窥探(不影响数据)
      .limit(10)                      // 取前 N 个
      .skip(5)                        // 跳过前 N 个

常用终端操作(触发计算,返回结果):

java
stream.forEach(System.out::println)           // 遍历
stream.collect(Collectors.toList())           // 收集为 List
stream.collect(Collectors.groupingBy(x::getType))  // 分组
stream.reduce(0, Integer::sum)                // 归约
stream.count()                                // 计数
stream.anyMatch(x -> x > 10)                  // 是否存在匹配
stream.findFirst()                            // 取第一个(返回 Optional)
stream.toArray()                              // 转数组

惰性求值(Lazy Evaluation):

java
// 中间操作不会立即执行,只有终端操作触发时才真正计算
List<String> result = names.stream()
    .filter(name -> {
        System.out.println("filter: " + name);  // 如果没有终端操作,这行永远不会打印
        return name.length() > 3;
    })
    .map(String::toUpperCase)
    .collect(Collectors.toList());  // 终端操作触发整个管道执行

// 优势:短路操作(如 findFirst)可以提前终止,不会遍历所有元素
Optional<String> first = names.stream()
    .filter(name -> name.startsWith("A"))
    .findFirst();  // 找到第一个就停止,不会继续 filter 后面的元素

Stream 的常见面试编码题:

java
// 1. 按部门分组并统计平均薪资
Map<String, Double> avgSalary = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.averagingDouble(Employee::getSalary)
    ));

// 2. 扁平化嵌套集合并去重
List<String> allTags = articles.stream()
    .flatMap(article -> article.getTags().stream())
    .distinct()
    .sorted()
    .collect(Collectors.toList());

// 3. 找出薪资 Top3 的员工名
List<String> top3 = employees.stream()
    .sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
    .limit(3)
    .map(Employee::getName)
    .collect(Collectors.toList());

parallelStream 的注意事项:

① 底层使用 ForkJoinPool.commonPool()(默认线程数 = CPU 核数 - 1)
② 不适合 IO 密集型操作(阻塞公共线程池,影响整个应用)
③ 有线程安全问题:不要在 forEach 中操作共享可变状态
④ 适合:大数据量的 CPU 密集型计算(如排序、统计)
⑤ 不适合:数据量小(线程切换开销 > 并行收益)、有序要求强的场景

文档完

复习建议:

  1. ConcurrentHashMap JDK7 vs JDK8 必须熟背,这是面试高频对比题
  2. HashMap put 流程要能完整口述(hash计算 → 定桶 → 冲突处理 → 树化 → 扩容)
  3. epoll 相比 select/poll 的优势是网络/IO 相关题的通用基础,Redis 快、Netty 高性能都源于此
  4. Record / Sealed Classes / Virtual Threads 是 Java 17-21 面试新热点,尤其在要求 JDK 17+ 的岗位中高频出现
  5. 泛型 PECS 原则(Q27)配合 Collections 源码理解效果最好;反射+动态代理(Q28)是理解 Spring AOP 的前置知识
  6. Stream 编码题(Q29)建议手写 groupingBy + flatMap + sorted 组合链,面试现场写的概率很高
追问与易错

追问方向:

  • Stream ��惰性求值的吗?(中间操作惰性,终端操作触发执行)
  • parallelStream 有什么坑?(共享 ForkJoinPool/线程安全/有序性)
  • Lambda 捕获变量有什么限制?(effectively final)

易错点:

  • ❌ "parallelStream 一定比串行快"——小数据量或简单操作反而慢(线程开销)
  • ❌ Stream 操作后原始集合不变——Stream 不修改源数据

💡 记忆锚点

Stream是流水线:中间操作(filter/map/flatMap)只画图纸不开工(惰性),终端操作(collect/reduce)按下启动键才真正跑。parallelStream共用公共线程池,IO密集型别用它堵车。