Java 中的 Lambda 表达式机制

Lambda expressions mechanisms in Java

我刚刚在一本书中读到,当将 lambda 表达式分配给功能接口时,就会为 lambda 和该类型的实例(即功能接口的类型)设置 "target type"使用 lambda 表达式创建,用作功能接口中抽象方法的实现。

我的问题:如果是这样,那是否意味着 lambda 不是真正独立的方法,因此是一种新型元素引入语言,但只是一种更紧凑的方式来表达匿名 class,因此只是在编译器方面添加了便利(就像泛型一样)?

此外,方法引用如何遵守,特别是不与任何对象关联的静态方法?例如,当将实例方法的方法引用分配给功能接口时,将使用该方法的封装对象,但在静态方法的情况下会发生什么 - 这些方法不与任何对象关联.. ?

For example, when a method reference to an instance method is assigned to a functional interface then the encapsulating object for that method is used, but what happens in the case of a static method - those are not associated with any object..

这取决于上下文。假设我们有一个静态 Utils#trim(String) 方法,显然会 trim 给定字符串。

现在,以免有一个 List<String> list 并在其中添加一些字符串。我们可以这样做:

list.stream().map(Utils::trim).collect(Collectors.toList());

如您所见,在给定的上下文中,我们使用 lambda 静态方法引用,以便将列表中的每个字符串用作 Utils::trim 方法的输入参数。

If so, then does that mean lambdas aren't really standalone methods and as such a new type of element brought into the language,

正确,lambda 被编译成具有合成名称的普通方法

but are simply a more compact way for expressing an anonymous class and as such merely are added facility (just like generics) on the compiler's side?

不,它不仅在编译器方面。 JVM 中也有相关代码,因此编译器不必为 lambda 编写 class 文件。

Moreover, how do method references comply with that, in particular, static methods which are not associated with any objects?

方法引用与 lambda 没有什么不同:在运行时必须有一个对象实现功能接口。在调用对象的 "SAM" 时,此方法将调用引用的方法。

For example, when a method reference to an instance method is assigned to a functional interface then the encapsulating object for that method is used,

不,不能使用。让我们以使用 System.out::println 方法参考的以下示例为例:

Arrays.asList("A", "B").forEach(System.out::println);

List<E>.forEach() 期望 Consumer<? super E> 定义方法 void accept(E e)。编译器需要在class文件中生成字节码等信息,以便JVM在运行时生成一个class实现Consumer<E>的方法void accept(E e)。这个生成的方法然后调用 System.out.println(Object o).

生成的运行时 class 看起来像

class $$lambda$xy implements Consumer<Object> {
    private PrintStream out;

    $$lambda$xy(PrintStream out) {
        this.out = out;
    }

    void accept(Object o) {
        out.println(o);
    }
}

你的问题来自评论:"Why not directly assign to instance and its method?"

让我们稍微扩展一下示例:

static void helloWorld(Consumer<String> consumer) {
    consumer.apply("Hello World!");
}

public static void main(String[] args) {
    helloWorld(System.out::println);
}

为了编译它,编译器必须生成字节码来创建一个实现 Consumer<String> 的对象(因此它可以将对象传递给 helloWorld())。该对象必须以某种方式存储信息,即在调用它的 accept(x) 方法时,它必须在 System.out PrintStream 上调用 println(x)

其他语言可能对此类对象有其他名称或概念 - 在 Java 中,既定概念是 "an anonymous class implementing the interface and an object of that anonymous class"。

对象如何存储这些信息?好吧,你可以发明一些超级酷的新方法来存储这些信息。 Java 语言设计者决定匿名 class 就足够了 - 目前。但他们有先见之明,如果有人提出一个新想法以更有效的方式实现它,这应该很容易集成到 Java 生态系统(Java 编译器和 JVM)中。

因此他们还决定不在编译时创建匿名 class,而是让编译器将必要的信息写入 class 文件。现在 JVM 可以在运行时决定存储信息的最佳方式(在正确的对象上调用正确的方法)。