Java Lambda表达式和方法重载

Java Lambda表达式和方法重载,java,lambda,java-8,overloading,jls,Java,Lambda,Java 8,Overloading,Jls,好的,所以方法重载是一件坏事™. 现在已经解决了这个问题,让我们假设我真的想重载一个如下的方法: static void run(Consumer<Integer> consumer) { System.out.println("consumer"); } static void run(Function<Integer, Integer> function) { System.out.println("function&q

好的,所以方法重载是一件坏事™. 现在已经解决了这个问题,让我们假设我真的想重载一个如下的方法:

static void run(Consumer<Integer> consumer) {
    System.out.println("consumer");
}

static void run(Function<Integer, Integer> function) {
    System.out.println("function");
}
既然编译器应该能够推断出
整数
,那么我为什么不把
整数
放在一边呢

// Consumer
run(i -> {});

// Function
run(i -> 1);
但这并没有编译。编译器(javac,jdk1.8.0_05)不喜欢这样:

Test.java:63:error:run的引用不明确
运行(i->{});
^
这两种方法都在测试和测试中运行(使用者)
测试匹配中的方法运行(函数)
直觉上,这对我来说毫无意义。产生返回值(“值兼容”)的lambda表达式与产生
void
(“void兼容”)的lambda表达式之间绝对没有歧义,如中所述

但当然,JLS是深刻而复杂的,我们继承了20年的向后兼容性历史,并且有如下新事物:

适用性测试将忽略某些包含隐式类型lambda表达式()或不精确方法引用()的参数表达式,因为在选择目标类型之前无法确定其含义

上述限制可能与未完全实现的事实有关,如图所示和所示

问题: 谁能确切地告诉我JLS的哪些部分指定了这种编译时模糊性(或者它是编译器错误)

奖金:为什么事情是这样决定的

更新: 使用jdk1.8.0_40,以上代码可以很好地编译和工作

我想您已经找到了()

与之相比:

  • 如果满足以下所有条件,lambda表达式(§15.27)可能与函数接口类型(§9.8)兼容:

    • 目标类型的函数类型的arity与lambda表达式的arity相同

    • 如果目标类型的函数类型具有void返回,则lambda主体是语句表达式(§14.8)或void兼容块(§15.27.2)

    • 如果目标类型的函数类型具有(非void)返回类型,则lambda主体是表达式或值兼容块(§15.27.2)

请注意“
void
兼容块”和“值兼容块”之间的明确区别。虽然在某些情况下块可能同时是两个,但该部分清楚地指出,像
()->{}
这样的表达式是“
void
兼容块”,因为它正常完成而不返回值。显然,
i->{}
也是一个“
void
兼容块”

并且根据上面引用的部分,lambda与值不兼容的块以及目标类型与(非
void
)返回类型的组合不是方法重载解析的潜在候选。所以你的直觉是对的,这里不应该有歧义

不明确块的示例包括

() -> { throw new RuntimeException(); }
() -> { while (true); }

因为它们不能正常完成,但您的问题不是这样。

假设我们有方法和方法调用

void run(Function<Integer, Integer> f)

run(i->i)
这会导致编译器错误,因为
run(..)和run(..)具有相同的擦除。因此,由于JVM不能支持具有相同名称和参数类型的两个函数,因此无法编译这两个函数。因此,编译器永远不必解决此类场景中的歧义,因为由于Java类型系统中预先存在的规则,它们被明确禁止

这就给我们留下了参数arity为1的其他函数类型

void run(IntUnaryOperator f)
此处
run(i->i)
函数和
IntUnaryOperator
都有效,但这将拒绝编译,因为运行的
引用不明确
,因为两个函数都匹配此lambda。事实上,他们确实是这样做的,这里的错误是意料之中的

interface X { void thing();}
interface Y { String thing();}

void run(Function<Y,String> f)
void run(Consumer<X> f)
run(i->i.thing())
这里我们知道两种函数类型都有一个
Integer
参数,因此我们知道
i->
中的
i
必须是
Integer
。所以我们知道必须调用
run(Function)
。但是编译器没有尝试这样做。这是编译器第一次执行我们不期望的操作


为什么不这样做呢?我这么说是因为这是一个非常特殊的情况,在这里推断类型需要我们在上述任何其他情况下都没有见过的机制,因为在一般情况下,他们无法正确推断类型并选择正确的方法。

JDK错误系统中已经报告了此错误:。正如你所能检查的那样,这个bug已经被修复了。此修复程序在这方面将javac与规范同步。现在javac正确地接受了带有隐式lambdas的版本。要获取此更新,您需要克隆

修复程序所做的是分析lambda主体,并确定其是否为void或值兼容。要确定这一点,您需要分析所有返回语句。让我们记住,上述规范(15.27.2)中已经提到:

  • 如果块中的每个返回语句 该块具有返回表单
  • 如果无法完成,则块lambda主体与值兼容 通常()和块中的每个return语句都具有 表单返回表达式
这意味着,通过分析lambda主体中的返回,您可以知道lambda主体是否与void兼容,但要确定其是否与值兼容,您还需要对其进行流分析,以确定它是否可以正常完成()

对于正文既不是void也不是值兼容的情况,此修复程序还引入了一个新的编译器错误,例如,如果我们编译以下代码:

class Test {
    interface I {
        String f(String x);
    }

    static void foo(I i) {}

    void m() {
        foo((x) -> {
            if (x == null) {
                return;
            } else {
                return x;
            }
        });
    }
}
编译器将给出以下输出:

Test.java:9: error: lambda body is neither value nor void compatible
    foo((x) -> {
        ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

我希望这能有所帮助。

@SyamS:
I
Consumer.accept()
Function.apply()的第一个(也是唯一一个)参数。这本身可能是含糊不清的
void run(IntUnaryOperator f)
interface X { void thing();}
interface Y { String thing();}

void run(Function<Y,String> f)
void run(Consumer<X> f)
run(i->i.thing())
void run(Consumer<Integer> f)
void run(Function<Integer,Integer> f)
run(i->i)
class Test {
    interface I {
        String f(String x);
    }

    static void foo(I i) {}

    void m() {
        foo((x) -> {
            if (x == null) {
                return;
            } else {
                return x;
            }
        });
    }
}
Test.java:9: error: lambda body is neither value nor void compatible
    foo((x) -> {
        ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error