为什么这个lambda表达式可以分配给不同的功能接口?

时间:2018-06-09 20:24:46

标签: java lambda java-8

鉴于此方法:

private static Integer return0() {
    return 0;
}

我发现了以下lambda表达式的奇怪属性:

() -> return0();

它实际上是从它调用的函数返回值(这会使它成为Supplier-Interface)还是不返回值,而只调用函数并返回void(这将使其成为Runnable-Interface)。直觉上,我希望第一种情况是正确的,但可以与第二种情况一起使用。

尝试分配语句时:

    Supplier<Integer> supplier2 = () -> return0();
    Runnable runnable2 = () -> return0();

事实证明这两行都是编译的!他们为什么要这样做?这完全是模棱两可的,真的令人困惑!

修改 这里有更多的代码来展示我的意思是混淆/暧昧:

public static void main(String[] args) {
    callMe(() -> return0());
}

private static Integer return0() {
    return 0;
}

private static void callMe(Supplier<Integer> supplier) {
    System.out.println("supplier!");
}

private static void callMe(Runnable runnable) {
    System.out.println("runnable!");
}

这一切都很好地编译并在执行时打印“供应商!”。我没有发现选择第一种方法而是相当随意的特别直观。

3 个答案:

答案 0 :(得分:3)

规范的相关部分是Sec 15.27.3(强调我的):

  

如果满足以下所有条件,则lambda表达式与函数类型一致:

     
      
  • 函数类型没有类型参数。

  •   
  • lambda参数的数量与函数类型的参数类型的数量相同。

  •   
  • 如果明确键入lambda表达式,则其形式参数类型与函数类型的参数类型相同。

  •   
  • 如果假设lambda参数与函数类型的参数类型具有相同的类型,则:

         
        
    • 如果函数类型的结果为void,则lambda主体是语句表达式(§14.8)或与void兼容的块。

    •   
    • 如果函数类型的结果是(非void)类型R,那么i)lambda body是一个在赋值上下文中与R兼容的表达式,或者ii)lambda body是一个值兼容的块,每个结果表达式(§15.27.2)在赋值上下文中与R兼容。

    •   
  •   

你的lambda body是一个语句表达式,函数类型的结果是void。

换句话说,你可以这么写:

return0();

并忽略“常规”代码中的返回值,因此也可以忽略lambda中的结果值。

关于重载歧义的问题,在这种情况下没有歧义(很容易构造一个 歧义的情况,例如另一个具有类似{{的参数的重载' 1}}但是是一个不同的接口,即不带参数,返回一个值。)

您必须详细阅读规范以获得准确的推理,但我认为最相关的部分是Sec 15.12,它描述了方法调用表达式,其中最有用的引用是{{3} },它处理选择最具体的重载:

  

非正式的直觉是,如果第一个方法处理的任何调用都可以传递给另一个没有编译时错误的调用,那么一个方法比另一个方法更具体。

您可以使用Supplier代替Supplier<Integer>(带有一点手工波浪的软糖),因为您可以简单地忽略返回值;您无法使用Runnable代替Runnable,因为它没有返回值。

因此,采用Supplier<Integer>的方法比采用Supplier<Integer>的方法更具体,因此这是调用的方法。

答案 1 :(得分:2)

如果您对lambda表达式感到困惑,请将它们替换为匿名类以便更好地理解(IntelliJ IDEA可以轻松地帮助您)。以下代码段完全有效:

Supplier<Integer> supplier2 = () -> return0()相当于:

Supplier<Integer> supplier2 = new Supplier<Integer>() {
    @Override
    public Integer get() {
        return return0();
    }
};

Runnable runnable2 = () -> return0()相当于:

Runnable runnable2 = new Runnable() {
    @Override
    public void run() {
        return0();
    }
};

答案 2 :(得分:0)

public static void main(String[] args) throws Exception
{
    Supplier<Integer> consumer2 = Trial::return0;
    Runnable runnable2 = Trial::return0;
    run(Trial::return0);
}

private static Integer return0() {
    return 0;
}

private static int run(Supplier<Integer> a)
{
    System.out.println("supplier");
    return a.get();
}

private static void run(Runnable r)
{
    System.out.println("runnable");
    r.run();
}

就方法重载而言,类Trial中的此代码打印“supplier”。