[英]Java: Generic method overloading ambiguity
請考慮以下代碼:
public class Converter {
public <K> MyContainer<K> pack(K key, String[] values) {
return new MyContainer<>(key);
}
public MyContainer<IntWrapper> pack(int key, String[] values) {
return new MyContainer<>(new IntWrapper(key));
}
public static final class MyContainer<T> {
public MyContainer(T object) { }
}
public static final class IntWrapper {
public IntWrapper(int i) { }
}
public static void main(String[] args) {
Converter converter = new Converter();
MyContainer<IntWrapper> test = converter.pack(1, new String[]{"Test", "Test2"});
}
}
上面的代碼編譯沒有問題。 但是,如果在pack
簽名和new String[]{"Test", "Test2"}
到"Test", "Test2"
中將String[]
更改為String...
,編譯器會抱怨對converter.pack
的調用。曖昧。
現在,我可以理解為什么它可以被認為是模糊的(因為int
可以自動裝入Integer
,從而匹配K
的條件或缺乏條件)。 但是,我無法理解的是,如果你使用String[]
而不是String...
,那么歧義就不存在了。
有人可以解釋這個奇怪的行為嗎?
你的第一個案例非常簡單。 以下方法:
public MyContainer<IntWrapper> pack(int key, Object[] values)
是參數的完全匹配 - (1, String[])
。 來自JLS第15.12.2節 :
第一階段(§15.12.2.2)執行重載解析而不允許裝箱或拆箱轉換
現在,將這些參數傳遞給第二個方法時沒有涉及裝箱。 As Object[]
是String[]
的超類型。 並且,即使在Java 5之前,為Object[]
參數傳遞String[]
參數也是一個有效的調用。
在你的第二種情況下,因為你已經使用了var-args,所以使用var-args,裝箱或拆箱來完成方法重載分辨率,按照JLS部分中解釋的第3階段:
第三階段(§15.12.2.4)允許重載與變量arity方法,裝箱和拆箱相結合。
注意,由於使用var-args ,第二階段不適用於此:
第二階段(§15.12.2.3)執行重載解析,同時允許裝箱和拆箱,但仍然排除使用變量arity方法調用。
現在發生的事情是
編譯器沒有正確地推斷出類型參數*
(實際上,它正確地推斷它,因為類型參數被用作形式參數,請參閱本答案末尾的更新)。 所以,對於你的方法調用:
MyContainer<IntWrapper> test = converter.pack(1, "Test", "Test2");
編譯器應該從LHS推斷泛型方法中的K
類型為IntWrapper
。 但似乎它推斷K
是一個Integer
類型,因為你的方法現在同樣適用於這個方法調用,因為它們都需要var-args
或boxing
。
但是,如果該方法的結果未分配給某個引用,那么我可以理解編譯器無法推斷出正確的類型,因為在這種情況下,給出歧義錯誤是完全可以接受的:
converter.pack(1, "Test", "Test2");
可能是我猜,只是為了保持一致性,第一種情況也明顯不明確。 但是,我並不確定,因為我沒有從JLS或其他官方參考資料中找到任何有關此問題的可信來源。 我將繼續搜索,如果我找到一個,將更新答案。
如果更改方法調用以提供顯式類型信息:
MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");
現在,類型K
將被推斷為IntWrapper
,但由於1
不能轉換為IntWrapper
,因此該方法將被丟棄,並且第二個方法將被調用並且它將完美地工作。
坦率地說,我真的不知道這里發生了什么。 我希望編譯器在第一種情況下從方法調用上下文中推斷出類型參數,因為它適用於以下問題:
public static <T> HashSet<T> create(int size) {
return new HashSet<T>(size);
}
// Type inferred as `Integer`, from LHS.
HashSet<Integer> hi = create(10);
但是,在這種情況下它並沒有這樣做。 所以這可能是一個錯誤。
*或者,當類型未作為參數傳遞時,我可能不完全理解編譯器如何推斷類型參數。 因此,為了更多地了解這一點,我試圖通過--JLS§15.12.2.7和JLS§15.12.2.8 ,這是關於編譯器如何推斷類型參數,但這完全超出了我的頭腦。
所以,現在你必須忍受它,並使用替代方法(提供顯式類型參數)。
正如@ zhong.j.yu。在評論中最后解釋的那樣,編譯器僅在第15.12.2.8節中對類型推斷應用,當它無法根據15.12.2.7部分推斷它時。 但是在這里,它可以從傳遞的參數推斷出類型為Integer
,因為類型參數顯然是方法中的格式參數。
所以,是的編譯器正確地將類型推斷為Integer
,因此歧義是有效的。 現在我覺得這個答案已經完成了。
在這里,您可以看出以下兩種方法的區別:方法1:
public MyContainer<IntWrapper> pack(int key, Object[] values) {
return new MyContainer<>(new IntWrapper(""));
}
方法2:
public MyContainer<IntWrapper> pack(int key, Object ... values) {
return new MyContainer<>(new IntWrapper(""));
}
方法2也一樣好
public MyContainer<IntWrapper> pack(Object ... values) {
return new MyContainer<>(new IntWrapper(""));
}
這就是為什么你會有歧義的原因..
編輯是的我想說它們在編譯時是一樣的。 使用變量參數的全部目的是使用戶能夠在他/她不確定給定類型的參數數量時定義方法。
因此,如果您使用對象作為變量參數,您只需說編譯器我不確定將發送多少個對象,另一方面,您說,“我傳遞的是整數和未知數量的對象”。 對於編譯器,整數也是一個對象。
如果要檢查有效性,請嘗試傳遞一個整數作為第一個參數,然后傳遞String的變量參數。 你會看到差異。
例如:
public class Converter {
public static void a(int x, String... y) {
}
public static void a(String... y) {
}
public static void main(String[] args) {
a(1, "2", "3");
}
}
另外,請不要互換使用數組和變量args,它們有一些不同的用途。
當您使用varargs時,該方法不會期望一個數組,但可以使用相同類型的不同參數,這些參數可以以索引方式訪問。
在這種情況下
(1) m(K, String[])
(2) m(int, String[])
m(1, new String[]{..});
m(1)滿足15.12.2.3。 階段2:確定方法調用轉換適用的匹配Arity方法
m(2)滿足15.12.2.2。 階段1:確定子類型適用的匹配Arity方法
編譯器在第1階段停止; 它發現m(2)是該階段唯一適用的方法,因此選擇m(2)。
在var arg的情況下
(3) m(K, String...)
(4) m(int, String...)
m(1, str1, str2);
m(3)和m(4)都滿足15.12.2.4。 階段3:確定適用的變量Arity方法 。 兩者都沒有比另一個更具體,因此含糊不清。
我們可以將適用的方法分為4組:
規范合並了第3組和第4組,並在第3階段對它們進行處理。因此不一致。
他們為什么這樣做? Maye他們只是厭倦了它。
另一個批評是,不應該有所有這些階段,因為程序員不會這樣思考。 我們應該簡單地找到所有適用的方法,然后選擇最具體的方法(有一些機制來避免裝箱/拆箱)
首先,這只是一些第一線索......可以編輯更多。
編譯器始終搜索並選擇可用的最具體方法。 雖然閱讀有點笨拙,但它在JLS 15.12.2.5中都有規定。 因此,通過電話
converter.pack(1,“Test”,“Test2”)
對於編譯器來說, 1
是否應該解散為K
或int
是不可確定的。 換句話說,K可以應用於任何類型,因此它與int / Integer處於同一級別。
不同之處在於參數的數量和類型。 考慮new String[]{"Test", "Test2"}
是一個數組,而"Test", "Test2"
是String類型的兩個參數!
converter.pack(1); //模棱兩可,編譯錯誤
converter.pack(1,null); //調用方法2,編譯器警告
converter.pack(1,new String [] {}); //調用方法2,編譯器警告
converter.pack(1,new Object()); //模糊,編譯錯誤
converter.pack(1,new Object [] {}); //調用方法2,沒有警告
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.