下面的代码在Java 7中编译并运行正常,但无法在Java 1.8.0 u25中编译:
public class GenericTest {
public static class GenericClass<T> {
T value;
public GenericClass(T value) {
this.value = value;
}
}
public static class SecondGenericClass<T> {
T value;
public SecondGenericClass(T value) {
this.value = value;
}
}
public static<T >void verifyThat(SecondGenericClass<T> actual, GenericClass<T> matcher) {
}
public static<T >void verifyThat(T actual, GenericClass<T> matcher) {
}
@Test
public void testName() throws Exception {
verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
}
}
Java 8中的错误消息如下所示:
Error:(33, 9) java: reference to verifyThat is ambiguous
both method <T>verifyThat(com.sabre.ssse.core.dsl.GenericTest.SecondGenericClass<T>,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest and method <T>verifyThat(T,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest match
我已经回顾了以下所有变化:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2
https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2
但我没有注意到这种行为的确切原因。
修改:
只是回答一些评论,很明显Java 7和Java 8中的编译器都能够处理这样的调用(签名类似于编译时类型擦除后留下的签名:
public static void verifyThat(SecondGenericClass actual, GenericClass matcher) {
}
public static void verifyThat(Object actual, GenericClass matcher) {
}
@Test
public void testName() throws Exception {
verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
}
为这两种通用方法生成并删除的字节码是相同的,如下所示:
public static verifyThat(Lcom/sabre/ssse/core/dsl/GenericTest$SecondGenericClass;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V
public static verifyThat(Ljava/lang/Object;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V
编辑2:
javac 1.8.0_40下的编译失败并出现相同的错误
答案 0 :(得分:11)
JLS, chapter §15.12.2.5 Choosing the Most Specific Method是一个难以阅读的内容,但包含一个有趣的摘要:
非正式的直觉是,如果第一个方法处理的任何调用都可以传递给另一个没有编译时类型错误的调用,那么一个方法比另一个方法更具体。
我们可以通过以下示例轻松反驳您的情况:
GenericTest.<String>verifyThat( // invokes the first method
new SecondGenericClass<>(""), new GenericClass<>(""));
GenericTest.<SecondGenericClass<String>>verifyThat( // invokes the second
new SecondGenericClass<>(""), new GenericClass<>(null));
所以这里没有最具体的方法,但是,如示例所示,可以使用使另一个方法不适用的参数调用任一方法。
在Java 7中,由于(编译器)尝试查找类型参数以使更多方法适用(也称为有限类型推断),因此更容易使方法不适用。表达式new SecondGenericClass<>("")
从其参数SecondGenericClass<String>
中推断出类型""
,就是这样。因此,对于调用verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""))
,参数的类型为SecondGenericClass<String>
和GenericClass<String>
,这使得方法<T> void verifyThat(T,GenericClass<T>)
不适用。
请注意,有一个模糊调用的例子表明了Java 7(甚至Java 6)下的歧义:verifyThat(null, null);
在使用javac
时会引发编译错误。
但Java 8有Invocation Applicability Inference(我们对JLS 7有一个区别,这是一个全新的章节......),它允许编译器选择使候选方法适用的类型参数(通过嵌套调用工作)。你可以为你的特殊情况找到这样的类型参数,你甚至可以找到一个适合两者的类型参数,
GenericTest.<Object>verifyThat(new SecondGenericClass<>(""), new GenericClass<>(""));
毫不含糊(在Java 8中),即使Eclipse也同意这一点。相比之下,调用
verifyThat(new SecondGenericClass<>(""), new GenericClass<String>(""));
具体到足以使第二个方法不适用并调用第一个方法,这给了我们一个关于Java 7中发生了什么的提示,其中new GenericClass<>("")
的类型固定为GenericClass<String>
就像new GenericClass<String>("")
。
最重要的是,它不是选择从Java 7更改为Java 8(显着)的最具体方法,而是由于改进的类型推断而适用性。一旦两种方法都适用,调用就不明确,因为这两种方法都不比另一种方法更具体。
答案 1 :(得分:1)
在解决在多种方法适用的情况下使用哪种方法时,&#34; ...调用参数的类型通常不能作为分析的输入。 #34; Java 7规范缺少此资格。
如果您在T
的第二个定义中将verifyThat
替换为SecondGenericClass
,则签名匹配。
换句话说,想象一下试图像这样调用verifyThat
的第二个定义:
SecondGenericClass<String> t = new SecondGenericClass<String>("foo");
GenericTest.verifyThat(t, new GenericClass<String>("bar"));
在运行时,由于变量类型verifyThat
是t
和SecondGenericClass<T>
的有效替代,因此无法确定要调用的T
版本
请注意,如果Java已经确定了泛型(并且它将在某一天),则在此示例中,一个方法签名并不比另一个更具体。堵塞漏洞......