Skip to content

Chapter 18 · Lambda 与 Stream API

JDK 8 引入的"函数式革命",让 Java 代码瞬间变得简洁优雅。


🎯 本章目标

  • 掌握 Lambda 表达式 的多种写法
  • 学会 方法引用::
  • 理解 函数式接口@FunctionalInterface
  • 用熟 Stream API:filter / map / reduce / collect
  • 知道 Optional 优雅处理 null

1. Lambda 表达式

没有 Lambda

java
List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);

Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer a, Integer b) {
        return a - b;
    }
});

有了 Lambda

java
Collections.sort(list, (a, b) -> a - b);
// 还可以更简
list.sort(Comparator.naturalOrder());

Lambda 语法

java
(参数列表) -> { 方法体 }

() -> 42                              // 无参
x -> x * 2                            // 单参(括号可省略)
(x, y) -> x + y                       // 多参
(int x, int y) -> x + y               // 显式类型
(x, y) -> { System.out.println(x); return x + y; }   // 多语句

2. 函数式接口

只有一个抽象方法的接口,可以用 Lambda 实现。

java
@FunctionalInterface
interface Calculator {
    int calc(int a, int b);
}

Calculator add = (a, b) -> a + b;
Calculator mul = (a, b) -> a * b;
System.out.println(add.calc(3, 4));   // 7
System.out.println(mul.calc(3, 4));   // 12

JDK 内置常用函数式接口

接口抽象方法用途
Runnablevoid run()无参无返回
Supplier<T>T get()无参,返回 T
Consumer<T>void accept(T)接收 T,无返回
Function<T,R>R apply(T)T → R
Predicate<T>boolean test(T)T → boolean(判断)
BiFunction<T,U,R>R apply(T,U)(T,U) → R
UnaryOperator<T>T apply(T)T → T
BinaryOperator<T>T apply(T,T)(T,T) → T
Comparator<T>int compare(T,T)比较

函数式接口


3. 方法引用 ::

Lambda 直接调一个已有方法 → 用方法引用更简洁:

java
list.forEach(s -> System.out.println(s));   // Lambda
list.forEach(System.out::println);           // 方法引用

4 种方法引用

类型形式例子
静态方法类::staticMethodInteger::parseInt
实例方法(特定对象)obj::methodSystem.out::println
实例方法(任意对象)类::instanceMethodString::toUpperCase
构造器引用类::newArrayList::new
java
List<String> nums = List.of("1", "2", "3");
nums.stream().map(Integer::parseInt).forEach(System.out::println);

list.stream().map(String::toUpperCase).toList();

Supplier<List<String>> factory = ArrayList::new;
List<String> newList = factory.get();

4. Stream API

"对集合的链式操作" — 像水流一样连续处理。

java
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sum = list.stream()                    // 创建 Stream
              .filter(x -> x % 2 == 0)     // 中间操作:偶数
              .mapToInt(Integer::intValue) // 中间操作:拆箱
              .sum();                       // 终止操作:求和

System.out.println(sum);   // 30

Stream 流水线

Stream 三阶段

  1. 创建:从集合 / 数组 / 文件得到 Stream
  2. 中间操作(懒加载,可链式):filter / map / sorted / distinct / limit / skip
  3. 终止操作(触发执行):forEach / collect / reduce / count / sum / findFirst

4.1 创建 Stream

java
Stream<String> s1 = Stream.of("a", "b", "c");
Stream<Integer> s2 = list.stream();
Stream<Integer> s3 = list.parallelStream();   // 并行流
IntStream s4 = IntStream.range(1, 100);
Stream<String> s5 = Files.lines(Paths.get("a.txt"));

4.2 中间操作

java
list.stream()
    .filter(x -> x > 3)            // 筛选
    .map(x -> x * 2)               // 转换
    .sorted()                       // 排序
    .distinct()                     // 去重
    .limit(5)                       // 取前 5
    .skip(2)                        // 跳过前 2
    .peek(System.out::println)     // 偷看(debug 用)
    .toList();

4.3 终止操作

java
// forEach
list.forEach(System.out::println);

// collect
List<Integer> r1 = list.stream().filter(...).toList();    // JDK 16+
List<Integer> r2 = list.stream().filter(...).collect(Collectors.toList());
Set<Integer> r3 = list.stream().collect(Collectors.toSet());
Map<String, Integer> r4 = list.stream().collect(Collectors.toMap(k -> k, v -> v.length()));

// reduce
int sum = list.stream().reduce(0, Integer::sum);
Optional<Integer> max = list.stream().reduce(Integer::max);

