Java的lambda语法的细分是什么?

2022-09-01 00:32:28

请解释 Java 8 的 lambda 方法的语法。

关于lambda函数是什么有很多解释,但我找不到对语法的彻底解释,而且我发现很难学会正确复制语法,因为我不明白为什么它们被写成它们。

以下是我遇到的一个常见案例,由 NetBeans 提供:

public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
        new MainAppJFrame();
    });
}

因此,不知何故,以下 lambda 表达式解析为匿名对象的 run() 方法:Runnable

() -> {
    // do stuff
}

lambda语法是否正确,对吧?大括号仅包含匿名方法代码。括号是否是空参数,因为在本例中我们正在创建一个方法?->Runnable.run()

这对我来说都是相当不清楚的。我假设编译器知道根据方法期望的类型实例化匿名者?如果有两种方法仅在参数列表上有所不同,会发生什么情况?显然,在这种特定情况下没有,但在其他地方是可能的:RunnableSwingUtilities.invokeLater(Runnable)SwingUtilities.invokeLater

interface ExampleLambdaConsumer {
    public void doSomething(Runnable r);
    public void doSomething(java.lang.reflect.Method m);
}

class C implements ExampleLambdaConsumer {
    // implementations for doSomething methods here

    public static void main(String[] args) {
        doSomething(() -> {
            // lambda method body here
        }
    }
}

答案 1

语法为:

arguments -> body

其中可以是arguments

  • ()

  • 单个变量(如果可以从上下文中推断出该变量的类型)

  • 一个变量序列,带或不带类型(或自 Java 11 起,带 ),括在括号中。
    示例:、、、(Java 11+)。
    以下情况无效:、 、var(x)(x, y)(int x, int y)(var x, var y)(int x, y)(x, var y)(var x, int y)

并且可以是表达式或带语句的块。表达式(方法或构造函数调用除外)只是返回,即 等效于body{...}() -> 2() -> {return 2;}


对于 lambda 表达式,如(主体是方法或构造函数调用表达式):() -> f()

  • 如果返回,它们等价于f()void() -> { f(); }

  • 否则,它们等效于 或 。编译器从调用上下文中推断它,但通常它更喜欢后者。() -> { f(); }() -> { return f(); })

因此,如果您有两种方法: 和 ,则:void handle(Supplier<T>)void handle(Runnable)

  • handle(() -> { return f(); })并将呼叫第一个,handle(() -> x)

  • handle(() -> { f(); }将调用第二个,并且

  • handle(() -> f()):

    • 如果返回或类型不可转换为 ,则它将调用第二个f()voidT

    • 如果返回可转换为 的类型,则它将调用第一个类型f()T


编译器尝试将 lambda 的类型与上下文匹配。我不知道确切的规则,但答案是:

如果有两个 SwingUtilities.invokeLater 方法仅在参数列表上有所不同,会发生什么情况?

是:这取决于这些参数列表是什么。如果另一个参数也只有一个参数,并且该参数的类型也是具有一种类型方法的接口,那么它会抱怨它无法弄清楚您指的是哪种方法。invokeLatervoid*()

为什么它们被写成现在的样子?好吧,我认为这是因为C#和Scala中的语法几乎相同(他们使用而不是)。=>->


答案 2

语法为

(parameter_list_here) -> { stuff_to_do; }

如果是单个表达式,则可以省略大括号。如果参数列表是单个参数,则可以省略它周围的常规括号。

该语法仅适用于所有功能接口。@FunctionalInterface注释告诉编译器您打算编写这样的接口,如果您不满足要求,则会给出编译错误 - 例如,它只能有1个可重写的方法。

@FunctionalInterface
interface TestInterface {
    void dostuff();
}

Runnable也是这样宣布的。其他接口则不然,它们不能与 lambda 函数一起使用。

现在我们已经用一个不带参数的方法制作了一个新的功能接口,那么我们测试一下你关于签名中“碰撞”的问题怎么样?

public class Main {
    private void test(Runnable r) {

    }
    private void test(TestInterface ti) {

    }
    public static void main(String[] args) { 
        test(() -> { System.out.println("test");})
    }

    @FunctionalInterface
    interface TestInterface {
        void dostuff();
    }
}

结果:编译错误:对方法测试的明确调用。

您会看到,编译器/VM(如果完成运行时)找到适当的方法及其参数列表,并查看参数是否是功能接口,如果是,则创建该接口的匿名实现。从技术上讲(在字节码中),它与匿名类不同,但在其他方面是相同的(你不会看到Main$1.class文件)。

您的示例代码(由 Netbeans 提供)也可以替换为

SwingUtilities.invokeLater(MainAppJFrame::new);

顺便说一句,:)