[英]Java 8 ambiguous method reference for generic class
下面的代碼在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和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
EDIT2:
javac 1.8.0_40下的編譯失敗並出現相同的錯誤
JLS,章節§15.12.2.5選擇最具體的方法是一個難以閱讀但包含一個有趣的總結:
非正式的直覺是,如果第一個方法處理的任何調用都可以傳遞給另一個沒有編譯時類型錯誤的調用,那么一個方法比另一個方法更具體。
我們可以通過以下示例輕松地反駁您的情況:
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(顯着)的最具體方法,而是由於改進的類型推斷而適用性。 一旦兩種方法都適用,調用就不明確,因為這兩種方法都不比另一種方法更具體。
在解決在多種方法適用的情況下使用哪種方法時, “......通常,調用的參數類型不能作為分析的輸入。” 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已經確定了泛型(並且它將在某一天),則在此示例中,一個方法簽名並不比另一個更具體。 堵塞漏洞......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.