CodeGym /课程 /JAVA 25 SELF /Lambda 表达式:语法与作用域

Lambda 表达式:语法与作用域

JAVA 25 SELF
第 48 级 , 课程 0
可用

1. 引言

坦白说:为了写一两行代码去使用匿名类,就像为把面包店的一只面包搬到商店而雇一辆大卡车一样夸张。

例如,如果你想按长度对字符串列表排序,在 Java 8 之前通常要这样写:

List<String> list = Arrays.asList("苹果", "香蕉", "猕猴桃");
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

看起来任务很简单,代码却占了半个屏幕。尤其当这种操作很多时,代码会变得“嘈杂”,问题的本质被淹没。程序员们抱怨又痛苦,后来就发明了 Lambda 表达式。我们之前已经学过一些,现在来复习并进一步深入。

什么是 Lambda 表达式

Lambda 表达式是一种实现函数式接口的简洁写法,即只包含一个抽象方法的接口(例如 ComparatorRunnableConsumer 等)。

简单来说,Lambda 让你可以在需要的地方“现场”写出一个函数,而无需声明单独的类或方法。

通用语法:

(params) -> { body }

示例:

  • (a, b) -> a + b — 两数相加的函数
  • x -> x * x — 求平方的函数
  • () -> System.out.println("Hello!") — 无参数的函数

与函数式接口的关系:
Lambda 表达式总是可以赋给函数式接口类型的变量,或作为参数传给期望该接口的方法。

2. Lambda 表达式的语法

无参数

Runnable r = () -> System.out.println("你好,世界!");
r.run(); // 输出:你好,世界!

单个参数
如果只有一个参数,可以省略括号:

Consumer<String> print = s -> System.out.println(s);
print.accept("Java 很酷!");

多个参数
必须加括号:

Comparator<String> cmp = (a, b) -> a.length() - b.length();

单表达式主体
如果主体只有一个表达式,则无需花括号和 return

Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25

代码块作为主体
如果需要多条语句,使用花括号,并在有返回值时使用 return

Function<Integer, Integer> abs = x -> {
    if (x < 0) {
        return -x;
    }
    return x;
};
System.out.println(abs.apply(-3)); // 3

参数类型
大多数情况下可由上下文推断,无需显式声明;如果愿意,也可以显式写出:

Comparator<String> cmp = (String a, String b) -> a.length() - b.length();

无返回值的 Lambda
如果接口返回 void,只需写语句:

list.forEach(s -> System.out.println("元素:" + s));

表:Lambda 语法的不同写法

要做什么 示例 说明
无参数
() -> System.out.println("Hi")
例如用于 Runnable
单个参数
x -> x * x
可省略括号
多个参数
(a, b) -> a + b
必须带括号
单表达式
x -> x + 1
无需 return 和花括号
代码块
x -> { int y = x + 1; return y * 2; }
有返回值时需要 return

3. 应用:Lambda 表达式的使用场景与方式

当需要将“行为”(函数)作为参数传递时,最常用的就是 Lambda 表达式。这为集合、流(Stream API)、事件等带来了真正的变革。

列表排序

在 Java 8 之前:

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

使用 Lambda:

list.sort((a, b) -> a.length() - b.length());

创建线程(Thread)

Thread t = new Thread(() -> System.out.println("线程已启动!"));
t.start();

处理集合

list.forEach(s -> System.out.println(s.toUpperCase()));

过滤列表

List<String> longWords = list.stream()
    .filter(s -> s.length() > 5)
    .collect(Collectors.toList());

示例:扩展我们的教学应用

假设我们有一个用户列表:

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

输出所有名字长度大于 4 的用户:

users.stream()
    .filter(name -> name.length() > 4)
    .forEach(name -> System.out.println("用户:" + name));

4. Lambda 表达式中的变量作用域

Lambda 可以使用外层方法中的变量,但有一些细节需要注意!

变量与 Lambda

Java 中的 Lambda 只能“捕获”初始化后不再改变的变量。如果变量声明为 final——很直观。即使没有写出 final,编译器也会检查其值是否被修改;如果没有修改,就将其视为“有效 final”,可以放心地在 Lambda 中使用。

示例:

int minLength = 4; // 值在任何地方都未改变
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});

这里可以正常工作,因为 minLength 始终保持不变。

但如果在 Lambda 使用之后尝试重新给 minLength 赋值,就会得到编译错误:

int minLength = 4;
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});
minLength = 10; // 错误!Lambda 已经“固定”了该值

本质上规则很简单:进入 Lambda 的变量必须是不可变的。

与匿名类的区别

在匿名类和 Lambda 表达式中,来自外部方法的变量规则相同:只能是 final/有效 final。

但是!
在 Lambda 中,this 指向外部对象(当前类实例);而在匿名类中,this 指向匿名类的实例。如果你在 Lambda 中访问当前类的字段或方法,这一点非常重要。

示例:

public class Example {
    String name = "外部类";

    void demo() {
        Runnable r1 = new Runnable() {
            String name = "匿名类";
            @Override
            public void run() {
                System.out.println(this.name); // "匿名类"
            }
        };

        Runnable r2 = () -> System.out.println(this.name); // "外部类"

        r1.run();
        r2.run();
    }
}

5. 使用 Lambda 时的常见错误

错误 1:使用非 final/非“有效 final”的变量。 如果你决定在 Lambda 使用之后再修改变量,编译器会立刻阻止。这是为了安全:否则就不清楚应当使用变量的哪个值。

错误 2:把 this 搞混。 在 Lambda 中,this 指外部类;在匿名类中,this 指匿名类本身。如果你想从 Lambda 调用外部类的方法可以正常工作,而在匿名类中则不行(如果你指望外部类的上下文)。

错误 3:脱离上下文的 Lambda。 Lambda 不能单独使用——要么赋给函数式接口类型的变量,要么传入一个期望该接口的地方。尝试在没有上下文的情况下直接写 x -> x + 1 会导致错误。

错误 4:Lambda 过于复杂。 当 Lambda 超过 3–5 行时就会难以阅读,此时最好把逻辑提取到单独的方法中。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION