
1. Lambda表达式从匿名类到函数式编程的进化Java SE 8最革命性的特性莫过于Lambda表达式了。记得我第一次看到这种写法时整个人都是懵的——这跟传统的Java代码风格差别太大了。但用顺手之后才发现这简直是集合操作的瑞士军刀。先来看个典型场景我们要给按钮添加点击事件。在Java 8之前得这么写button.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { System.out.println(按钮被点击了); } });而有了Lambda之后代码可以简化为button.addActionListener(e - System.out.println(按钮被点击了));这种转变不仅仅是语法糖更代表着编程思维的转变。Lambda表达式的核心结构是(参数列表) - { 方法体 }当方法体只有一行时花括号和return都可以省略。我在实际项目中踩过的坑是如果方法体有多行语句必须用花括号包裹否则编译会报错。2. Stream API让集合操作行云流水如果说Lambda是子弹那么Stream就是枪。两者配合使用才能真正发挥Java 8的威力。我经常用Stream来处理各种集合数据比如要从员工列表中找出所有薪资超过1万的开发工程师ListEmployee highPaidDevs employees.stream() .filter(e - e.getSalary() 10000) .filter(e - Developer.equals(e.getPosition())) .collect(Collectors.toList());Stream的操作分为中间操作和终端操作两类。常见的中间操作包括filter()过滤元素map()元素转换sorted()排序distinct()去重终端操作则会触发实际计算比如collect()收集结果forEach()遍历元素count()计数reduce()归约运算有个容易掉坑的地方Stream是一次性的执行完终端操作后就不能再用了。我有次尝试重复使用同一个Stream对象结果抛出了IllegalStateException。3. 方法引用让代码更简洁的语法糖方法引用是Lambda的孪生兄弟本质上也是函数式接口的实例。当Lambda只是调用已有方法时可以用方法引用进一步简化代码。比如// Lambda写法 list.forEach(s - System.out.println(s)); // 方法引用写法 list.forEach(System.out::println);方法引用有四种形式类::静态方法Math::max对象::实例方法System.out::println类::实例方法String::length类::newEmployee::new我在代码审查时发现很多同事会混淆第三种和第四种形式。记住带new的是构造器引用不带new的是实例方法引用。4. 实战陷阱这些坑我都踩过4.1 Lambda中的变量捕获Lambda可以捕获外围的final或等效final的局部变量。但要注意在Lambda内部修改这些变量会导致编译错误int count 0; list.forEach(e - { count; // 编译错误 });解决方案是使用原子类如AtomicInteger或者重构代码避免修改外部变量。4.2 并行流的线程安全问题并行流(parallelStream)虽然能利用多核优势但容易引发线程安全问题ListInteger unsafeList new ArrayList(); IntStream.range(0, 10000).parallel() .forEach(unsafeList::add); // 可能抛出ArrayIndexOutOfBoundsException正确做法是使用线程安全的收集器ListInteger safeList IntStream.range(0, 10000).parallel() .boxed() .collect(Collectors.toList());4.3 空指针异常链Stream操作链中任何一个环节出现null都会导致NPEListString names Arrays.asList(Alice, null, Bob); names.stream() .map(String::toUpperCase) // 在null元素处抛出NPE .forEach(System.out::println);解决方法是用Optional包装或者提前过滤null值names.stream() .filter(Objects::nonNull) .map(String::toUpperCase) .forEach(System.out::println);5. 性能优化小贴士经过大量实测我总结出一些Stream性能优化的经验对于小数据集1000元素普通循环往往比Stream更快避免在Stream链式调用中重复计算// 不好的写法 list.stream().filter(e - e.length() 3).map(e - e.length())... // 好的写法 list.stream().map(e - e.length()).filter(len - len 3)...并行流适合CPU密集型操作IO密集型任务反而可能更慢预分配集合大小能提升collect性能list.stream().collect(Collectors.toCollection(() - new ArrayList(list.size())));6. 经典面试题解析来看几道考察Lambda和Stream掌握程度的典型题目题目1下面的代码输出是什么ListString list Arrays.asList(a, b, c); String result list.stream() .reduce(, (partial, elem) - partial elem.toUpperCase()); System.out.println(result);答案输出ABC。reduce的第一个参数是初始值Lambda表达式将每个元素转为大写并拼接。题目2如何用Stream统计文本中每个单词的出现频率解决方案MapString, Long wordCount Files.lines(Paths.get(text.txt)) .flatMap(line - Arrays.stream(line.split(\\W))) .filter(word - !word.isEmpty()) .collect(Collectors.groupingBy( String::toLowerCase, Collectors.counting() ));题目3下面的方法引用等价于哪个Lambda表达式FunctionString, Integer parser Integer::parseInt;答案等价于FunctionString, Integer parser s - Integer.parseInt(s);7. 工具与技巧工欲善其事必先利其器。推荐几个我日常使用的工具JShellJava 9引入的REPL工具特别适合测试Lambda表达式IntelliJ IDEA对Lambda和方法引用有强大的重构支持VisualVM分析Stream操作的性能瓶颈JUnit 5配合Lambda写测试更简洁assertAll( () - assertEquals(4, calculator.add(2, 2)), () - assertTrue(list.contains(key)) );调试Lambda时有个小技巧可以先写成完整的匿名类形式调试通过后再转换为Lambda。IDEA的Show Context Actions(AltEnter)能自动完成这种转换。