为什么下面的lambda在分配给Runnable时编译?



我有下面一段显然无法编译的代码:

jshell> static int counter = 0;
counter ==> 0
jshell> Runnable r = () -> counter;
|  Error:
|  incompatible types: lambda body is not compatible with a void functional interface
|      (consider using a block lambda body, or use a statement expression instead)
|  Runnable r = () -> counter;
|

这是可以理解的,因为Runnablerun签名是void run()。现在,如果我有以下lambda,它将正常编译:

jshell> static int counter = 0;
counter ==> 0
jshell> Runnable r = () -> counter++;
r ==> $Lambda$23/0x0000000800c0b260@3941a79c

为什么这段代码编译,知道counter++仍将返回一个整数?

更令人困惑的是,这段代码也可以被编译:

jshell> Supplier<Integer> f = () -> counter++;
f ==> $Lambda$22/0x0000000800c0a410@12edcd21

我试着用Java 17的jshell编译上面的代码。

Runnable中,返回值被忽略;主要的部分(为什么它会编译)是其中有一个语句。

它确实返回一个值,所以它也是一个Supplier。它还操纵counter(作为副作用),但这对于实现功能接口无关紧要。

(展开评论,附带其他答案和评论)

如果将考虑使用块lambda体解释为"考虑如果使用块lambda体会发生什么",则错误消息本身提供了一些指导。

当您编写无块lambda函数arguments -> something时,Java仍然会从两个实际块中选择一个:

  1. void somefunction(arguments) {
    something;
    }
    
    并且(正如其他答案和评论所指出的)在这种情况下,something;必须是一个有效的语句
  2. sometype somefunction(arguments) {
    return something;
    }
    
    其中something必须是一个有效的表达式,提供返回的结果。

选择是显式的,您在指定lambda本身的类型时亲自执行。

所以当你写Runnable时,它有一个单独的void run()方法,Java试图为你创建它:

  1. void run() {
    counter;
    }
    
    这不会工作,因为counter;不存在于Java
  2. void run() {
    return counter;
    }
    
    这将不起作用,因为void方法不能return数字。

然而,后者将与Supplier<Integer>一起工作,Integer get()只有一个方法:

  1. Integer get() {
    return counter;
    }
    
    此方法可以存在(与自动int->Integer拳击)。

当您有counter++时,两者都可以存在,(1.)用于Runnable,(2.)用于Supplier<Integer>:

  1. class InnerClassForLambda implements Runnable {
    public void run() {
    counter++;
    }
    }
    
    增加counter并且不返回任何内容
  2. class InnerClassForLambda implements Supplier<Integer> {
    Integer get() {
    return counter++;
    }
    }
    
    counter加1,并返回加1之前的值。

比较

jshell> Runnable r = () -> counter++;

jshell> Runnable r = () -> System.out.println("Hello");

,然后更容易理解为什么前者可以编译。

System.out.println("Hello");一样,counter++;也是一个语句。

现在

Supplier<Integer> f = () -> counter++;

可以编译,因为counter++没有返回值。

最新更新