为了账号安全,请及时绑定邮箱和手机立即绑定

lambda 调用如何与接口交互?

lambda 调用如何与接口交互?

小怪兽爱吃肉 2024-01-28 16:57:35
下面显示的代码片段有效。但是,我不确定它为什么有效。我不太明白 lambda 函数如何将信息传递到接口的逻辑。控制权传递到哪里了?编译器如何理解n循环中的每个和message创建的每个?这段代码编译并给出了预期的结果。我只是不知道怎么做。import java.util.ArrayList;import java.util.List;public class TesterClass {    public static void main(String[] args) {        List<String> names = new ArrayList<>();        names.add("Akira");        names.add("Jacky");        names.add("Sarah");        names.add("Wolf");        names.forEach((n) -> {            SayHello hello = (message) -> System.out.println("Hello " + message);            hello.speak(n);        });    }    interface SayHello {        void speak(String message);    }}
查看完整描述

2 回答

?
森林海

TA贡献2011条经验 获得超2个赞

SayHello是一个单一抽象方法接口,它有一个接受字符串并返回 void 的方法。这类似于消费者。您只是以使用者的形式提供该方法的实现,这类似于以下匿名内部类实现。

SayHello sh = new SayHello() {
    @Override
    public void speak(String message) {
        System.out.println("Hello " + message);
    }
};

names.forEach(n -> sh.speak(n));

幸运的是,您的接口有一个方法,以便类型系统(更正式地说,类型解析算法)可以将其类型推断为SayHello. 但如果有 2 个或更多方法,这是不可能的。

然而,更好的方法是在 for 循环之前声明使用者并使用它,如下所示。声明每次迭代的实现都会创建不必要的对象,这对我来说似乎违反直觉。这是使用方法引用而不是 lambda 的增强版本。这里使用的有界方法引用调用上面声明的实例上的相关方法hello

SayHello hello = message -> System.out.println("Hello " + message);
names.forEach(hello::speak);

更新

考虑到对于无状态 lambda,仅在创建实例时才不会从其词法范围捕获任何内容,这两种方法都仅创建 的一个实例SayHello,并且遵循建议的方法没有任何收益。然而,这似乎是一个实现细节,直到现在我才知道。因此,更好的方法是将消费者传递给您的 forEach,如下面评论中所建议的。另请注意,所有这些方法都仅创建SayHello界面的一个实例,而最后一种更简洁。它看起来是这样的。

names.forEach(message -> System.out.println("Hello " + message));

以下是 JLS §15.27.4的相关部分:Lambda 表达式的运行时求值

这些规则旨在为 Java 编程语言的实现提供灵活性,因为:

  • 不需要在每次评估时分配新对象。

事实上,我最初认为每次评估都会创建一个新实例,这是错误的。

查看完整回答
反对 回复 2024-01-28
?
墨色风雨

TA贡献1853条经验 获得超6个赞

foreach 的签名如下所示:


void forEach(Consumer<? super T> action)

它接受 Consumer 对象。Consumer 接口是一个函数式接口(具有单个抽象方法的接口)。它接受输入并且不返回结果。


定义如下:


@FunctionalInterface

public interface Consumer {

    void accept(T t);

}

因此,任何实现,您的情况都可以用两种方式编写:


Consumer<String> printConsumer = new Consumer<String>() {

    public void accept(String name) {

        SayHello hello = (message) -> System.out.println("Hello " + message);

        hello.speak(n);

    };

};

或者


(n) -> {

            SayHello hello = (message) -> System.out.println("Hello " + message);

            hello.speak(n);

        }

类似地,SayHello 类的代码可以写为


SayHello sh = new SayHello() {

    @Override

    public void speak(String message) {

        System.out.println("Hello " + message);

    }

};

或者


SayHello hello = message -> System.out.println("Hello " + message);

因此,names.foreach在内部首先调用consumer.accept方法并运行其lambda/匿名实现,该实现创建并调用SayHello lambda实现等等。使用lambda表达式,代码非常紧凑、清晰。您唯一需要了解它是如何工作的,即在您的情况下它使用消费者界面。


所以最终流程:foreach -> consumer.accept -> sayhello.speak


查看完整回答
反对 回复 2024-01-28
  • 2 回答
  • 0 关注
  • 91 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信