// 数值聚合
IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
stats.getMax(); stats.getMin(); stats.getAverage(); stats.getCount();

// 短路操作
list.stream().anyMatch(x -> x > 100);   // 任意匹配
list.stream().allMatch(x -> x > 0);      // 全部匹配
list.stream().findFirst();               // 第一个

// 计数
long n = list.stream().count();

4.4 分组(Collectors.groupingBy)

java
Map<String, List<Person>> byCity = persons.stream()
    .collect(Collectors.groupingBy(Person::getCity));

Map<String, Long> countByCity = persons.stream()
    .collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));

Map<String, Integer> ageSum = persons.stream()
    .collect(Collectors.groupingBy(Person::getCity, Collectors.summingInt(Person::getAge)));

5. Optional:告别 NPE

java
Optional<String> opt = Optional.of("hello");
Optional<String> empty = Optional.empty();
Optional<String> nullable = Optional.ofNullable(maybeNull);

opt.isPresent();                // true
opt.get();                      // "hello"
opt.orElse("default");
opt.orElseThrow();              // 没有值就抛 NoSuchElementException
opt.ifPresent(System.out::println);

opt.map(String::toUpperCase)
   .filter(s -> !s.isEmpty())
   .ifPresentOrElse(
       v -> System.out.println("有: " + v),
       () -> System.out.println("空"));

6. Stream vs 传统 for(性能 + 可读性)

传统写法

java
List<String> result = new ArrayList<>();
for (Person p : persons) {
    if (p.getAge() >= 18 && p.getCity().equals("北京")) {
        result.add(p.getName().toUpperCase());
    }
}
result.sort(Comparator.naturalOrder());
result = result.stream().limit(10).toList();

Stream 写法

java
List<String> result = persons.stream()
    .filter(p -> p.getAge() >= 18)
    .filter(p -> p.getCity().equals("北京"))
    .map(p -> p.getName().toUpperCase())
    .sorted()
    .limit(10)
    .toList();

💡 可读性提升,但小数据集 Stream 可能略慢(创建对象开销);大数据集 + 并行流 → 可显著加速。


7. 实战练习

文件内容
LambdaBasics.javaLambda 多种写法
FunctionalInterfaceDemo.javaJDK 内置接口
MethodReferenceDemo.java方法引用 4 种
StreamBasics.javafilter/map/reduce/collect
StreamGroupingDemo.java分组、分区、聚合
OptionalDemo.javaOptional 优雅写法

8. 浏览器演示

打开 demo.html

  • Lambda 语法转换器
  • Stream 操作可视化
  • 性能对比(for 循环 vs Stream)

9. 面试可能会问什么?

Q1: Lambda 表达式本质是什么?

实现函数式接口的简便写法。编译后会生成一个匿名类(或 invokedynamic)。

Q2: Stream 是惰性求值吗?

是的。中间操作(filter/map/sorted)不会立即执行,只是构建处理链;只有终止操作(collect/forEach)才会触发整条链执行。

Q3: 中间操作和终止操作的区别?

  • 中间操作:返回 Stream,可链式
  • 终止操作:返回非 Stream(List、值、void),触发执行

Q4: parallelStream 和 stream 区别?

  • stream():单线程
  • parallelStream():用 ForkJoinPool 并行执行

⚠️ 别在 web 容器里随便用 parallelStream,可能抢占容器线程。

Q5: Optional 的注意事项?

  1. 不要把 Optional 作字段或参数,只用作返回值
  2. 不要 optional.get() 不判空
  3. 优先 orElse / orElseGet / ifPresent

Q6: 函数式接口必须加 @FunctionalInterface 吗?

不必须,但强烈建议加。编译期检查(防止意外加多个抽象方法)。

Q7: 收集到 Map 时 key 重复怎么办?

java
Map<String, Integer> m = list.stream()
    .collect(Collectors.toMap(
        k -> k.getName(),
        v -> v.getAge(),
        (existing, newVal) -> existing));     // 第三个参数:合并函数

不写第三个参数,重复 key 会抛 IllegalStateException。


🎁 本章小结

✅ Lambda:替代匿名内部类,简洁优雅
✅ 函数式接口:Function/Consumer/Supplier/Predicate
✅ 方法引用 4 种:静态/实例/类实例方法/构造器
✅ Stream:filter/map/reduce/collect 流水线处理
✅ Stream 是惰性的,终止操作才触发
✅ Optional 优雅处理可能为 null 的返回值

🔗 导航