【问题标题】:Lambda expressions mechanisms in Java [duplicate]Java中的Lambda表达式机制[重复]
【发布时间】:2017-11-25 10:33:49
【问题描述】:

我刚刚在一本书中读到,当一个 lambda 表达式被分配给一个函数式接口时,它会为 lambda 设置“目标类型”,并创建一个该类型的实例(即函数式接口的类型)用作函数式接口中抽象方法实现的 lambda 表达式。

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

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

【问题讨论】:

标签: java lambda anonymous-class


【解决方案1】:

如果是这样,那么这是否意味着 lambdas 并不是真正独立的方法,而是一种被引入语言的新型元素,

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

但只是表达匿名类的一种更紧凑的方式,因此只是在编译器方面添加了工具(就像泛型一样)?

不,它不仅在编译器方面。 JVM里面也有代码,这样编译器就不用为lambda写class文件了。

此外,方法引用如何遵守这一点,特别是不与任何对象关联的静态方法?

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

例如,当对实例方法的方法引用分配给功能接口时,则使用该方法的封装对象,

不,不能使用。让我们使用System.out::println 方法引用来举个例子:

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

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

运行时生成的类看起来像

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

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

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

评论中的问题:“为什么不直接分配给实例及其方法?”

让我们稍微扩展一下这个例子:

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

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

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

对于此类对象,其他语言可能有其他名称或概念 - 在 Java 中,已确立的概念是“实现接口的匿名类和该匿名类的对象”。

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

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

【讨论】:

  • 感谢您的详细回复。但是我仍然很难理解:当该类及其方法的实现将调用实例时,为什么“JVM 会生成一个使用方法 void accept(E e) 实现 Consumer 的类” (通过它的实际对象)来执行功能接口的契约?为什么不直接分配给实例及其方法?你认为这是因为实例没有装饰性地实现接口吗?
  • @Learnerer 我已经更新了我的答案,希望对您有所帮助
  • 可能混淆是由于编程模型和实现机制之间的干扰......感谢您的回答。
【解决方案2】:

例如,当分配一个实例方法的方法引用时 到一个功能接口,然后是它的封装对象 方法被使用,但在静态方法的情况下会发生什么 - 那些不与任何对象相关联..

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

现在,不要有List&lt;String&gt; list 并让其中包含一些字符串。我们可以这样做:

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

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

【讨论】:

  • 谢谢。但我真的不是在问如何使用 lambda 或方法引用,我只是想弄清楚幕后的工作原理——它们是如何实际实现的
猜你喜欢
  • 1970-01-01
  • 2021-11-27
  • 2011-01-21
  • 2013-01-23
  • 1970-01-01
  • 2017-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多