Java Lambda 表达式

更新于 2025-12-25

Jakob Jenkov 2020-11-12

Java Lambda 表达式是 Java 8 中的新特性。Lambda 表达式是 Java 向函数式编程迈出的第一步。
一个 Java Lambda 表达式本质上是一个不属于任何类的函数,它可以像对象一样被传递,并在需要时执行。

Lambda 表达式常用于实现简单的事件监听器/回调函数,或与 Java Stream API 结合进行函数式编程。它也广泛应用于 Java 函数式编程 中。


Lambda 与单方法接口

函数式编程经常用于实现事件监听器。在 Java 中,事件监听器通常被定义为只包含一个方法的接口。例如,下面是一个虚构的单方法接口:

public interface StateChangeListener {
    public void onStateChange(State oldState, State newState);
}

这个接口定义了一个方法,每当状态发生变化时(无论观察的是什么对象),该方法就会被调用。

在 Java 7 中,你需要实现这个接口才能监听状态变化。假设你有一个名为 StateOwner 的类,它可以注册状态事件监听器:

public class StateOwner {
    public void addStateListener(StateChangeListener listener) {
        // ...
    }
}

在 Java 7 中,你可以使用匿名内部类来添加监听器:

StateOwner stateOwner = new StateOwner();
stateOwner.addStateListener(new StateChangeListener() {
    public void onStateChange(State oldState, State newState) {
        // 对旧状态和新状态做些处理
    }
});

首先创建一个 StateOwner 实例,然后为其添加一个匿名实现的 StateChangeListener 监听器。

而在 Java 8 中,你可以使用 Lambda 表达式来添加监听器:

StateOwner stateOwner = new StateOwner();
stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed")
);

其中的 Lambda 表达式是这一部分:

(oldState, newState) -> System.out.println("State changed")

该 Lambda 表达式会与 addStateListener() 方法的参数类型进行匹配。如果 Lambda 表达式与参数类型(本例中是 StateChangeListener 接口)匹配,那么该表达式会被转换为一个实现了该接口的对象。

注意:Lambda 表达式只能用于匹配只有一个抽象方法的接口。上例中,Lambda 表达式作为参数传入,其类型是 StateChangeListener 接口,而该接口只有一个方法,因此匹配成功。


Lambda 与接口的匹配规则

只含一个方法的接口有时也被称为函数式接口(functional interface)。将 Lambda 表达式与函数式接口匹配的过程分为以下几步:

  1. 该接口是否只有一个抽象(未实现)方法?
  2. Lambda 表达式的参数是否与该单一方法的参数匹配?
  3. Lambda 表达式的返回类型是否与该单一方法的返回类型匹配?

如果以上三个问题的答案都是“是”,那么该 Lambda 表达式就能成功匹配该接口。


包含默认方法和静态方法的接口

从 Java 8 开始,Java 接口 可以包含默认方法(default methods)静态方法(static methods)。这两种方法都在接口声明中直接提供了实现。

这意味着,只要接口中只有一个未实现的方法(即抽象方法),即使它包含多个默认或静态方法,仍然可以用 Lambda 表达式来实现。

换句话说,只要接口只有一个抽象方法,即使它有默认方法和静态方法,它仍然是一个函数式接口。

下面是一个可以使用 Lambda 表达式实现的接口示例:

import java.io.IOException;
import java.io.OutputStream;

public interface MyInterface {
    void printIt(String text);

    default public void printUtf8To(String text, OutputStream outputStream){
        try {
            outputStream.write(text.getBytes("UTF-8"));
        } catch (IOException e) {
            throw new RuntimeException("Error writing String as UTF-8 to OutputStream", e);
        }
    }

    static void printItToSystemOut(String text){
        System.out.println(text);
    }
}

尽管该接口包含 3 个方法,但只有一个是未实现的(printIt),因此可以用 Lambda 表达式实现:

MyInterface myInterface = (String text) -> {
    System.out.print(text);
};

Lambda 表达式 vs 匿名接口实现

虽然 Lambda 表达式与匿名接口实现非常相似,但它们之间有几个重要区别。

主要区别在于:匿名接口实现可以拥有状态(成员变量),而 Lambda 表达式不能。

考虑以下接口:

public interface MyEventConsumer {
    public void consume(Object event);
}

你可以用匿名内部类实现它:

MyEventConsumer consumer = new MyEventConsumer() {
    public void consume(Object event){
        System.out.println(event.toString() + " consumed");
    }
};

这个匿名实现可以拥有自己的内部状态。例如:

MyEventConsumer myEventConsumer = new MyEventConsumer() {
    private int eventCount = 0;
    public void consume(Object event) {
        System.out.println(event.toString() + " consumed " + this.eventCount++ + " times.");
    }
};

注意这里新增了一个字段 eventCount

Lambda 表达式不能拥有这样的字段,因此 Lambda 被认为是无状态的(stateless)


Lambda 类型推断(Type Inference)

在 Java 8 之前,使用匿名内部类时必须显式指定要实现的接口。例如:

stateOwner.addStateListener(new StateChangeListener() {
    public void onStateChange(State oldState, State newState) {
        // 处理状态变化
    }
});

