对方法的引用是模糊的,具有泛型,奇怪的行为

时间:2011-10-30 06:18:54

标签: java generics overloading

考虑以下情况:

class C {
  void m(Class<?> c1, Class<?> c2) {}
  <S, U extends S> void m(S s, U u) {}
}

class x {{
  final Class<Integer> cInteger = Integer.class;
  final Class<?> cSomething = null;
  final C c = new C();
  c.m(cInteger,  cInteger);
  c.m(cSomething,  cSomething); // *
}}

使用Oracle的javac,版本1.7.0_01(以及1.7.0,以及OpenJDK的Java 7和Java 6编译器),我在标有// *的行上收到错误:

  

错误:对m的引用是不明确的,C中的方法m(Class,Class)和C中的方法m(S,U)匹配

我无法理解为什么会发生这种情况:当参数的静态类型为Class<Integer>时,编译器能够告诉调用哪个方法,但是Class<?>存在问题。

IntelliJ的代码分析表明这是可以的,以及JRockit(或Sun的Java 6)编译器。

所以,这里显然存在一个错误,无论是那些说它是对的软件,还是那些说它是错误的软件。

请注意,如果我删除U的边界(即,如果我将m声明为<S, U> void M(S s, U u) {},它将编译时没有错误。此外,还有一个原始类型的调用(即,Class x = null; m(x, x))编译也很好。

那么,根据Java规范,这段代码是有效还是无效?

感谢。

2 个答案:

答案 0 :(得分:2)

你确定吗?我的测试是第一个m(cInteger, cInteger)失败,第二个m(cSomething, cSomething)没问题。原始m(x,x)也无法编译。

(1)m(cInteger,cInteger)失败

m()匹配参数;对于 m2 ,推理产生S=U=Class<Integer>

现在的问题是, m1 m2 更具体吗?遵循15.12.2.5中的程序,它是。如果是这样的话,就没有歧义;应该选择 m1 作为最具体的方法,并且应该编译调用。

但是,这违反了规范给出的非正式要求:如果 m1 m2 更具体,则由 m1 可以传递给 m2 而不会出现编译时类型错误。例如, m1 可以接受参数(Class<Integer>,Class<String>),其中 m2 不能(由于不完美的类型推理) 过程)。

Javac显然坚持非正式观念;这可能是因为正式规范有一个错误 - 它应该在更具体的关系的定义中包含捕获转换(稍后解释);那么 m1 并不比 m2 更具体,因此调用m(cInteger,cInteger)是不明确的。

如果另一个编译器严格遵守正式规范,继承规范的错误并不是它的错误。

(2)m(x, x)失败

与(1)相同的原因;两种方法都匹配,但两者都没有比另一种更具体。

m1 通过方法调用转换进行匹配,这允许未经检查的转化从原始ClassClass<?>。在推断S=U=Class

之后 m2 匹配

(3)m(cSomething, cSomething)编译

这是因为 m1 是唯一适用的,因此没有歧义。

m2 不适用,让我们看看为什么。

首先,参数的类型并不完全(Class<?>,Class<?>) - 捕获转换首先应用于它们。 (同样,规范还不清楚(见第15章),但我非常确定这是很好理解的情况;任何表达式的类型都应用于捕获转换)

所以参数类型是(Class<X1>,Class<X2>),有两个新的类型变量。这里有另一个搞砸了,更精确的转换是(Class<X1>,Class<X1>),不幸的是,捕获转换应用了两次,独立地,两种不同的类型。

m1 可轻松匹配参数类型。但由于不完美的类型推断过程, m2 不匹配。该过程首先产生S=Class<X1>, U=Class<X2>,然后检查,类型变量的边界,U extends S失败。

(4)删除U的绑定

现在(1),(2),(3)全部编译。因为没有U extends S,推理就会通过。

对于(1)和(2), m1 现在比 m2 更具体,不再含糊不清。

对于(3), m2 现在匹配;但后来被更具体的 m1

所遮蔽

答案 1 :(得分:0)

也许是因为如果您在方法调用中说<S>类型为Class<?>,那么<U>也是Class<?>,并且无法确定应该使用哪种方法。< / p>

<S, U extends S> void m(S s, U u) {} //S=U=Class<?> => void m(Class<?> s, Class<?> u) {} //same signature

如果您说<S><U>类型为Class<Integer>,那么它必须是第二种使用泛型<S><U>的方法。

<S, U extend S> void m(S s, U u) {} //S=U=Class<Integer> => void m(Class<Integer> s, Class<Integer> u) {} //Ok

问题

<S,U>声明指定您打算使用两种不同的不相关类型。