Skip to content

第32课:Lambda 表达式

🎯 学习目标

  • 理解函数式编程思想
  • 掌握 Lambda 表达式语法
  • 理解函数式接口
  • 掌握方法引用和构造器引用
  • 理解闭包和变量捕获

📖 一、为什么需要 Lambda?

传统写法 vs Lambda

场景:给列表排序

java
// 传统写法:匿名内部类(冗长)
List<String> list = Arrays.asList("banana", "apple", "cherry");

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

// Lambda 写法(简洁)
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));

// 更简洁:方法引用
Collections.sort(list, String::compareTo);

📖 二、Lambda 语法

基本语法

java
// 完整形式
(参数列表) -> {
    语句;
    return 结果;
}

// 简化形式
参数 -> 表达式

语法演变

java
// 1. 标准接口定义
interface Calculator {
    int calculate(int a, int b);
}

// 2. 匿名内部类(Java 7)
Calculator add = new Calculator() {
    @Override
    public int calculate(int a, int b) {
        return a + b;
    }
};

// 3. Lambda 完整形式
Calculator add = (int a, int b) -> {
    return a + b;
};

// 4. 省略参数类型(类型推断)
Calculator add = (a, b) -> {
    return a + b;
};

// 5. 单语句省略 return 和大括号
Calculator add = (a, b) -> a + b;

// 6. 单参数省略括号
Function<String, Integer> len = s -> s.length();

// 7. 无参数
Runnable r = () -> System.out.println("Hello");

📖 三、函数式接口

什么是函数式接口?

只有一个抽象方法的接口

java
@FunctionalInterface  // 可选注解,编译时检查
public interface MyFunction {
    void run();  // 唯一的抽象方法
    
    // 可以有默认方法
    default void defaultMethod() { }
    
    // 可以有静态方法
    static void staticMethod() { }
}

// 使用
MyFunction func = () -> System.out.println("Running");
func.run();

常用函数式接口

1. Supplier&lt;T> - 供应者(无参数,有返回值)

java
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

// 使用
Supplier<String> supplier = () -> "Hello World";
String result = supplier.get();  // "Hello World"

// 实际应用:延迟计算
public String getValue(Supplier<String> supplier) {
    if (某条件) {
        return supplier.get();  // 需要时才计算
    }
    return null;
}

2. Consumer&lt;T> - 消费者(有参数,无返回值)

java
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

// 使用
Consumer<String> printer = str -> System.out.println(str);
printer.accept("Hello");  // 输出:Hello

// 实际应用:遍历
List<String> list = Arrays.asList("A", "B", "C");
list.forEach(item -> System.out.println(item));

3. Function<T, R> - 函数(有参数,有返回值)

java
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

// 使用
Function<String, Integer> strLen = str -> str.length();
int length = strLen.apply("Hello");  // 5

// 实际应用:转换
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> lengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());

4. Predicate&lt;T> - 断言(判断真假)

java
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

// 使用
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4));  // true

// 实际应用:过滤
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evens = numbers.stream()
    .filter(num -> num % 2 == 0)
    .collect(Collectors.toList());

5. BiFunction<T, U, R> - 双参数函数

java
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
int sum = add.apply(10, 20);  // 30

6. UnaryOperator&lt;T> - 一元操作符

java
// UnaryOperator<T> 相当于 Function<T, T>
UnaryOperator<Integer> square = x -> x * x;
int result = square.apply(5);  // 25

📖 四、方法引用

四种方法引用

1. 静态方法引用 - ClassName::staticMethod

java
// Lambda
Function<String, Integer> parser = str -> Integer.parseInt(str);

// 方法引用
Function<String, Integer> parser = Integer::parseInt;

int num = parser.apply("123");  // 123

2. 实例方法引用 - object::instanceMethod

java
String prefix = "Hello, ";

// Lambda
Function<String, String> greeter = name -> prefix.concat(name);

// 方法引用
Function<String, String> greeter = prefix::concat;

String greeting = greeter.apply("Alice");  // "Hello, Alice"

3. 类方法引用 - ClassName::instanceMethod

java
// Lambda
Function<String, Integer> lengthGetter = str -> str.length();

// 方法引用
Function<String, Integer> lengthGetter = String::length;

// 排序示例
List<String> list = Arrays.asList("banana", "apple", "cherry");
Collections.sort(list, String::compareTo);

4. 构造器引用 - ClassName::new

java
// Lambda
Supplier<List<String>> listSupplier = () -> new ArrayList<>();

// 构造器引用
Supplier<List<String>> listSupplier = ArrayList::new;

List<String> list = listSupplier.get();

// 带参数的构造器
Function<Integer, int[]> arrayCreator = size -> new int[size];
Function<Integer, int[]> arrayCreator = int[]::new;

int[] array = arrayCreator.apply(10);  // 创建长度为10的数组

