Lambdaexpression式和方法重载疑惑
好的,所以方法重载是一件坏事。 现在,这已经解决了,让我们假设我真的想重载像这样的方法:
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中,我想用lambdaexpression式来调用这些方法,我可以!
// 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节所述,在产生返回值的lambdaexpression式(“value-compatible”)和产生void
(“void-compatible”)的lambdaexpression式之间绝对没有含糊之处。
但是当然,JLS是非常复杂的,我们inheritance了20年的向后兼容的历史,并且有了新的东西:
包含隐式types的lambdaexpression式 (第15.27.1节 )或不精确的方法引用(第15.13.1节 )的某些参数expression式将被适用性testing忽略,因为在select目标types之前,它们的含义无法确定。
来自JLS§15.12.2
上述限制可能与JEP 101并未完全实现有关,可以在这里和这里看到。
题:
谁可以告诉我,JLS的哪些部分指定了编译时的模糊性(或者是编译器错误)?
奖金:为什么事情是这样决定的?
更新:
使用jdk1.8.0_40,上面的编译和工作正常
我想你在编译器中发现了这个bug:JDK-8029718 ( 或者在Eclipse中这个相似的:434642 )。
与JLS§15.12.2.1比较 确定潜在的适用方法 :
…
如果满足以下所有条件,则lambdaexpression式(第15.27节)可能与函数接口types(第9.8节)兼容:
目标types的函数types的arity与lambdaexpression式的arity相同。
如果目标types的函数types具有void返回值,那么lambda体是一个语句expression式(§14.8)或一个void兼容块(§15.27.2)。
如果目标types的函数types有一个(非void)返回types,那么lambda体是一个expression式或一个值兼容的块(§15.27.2)。
请注意“ void
兼容块”和“值兼容块”之间的明确区别。 虽然在某些情况下,块可能都是§15.27.2。 Lambda Body明确指出像() -> {}
这样的expression式是一个“ void
compatible block”,因为它通常没有返回值就完成了。 很显然, i -> {}
也是一个“ void
兼容块”。
并且根据上面引用的部分,lambda与不兼容值的块和具有(非void
)返回types的目标types的组合不是方法重载parsing的潜在候选者。 所以你的直觉是对的,在这里不应该有含糊之处。
模糊块的例子是
() -> { throw new RuntimeException(); } () -> { while (true); }
因为他们没有正常完成,但你的问题不是这样。
这个bug已经在JDK Bug System中报告过了: https : //bugs.openjdk.java.net/browse/JDK-8029718 。 正如你可以检查bug已经修复。 这个修复在这个方面同步了javac和spec。 现在,javac正确接受隐式lambdas的版本。 要获得此更新,您需要克隆javac 8回购 。
解决办法是分析lambda体,并确定它是无效的或值兼容。 要确定这个,你需要分析所有的返回语句。 让我们记住从上面已经引用的规范(15.27.2):
- 如果块中的每个return语句都具有returnforms,则块lambda体是void兼容的。
- 如果块lambda体不能正常完成( 14.21 ),则块的lambda体是值兼容的,并且块中的每个return语句都具有返回expression式的forms。
这意味着通过分析在lambda体中的返回,你可以知道lambda体是否是无效的兼容的,但是为了确定它的值是否兼容,你还需要对它做一个stream分析来确定它能正常完成( 14.21 )。
此修补程序还引入了一个新的编译器错误,例如,如果我们编译此代码,主体既不是空值也不是值兼容的情况:
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
我希望这有帮助。
让我们假设我们有方法和方法调用
void run(Function<Integer, Integer> f) run(i->i)
我们可以合法添加什么方法?
void run(BiFunction<Integer, Integer, Integer> f) void run(Supplier<Integer> f)
这里的参数arity是不同的,具体来说i->i
的i->
部分不符合BiFunction
的apply(T,U)
或Supplier
get()
的参数。 所以在这里,任何可能的歧义都是由参数arity定义的,而不是types,而不是返回。
我们不能添加什么方法?
void run(Function<Integer, String> f)
这会给编译器带来错误,因为run(..) and run(..) have the same erasure
。 因此,由于JVM不能支持两个具有相同名称和参数types的函数,所以不能编译。 所以编译器从来不需要解决这种情况下的歧义,因为它们是由于Javatypes系统中预先存在的规则而被明确禁止的。
所以这就给我们留下了一个参数为1的其他函数types。
void run(IntUnaryOperator f)
这里run(i->i)
对于Function
和IntUnaryOperator
都是有效的,但是由于reference to run is ambiguous
导致reference to run is ambiguous
因为两个函数都匹配这个lambda。 他们确实是这样做的,这里有一个错误是值得期待的。
interface X { void thing();} interface Y { String thing();} void run(Function<Y,String> f) void run(Consumer<X> f) run(i->i.thing())
在这里,由于含糊不清,这又不能编译。 如果不知道这个lambda中的i
的types,就不可能知道i.thing()
的types。 因此,我们认为这是模棱两可的,没有正确的编纂。
在你的例子中:
void run(Consumer<Integer> f) void run(Function<Integer,Integer> f) run(i->i)
这里我们知道这两个函数types都有一个Integer
参数,所以我们知道i->
中的i->
必须是Integer
。 所以我们知道它必须被调用run(Function)
。 但编译器不会尝试这样做。 这是编译器第一次做我们不期望的事情。
为什么不这样做? 我想说,因为这是一个非常具体的情况,推断这里的types需要我们还没有看到的其他任何上述情况的机制,因为在一般情况下,他们不能正确地推断types,select正确的方法。