CodeGym /课程 /JAVA 25 SELF /Lambda 表达式简介

Lambda 表达式简介

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

1. 认识

简而言之:Lambda 表达式——是一种在“运行中”快速创建函数式接口实现的方式,无需声明单独的类或匿名类。它就像一个没有名字的迷你方法,可以作为参数传递或保存到变量中。

Java 很长一段时间都是一种“经典”的面向对象语言。如果你想传递一小段行为,例如按钮被按下时要做什么,就需要编写匿名类:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("按钮被点击!");
    }
});

自 Java 8 起,出现了 Lambda 的“魔法”:

button.setOnClickListener(() -> System.out.println("按钮被点击!"));

这里的 () -> System.out.println("按钮被点击!") 就是一个 Lambda 表达式。

形式上:Lambda 表达式是对函数式接口中单个抽象方法实现的紧凑写法。

为什么这很重要?

  • 可以把“行为”当作值传递(例如,处理列表中每个元素时做什么)。
  • 编写更加简洁、可读性更高的代码。
  • 使用 Java 的现代 API:Stream API、事件处理、异步任务等。

2. Lambda 表达式语法

通用形式

(parameters) -> expression
// 或者
(parameters) -> { 代码块 }

不同接口的示例

1. 无参数(例如 Runnable):

Runnable r = () -> System.out.println("你好,Lambda!");
r.run();

2. 单个参数(例如 Consumer):

Consumer<String> printer = s -> System.out.println("你传入了:" + s);
printer.accept("Java");

如果只有一个参数且其类型可推断,可以省略括号。
类型 String 是我们传入的变量(s)的类型。

3. 多个参数(例如 Comparator):

Comparator<Integer> cmp = (a, b) -> a - b;
System.out.println(cmp.compare(10, 5)); // 5

类型 Integer 是我们传入的变量(ab)的类型。

4. 多行主体(若有显式返回,需要花括号并写 return):

Function<Integer, Integer> square = x -> {
    int result = x * x;
    return result;
};
System.out.println(square.apply(6)); // 36

第一个 Integer 是函数的返回类型,第二个 Integer 是参数类型。

简写与更简洁的写法

  • 如果主体只有一个表达式,可以省略花括号和 return
  • 如果没有参数——写空括号:() -> ...
  • 如果只有一个参数——可以省略括号:x -> ...
  • 如果有多个参数——需要括号:(ab) -> ...

汇总表

看看同一想法用 Lambda 和匿名类分别如何书写:

接口 Lambda 表达式(示例) 匿名类等价写法
Runnable
() -> System.out.println("Hi")
new Runnable() { public void run() { System.out.println("Hi"); } }
Consumer<String>
s -> System.out.println(s)
new Consumer<String>() { public void accept(String s) { System.out.println(s); } }
Comparator<Integer>
(a, b) -> a - b
new Comparator<Integer>() { public int compare(Integer a, Integer b) { return a - b; } }

自定义接口的示例

假设我们有一个函数式接口:

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

过去我们会这样实现:

Operation sum = new Operation() {
    @Override
    public void apply(int a, int b) {
        return a + b;
    }
};

现在——更现代:

Operation sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8

标准接口的示例

Runnable:

Runnable hello = () -> System.out.println("Hello from thread!");
new Thread(hello).start();

Comparator:

List<String> list = Arrays.asList("苹果", "香蕉", "猕猴桃");
list.sort((a, b) -> a.length() - b.length());
System.out.println(list);

Function:

Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123

3. 作用域与变量捕获

外部上下文的变量(effectively final)

Lambda 表达式可以使用所在方法中的变量。但有一条规则:这些变量必须是effectively final——要么显式声明为 final,要么在初始化后不再被修改。

示例:

String prefix = "结果:";
Function<Integer, String> f = x -> prefix + (x * 2);
// prefix 在这里被“冻结”——之后不能再修改
System.out.println(f.apply(5)); // 结果:10

如果尝试在它被 Lambda 使用之后再修改 prefix,编译器会报错。

为什么?
Lambda 表达式可能会在声明它的那个方法返回之后才被调用。为避免“神奇”的 Bug,Java 只允许使用不可变的变量。

与匿名类的区别

在匿名类中也有同样的规则:外部方法的变量必须是 final/effectively final。但在作用域上有细微差别:匿名类内部的 this 指向匿名类本身,而在 Lambda 中——指向外部类。

public class Demo {
    public void test() {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(this); // 输出:Demo$1(匿名类)
            }
        };

        Runnable r2 = () -> System.out.println(this); // 输出:Demo(外部类)

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

Lambda 与类的字段

Lambda 表达式可以不受限制地访问外部类的字段:

public class Counter {
    private int base = 10;

    public void printSum(int x) {
        Function<Integer, Integer> sum = y -> base + y + x;
        System.out.println(sum.apply(5));
    }
}

这里的 base——可以修改(它是类字段)。
x——必须是 effectively final。

4. 实战:写几个 Lambda 表达式

示例:过滤数字列表

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

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(n -> System.out.println("偶数:" + n));

关于 Stream API 的更多内容,你会在第 30 级了解 :P

示例:字符串转换函数

Function<String, String> capitalize = s -> s.toUpperCase();
System.out.println(capitalize.apply("java")); // JAVA

示例:自定义函数式接口

@FunctionalInterface
interface StringTransformer {
    String transform(String s);
}

StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("你好")); // 你好!

示例:使用外部上下文中的变量

int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// 之后不能再修改 factor!

5. 使用 Lambda 表达式时的常见错误

错误 1:试图修改被 Lambda 捕获的变量。
这样的代码无法编译——变量必须是 effectively final:

int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // 错误:sum 不是 final!

如果需要累加值——使用数组或包装对象。

错误 2:对 this 的作用域产生混淆。
在 Lambda 内部,this 指向外部类,而不是 Lambda 本身(与匿名类不同)。

错误 3:在多行 Lambda 中忘记花括号和 return
如果 Lambda 主体不是单个表达式,就需要花括号和 return

Function<Integer, Integer> square = x -> {
    int y = x * x;
    return y;
};

错误 4:错误地确定 Lambda 的类型。
Lambda 表达式始终是对某个函数式接口的实现。不能直接写:

var f = x -> x + 1; // 错误!无法知道是哪个接口类型。

需要显式指定类型:

Function<Integer, Integer> f = x -> x + 1;
评论 (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
ncksllpo 级别 44,Cherkasy,Ukraine
7 二月 2026
这里有一题没做