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

时间:2013-02-01 17:16:49

标签: java generics type-inference

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

public class Nonsense {
    public static class Bar {
        private static void func(Foo<String> arg) { }
    }

    public static class Foo<T> {

        public static <T> Foo<T> create() {
            return new Foo<T>();
        }

        private static void test() {
            Foo<String> foo2 = Foo.create(); // compiles
            Bar.func(Foo.create());          // won't compile
            Bar.func(Foo.<String>create());  // fixes the prev line
        }
    }
}

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

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

2 个答案:

答案 0 :(得分:15)

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

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

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

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

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

答案 1 :(得分:3)

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

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

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