11

在下面的示例中,为什么编译器能够推断出第一次调用Foo.create()in的通用参数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() 中的第三行修复 - 我很好奇是否存在阻止编译器推断类型的特定限制。在我看来,这里有足够的上下文。

4

2 回答 2

15

从 Java 7 开始,必须先进行方法重载解析,然后才能考虑您正在调用的方法中的任何目标类型信息,以尝试T推断func. 这似乎很愚蠢,因为我们都可以看到,在这种情况下,只有一个名为 的方法func,但是,它是 JLS 规定的,并且是javacJava 7 的行为。

编译过程如下:首先,编译器看到它正在编译对名为 func 的类 Bar 的静态方法的调用。要执行重载决议,它必须找出调用该方法的参数。尽管这是一个微不足道的情况,但它仍然必须这样做,并且在它这样做之前,它没有任何关于可用于帮助它的方法的形式参数的信息。实际参数由一个参数组成,一个调用Foo.create()被声明为返回Foo<T>。同样,在目标方法没有标准的情况下,它只能推断出返回类型是 is 的擦除Foo<T>Foo<Object>并且确实如此。

然后方法重载解析失败,因为没有一个重载func与 的实际参数兼容Foo<Object>,并且会为此发出错误。

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

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

于 2013-02-01T17:57:41.607 回答
3

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

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

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

于 2013-02-01T17:22:36.050 回答