📖 五、闭包和变量捕获

1. 闭包概念

java
public static Function<Integer, Integer> makeAdder(int x) {
    // Lambda 捕获外部变量 x
    return y -> x + y;
}

Function<Integer, Integer> add5 = makeAdder(5);
System.out.println(add5.apply(10));  // 15

2. 变量捕获规则

java
public void test() {
    int x = 10;
    
    // ✅ 可以访问外部变量
    Runnable r1 = () -> System.out.println(x);
    
    // ❌ 但不能修改(必须是 final 或 effectively final)
    Runnable r2 = () -> {
        // x = 20;  // 编译错误
    };
    
    // ❌ 外部修改也不行
    // x = 20;  // 这样会导致 Lambda 编译错误
}

Effectively Final

java
int x = 10;  // 没有声明 final

// 但如果后续没有修改,就是 effectively final
Runnable r = () -> System.out.println(x);  // ✅ 可以

// 如果修改了,就不是 effectively final
x = 20;  // 导致上面的 Lambda 编译错误

解决方案:使用数组或对象包装

java
final int[] counter = {0};

Runnable r = () -> {
    counter[0]++;  // ✅ 可以修改数组内容
};

📖 六、实战应用

1. 集合操作

java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 过滤
List<String> filtered = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

// 转换
List<Integer> lengths = names.stream()
    .map(String::length)
    .collect(Collectors.toList());

// 排序
names.sort((a, b) -> a.length() - b.length());
// 或
names.sort(Comparator.comparingInt(String::length));

2. 线程

java
// 传统写法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Running");
    }
}).start();

// Lambda
new Thread(() -> System.out.println("Running")).start();

3. 事件处理

java
// GUI 按钮点击
button.addActionListener(event -> {
    System.out.println("Button clicked");
});

4. 自定义排序

java
List<Person> people = Arrays.asList(
    new Person("Alice", 30),
    new Person("Bob", 25),
    new Person("Charlie", 35)
);

// 按年龄排序
people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));

// 或使用 Comparator
people.sort(Comparator.comparingInt(Person::getAge));

// 多条件排序
people.sort(Comparator.comparing(Person::getName)
                      .thenComparingInt(Person::getAge));

⚠️ 常见陷阱

陷阱1:把 Lambda 写成复杂业务块

Lambda 适合表达短小行为。如果函数体超过几行、包含多个分支或异常处理,应该抽成具名方法。

陷阱2:误解变量捕获

Lambda 只能捕获 final 或 effectively final 的局部变量。不能在 Lambda 内修改外部局部变量。

陷阱3:异常处理变得隐蔽

函数式接口的方法签名如果没有声明受检异常,Lambda 内部就不能直接抛出受检异常,需要转换或在内部处理。

陷阱4:方法引用可读性反而更差

方法引用不是越多越好。如果 this::handle 让读者不知道处理逻辑在哪里,普通 Lambda 可能更直观。

陷阱5:忽略闭包持有对象生命周期

Lambda 捕获外部对象后,可能延长对象生命周期。异步任务、缓存回调、监听器中尤其要注意内存泄漏。


🆚 Java vs C 对比

特性CJava Lambda
行为传递函数指针函数式接口
上下文捕获手动传上下文指针自动捕获 effectively final 变量
类型检查函数指针签名编译期检查函数式接口
运行机制直接函数地址调用invokedynamic + 运行时适配

对 C 程序员来说,Lambda 可以理解为“带类型约束和有限闭包能力的函数指针”。区别在于 Java 的 Lambda 必须落到某个函数式接口上,不能脱离类型系统单独存在。


💡 最佳实践

1. 保持简洁

java
// ❌ 过于复杂
list.forEach(item -> {
    String processed = item.trim().toLowerCase();
    if (processed.length() > 5) {
        System.out.println(processed);
    }
});

// ✅ 提取为方法
list.forEach(this::processAndPrint);

private void processAndPrint(String item) {
    String processed = item.trim().toLowerCase();
    if (processed.length() > 5) {
        System.out.println(processed);
    }
}

2. 优先使用方法引用

java
// ❌ Lambda
list.forEach(item -> System.out.println(item));

// ✅ 方法引用
list.forEach(System.out::println);

3. 避免副作用

java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = new ArrayList<>();

// ❌ 有副作用(修改外部变量)
numbers.forEach(num -> result.add(num * 2));

// ✅ 无副作用(使用 Stream)
List<Integer> result = numbers.stream()
    .map(num -> num * 2)
    .collect(Collectors.toList());

📝 练习

完成 练习/Ex32_Lambda.java

  1. 基本 Lambda 语法
  2. 函数式接口使用
  3. 方法引用
  4. 闭包和变量捕获
  5. 集合操作
  6. 综合:使用 Lambda 重构旧代码

🎓 下一步

  • 第33课:Stream API - 中间操作、终止操作、并行流