为什么javac不能推断用作参数的函数的generics类型参数?

在下面的示例中,为什么编译器能够在Foo.create()中推断第一次调用Foo.create()的generics参数,但在第二次调用Foo.create()无法这样做? 我正在使用Java 6。

 public class Nonsense { public static class Bar { private static void func(Foo arg) { } } public static class Foo { public static  Foo create() { return new Foo(); } private static void test() { Foo foo2 = Foo.create(); // compiles Bar.func(Foo.create()); // won't compile Bar.func(Foo.create()); // fixes the prev line } } } 

(编译错误是Nonsense.Bar类型中的方法func(Nonsense.Foo)不适用于参数(Nonsense.Foo) )。

注意:我理解编译器错误可以通过test()中的第三行修复 – 我很好奇是否存在阻止编译器推断类型的特定限制。 在我看来,这里有足够的背景。

从Java 7开始,方法重载解析必须在您调用的方法中的任何目标类型信息被考虑之前进行,以尝试在func的声明中推断出类型变量T 这看起来很愚蠢,因为我们都可以看到在这种情况下只有一个名为func方法,但是,它是由JLS强制执行的,是来自Java 7的javac的行为。

编译过程如下:首先,编译器看到它正在编译对名为func的类Bar的静态方法的调用。 要执行重载解析,它必须找出调用该方法的参数。 尽管这是一个微不足道的案例,它仍然必须这样做,并且在它完成之前它没有关于可用于帮助它的方法的forms参数的任何信息。 实际参数包含一个参数,一个对Foo.create()的调用,它被声明为返回Foo 。 同样,没有来自目标方法的标准,它只能推断出返回类型是Foo的擦除,它是Foo ,它就是这样做的。

方法重载解析然后失败,因为func任何重载都不与Foo的实际参数兼容,并且会发出错误。

这当然是非常不幸的,因为我们都可以看到,如果信息可以简单地从另一个方向流动,从方法调用的目标回到呼叫站点,则可以很容易地推断出类型,并且不会出现错误。 实际上,Java 8中的编译器可以做到这一点,并且确实如此。 正如另一个答案所述,这种更丰富的类型推理对于在Java 8中添加的lambda以及为利用lambda所做的Java API的扩展非常有用。

您可以从前面的链接下载带有JSR 335 lambdas的Java 8预发布版本。 它编译问题中的代码,没有任何警告或错误。

从上下文中推断类型太复杂了。 主要障碍可能是方法重载。 例如, f(g(x)) ,要确定应用哪个f() ,我们需要知道g(x)的类型; 然而,可能需要从f()的参数类型推断出g(x)的类型。 在某些语言中,方法只是禁止重载,因此类型推断可以更容易。

在Java 8中,您的示例进行编译。 由于lambda表达式用例,Java团队更有动力扩展类型推断。 这不是一件容易的事。

java 7的java语言规范包含40个页面,仅用于spec方法调用表达式(第15.12节)