中 基础
Lambda与Stream#
一句话答案#
Lambda 是匿名函数简写(函数式接口的实例),Stream 提供声明式集合操作(filter/map/reduce),支持惰性求值和并行处理。
核心要点
Stream 的三段式结构:
数据源 → 中间操作(惰性,不立即执行)→ 终端操作(触发实际计算)plaintext常用中间操作(返回 Stream,惰性求值):
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() // 转数组java惰性求值(Lazy Evaluation):
// 中间操作不会立即执行,只有终端操作触发时才真正计算
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 后面的元素javaStream 的常见面试编码题:
// 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());javaparallelStream 的注意事项:
① 底层使用 ForkJoinPool.commonPool()(默认线程数 = CPU 核数 - 1)
② 不适合 IO 密集型操作(阻塞公共线程池,影响整个应用)
③ 有线程安全问题:不要在 forEach 中操作共享可变状态
④ 适合:大数据量的 CPU 密集型计算(如排序、统计)
⑤ 不适合:数据量小(线程切换开销 > 并行收益)、有序要求强的场景plaintext面试回答(2分钟版)
Lambda表达式是Java 8引入的匿名函数简写,它是函数式接口的实例。函数式接口是只有一个抽象方法的接口,Lambda通过类型推断自动匹配。Stream是基于Lambda的声明式集合操作API,采用三段式结构:数据源、中间操作和终端操作。中间操作如filter、map、flatMap都是惰性的,只是定义了操作管道但不立即执行;只有终端操作如collect、reduce、forEach触发时才真正执行整个管道。这种惰性求值的好处是短路操作可以提前终止,比如findFirst找到第一个就停。flatMap用于一对多映射后扁平化,是面试编码的高频操作。parallelStream底层使用ForkJoinPool的公共线程池,线程数默认是CPU核数减一,不适合IO密集型操作因为会阻塞公共线程池影响整个应用,适合大数据量的CPU密集型计算。Lambda捕获的外部变量必须是effectively final的,不能修改。
追问与易错
追问方向:
- “Stream 是惰性求值的吗?”→ 是的,中间操作(filter/map/flatMap)只定义管道不立即执行,只有终端操作(collect/reduce/forEach)触发时才真正计算,支持短路优化
- “parallelStream 有什么坑?”→ 底层共享 ForkJoinPool.commonPool(),IO 密集操作会阻塞公共线程池影响全局;forEach 中操作共享可变状态有线程安全问题;可能破坏元素有序性
- “Lambda 捕获变量有什么限制?”→ 捕获的外部局部变量必须是 effectively final(事实上不可变),不能在 Lambda 内修改,因为 Lambda 捕获的是变量的副本而非引用
易错点:
- ❌ “parallelStream 一定比串行快”——小数据量或简单操作反而慢(线程开销)
- ❌ Stream 操作后原始集合不变——Stream 不修改源数据