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->ii->部分不符合BiFunctionapply(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)对于FunctionIntUnaryOperator都是有效的,但是由于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正确的方法。