2013-02-01 49 views
11

在下面的示例中,为什么编译器能够推断Foo.test()中第一次调用Foo.create()的通用参数,但第二次不能这样做?我使用Java 6为什么javac不能推断用作参数的函数的泛型类型参数?

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

我不确定你期望得到什么答案,除了“它不够聪明”。 –

+0

@路易斯 - 可以想象,这是不可能够聪明,但我还没有弄清楚为什么。 – bacar

+0

@bacar:它可能足够聪明,但它不是。 –

回答

14

从Java 7中,方法重载具有您所呼叫的方法的任何目标类型信息之前进行可以考虑试图推断在func的声明中的类型变量T。这看起来很愚蠢,因为我们都可以看到,在这种情况下,只有一个方法名为func,但是,它由JLS强制执行,并且是来自Java 7的javac的行为。

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

方法过载分辨率然后失败,因为func的过载没有一个与Foo<Object>的实际参数兼容,并且发生此错误。

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

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

3

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

在Java 8中编译您的示例。由于lambda表达式用例,Java团队更愿意扩大类型推断。这不是一件容易的事。

对Java 7 Java语言规范包含40页正好符合规范的方法调用表达式(第15.12)

相关问题