好的,方法重载是件坏事™。既然这已经解决了,现在假设我实际上想像这样重载一个方法:
static void run(Consumer<Integer> consumer) {
System.out.println("consumer");
}
static void run(Function<Integer, Integer> function) {
System.out.println("function");
}
在Java 7中,我可以使用非歧义的匿名类作为参数轻松调用它们:
run(new Consumer<Integer>() {
public void accept(Integer integer) {}
});
run(new Function<Integer, Integer>() {
public Integer apply(Integer o) { return 1; }
});
现在在Java 8中,我想当然地使用lambda表达式来调用那些方法,我可以做到!
// Consumer
run((Integer i) -> {});
// Function
run((Integer i) -> 1);
既然编译器可以推断出Integer
,那我为什么不把Integer
省略掉呢?
// Consumer
run(i -> {});
// Function
run(i -> 1);
但是这段代码无法编译。编译器(javac,jdk1.8.0_05)不喜欢它:
Test.java:63: error: reference to run is ambiguous
run(i -> {});
^
both method run(Consumer<Integer>) in Test and
method run(Function<Integer,Integer>) in Test match
对我来说,直觉上这是没有意义的。正如JLS §15.27所述,返回值为“value-compatible”的lambda表达式和返回值为“void-compatible”的lambda表达式之间绝对没有歧义。
但当然,JLS非常深奥复杂,我们继承了20年的向后兼容历史,而且还有新的东西,比如:
特定的参数表达式中包含有隐式类型的lambda表达式(§15.27.1)或不精确的方法引用(§15.13.1),这些表达式在适用性测试中被忽略,因为它们的意义只能在选择目标类型之后确定。 来自JLS §15.12.2
上述限制可能与JEP 101没有完全实现有关,可以在这里和这里看到。
问题:
谁能告诉我JLS的哪些部分规定了这种编译时的歧义(或者是编译器的bug)?
奖励:为什么会决定这样做?
更新:
使用jdk1.8.0_40,上述代码可以编译并正常工作。
i
是传递给Consumer.accept()
或Function.apply()
的第一个(也是唯一的)参数。这本身可能会产生歧义。但是,考虑到一个 lambda 表达式评估为“值兼容”类型(Function
),而另一个评估为“void 兼容”类型(Consumer
),我直觉上认为没有歧义。 - Lukas Ederbeta 102
及更早版本)中可以工作。 - Holgeri -> {}
永远无法评估为Function
,因为它是“void-compatible”的。i -> 1
永远无法评估为Consumer
,因为它是“value-compatible”的。在每次调用中,我认为只有一个重载方法是适用的。正如@jacobhyphenated所指出的那样,通过显式指定相同的函数参数类型(Integer i)
可以解决歧义。 - Lukas Ederrun((Consumer<Integer>) (Integer i) -> {1});
,它将无法编译。这让我相信这一定是编译器的错误,因为这两个lambda之间确实没有歧义。 - jacobhyphenated