而使用 Lambda 表达式时,类型通常可以从上下文中推断出来。例如,编译器可以通过 addStateListener() 方法的参数类型(即 StateChangeListener 接口)推断出 Lambda 应该实现哪个接口。这种机制称为类型推断

同样,Lambda 表达式中的参数类型也可以被推断。例如:

stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed")
);

这里没有显式写出 StateChangeListener 接口,也没有写出 oldStatenewState 的类型,但编译器能从 onStateChange() 方法的签名中推断出它们的类型。


Lambda 参数

Lambda 表达式本质上是方法,因此可以像方法一样接收参数。例如前面的 (oldState, newState) 就是参数列表,必须与目标接口方法的参数匹配。

无参数

如果目标方法不接受任何参数,Lambda 写作:

() -> System.out.println("Zero parameter lambda");

括号内为空,表示无参数。

单个参数

如果只有一个参数,可以写作:

(param) -> System.out.println("One parameter: " + param);

或者省略括号:

param -> System.out.println("One parameter: " + param);

多个参数

多个参数必须用括号括起来:

(p1, p2) -> System.out.println("Multiple parameters: " + p1 + ", " + p2);

只有单个参数时才可以省略括号

参数类型

有时需要显式指定参数类型(当编译器无法推断时):

(Car car) -> System.out.println("The car is: " + car.getName());

这类似于普通方法中的参数声明。

Java 11 起支持 var 参数类型

从 Java 11 开始,Lambda 参数也可以使用 var 关键字(Java 10 引入了局部变量类型推断):

Function<String, String> toLowerCase = (var input) -> input.toLowerCase();

这里的 input 类型会被推断为 String,因为变量声明中泛型指定了 Function<String, String>


Lambda 函数体

Lambda 的函数体写在 -> 的右侧。例如:

(oldState, newState) -> System.out.println("State changed")

如果函数体包含多行代码,需要用大括号 {} 包裹:

(oldState, newState) -> {
    System.out.println("Old state: " + oldState);
    System.out.println("New state: " + newState);
}

从 Lambda 表达式返回值

Lambda 表达式可以像普通方法一样返回值:

(param) -> {
    System.out.println("param: " + param);
    return "return value";
}

如果整个 Lambda 表达式只做一件事——计算并返回一个值,可以省略 return 和大括号:

// 冗长写法
(a1, a2) -> { return a1 > a2; }

// 简洁写法
(a1, a2) -> a1 > a2;

编译器会自动将表达式 a1 > a2 视为返回值。


Lambda 作为对象

Lambda 表达式本质上是一个对象,可以赋值给变量并传递:

public interface MyComparator {
    public boolean compare(int a1, int a2);
}

MyComparator myComparator = (a1, a2) -> a1 > a2;
boolean result = myComparator.compare(2, 5);

变量捕获(Variable Capture)

Lambda 表达式可以在特定条件下访问其外部定义的变量,包括:

  • 局部变量
  • 实例变量
  • 静态变量

局部变量捕获

Lambda 可以捕获外部的局部变量,但该变量必须是 “ effectively final ”(即赋值后不再改变):

String myString = "Test";
MyFactory myFactory = (chars) -> {
    return myString + ":" + new String(chars);
};

如果 myString 后续被修改,编译器会报错。

实例变量捕获

Lambda 可以捕获创建它的对象中的实例变量,甚至可以在捕获后修改该变量:

public class EventConsumerImpl {
    private String name = "MyConsumer";
    public void attach(MyEventProducer eventProducer){
        eventProducer.listen(e -> {
            System.out.println(this.name); // 捕获实例变量
        });
    }
}

注意:在 Lambda 中,this 指向外围对象,而不是 Lambda 自身(因为 Lambda 没有自己的实例变量)。这与匿名内部类不同。

静态变量捕获

Lambda 也可以访问静态变量,且允许在捕获后修改:

public class EventConsumerImpl {
    private static String someStaticVar = "Some text";
    public void attach(MyEventProducer eventProducer){
        eventProducer.listen(e -> {
            System.out.println(someStaticVar);
        });
    }
}

方法引用(Method References)

当 Lambda 表达式只是简单地调用另一个方法时,可以使用方法引用来简化语法。

例如,有如下接口:

public interface MyPrinter{
    public void print(String s);
}

普通 Lambda 写法:

MyPrinter myPrinter = s -> System.out.println(s);

使用方法引用:

MyPrinter myPrinter = System.out::println;

双冒号 :: 告诉编译器这是一个方法引用。

静态方法引用

public interface Finder {
    public int find(String s1, String s2);
}

public class MyClass{
    public static int doFind(String s1, String s2){
        return s1.lastIndexOf(s2);
    }
}

Finder finder = MyClass::doFind;

参数方法引用

引用第一个参数的方法:

Finder finder = String::indexOf;
// 等价于 (s1, s2) -> s1.indexOf(s2);

实例方法引用

引用某个对象的实例方法:

public interface Deserializer {
    public int deserialize(String v1);
}

public class StringConverter {
    public int convertToInt(String v1){
        return Integer.valueOf(v1);
    }
}

StringConverter stringConverter = new StringConverter();
Deserializer des = stringConverter::convertToInt;

构造器引用

引用类的构造器:

public interface Factory {
    public String create(char[] val);
}

Factory factory = String::new;
// 等价于 chars -> new String(chars);