![](/img/trans.png)
[英]Java CRTP and Wildcards: Code compiles in Eclipse but not `javac`
[英]Java generics code compiles with javac, fails with Eclipse Helios
我有以下測試類,它使用泛型來重載方法。 它在使用javac編譯時有效,無法在Eclipse Helios中編譯。 我的java版本是1.6.0_21。
我讀到的所有文章都表明Eclipse是對的,這段代碼不適用。 但是,使用javac和run編譯時,會選擇正確的方法。
這怎么可能?
謝謝!
import java.util.ArrayList;
public class Test {
public static void main (String [] args) {
Test t = new Test();
ArrayList<String> ss = new ArrayList<String>();
ss.add("hello");
ss.add("world");
ArrayList<Integer> is = new ArrayList<Integer>();
is.add(1);
is.add(2);
System.out.println(t.getFirst(ss));
System.out.println(t.getFirst(is));
}
public String getFirst (ArrayList<String> ss) {
return ss.get(0);
}
public Integer getFirst (ArrayList<Integer> ss) {
return ss.get(0);
}
}
在類中聲明具有覆蓋等效簽名(在下面定義)的兩個方法是編譯時錯誤。
如果m1是m2的子簽名或m2是m1的子簽名,則兩個方法簽名m1和m2是覆蓋等價的。
方法m1的簽名是方法m2的簽名的子簽名(如果有的話)
m2與m1具有相同的簽名,或
m1的簽名與m2的簽名擦除相同。
顯然,這些方法不是覆蓋等價的,因為ArrayList<String>
不是ArrayList
( ArrayList<Integer>
的擦除)。
因此宣布這些方法是合法的。 此外,方法調用表達式是有效的,因為通常只有一個方法匹配參數類型,因此通常是最具體的方法 。
編輯 :Yishai正確指出在這種情況下還有另一個限制。 Java語言規范,第8.4.8.3節寫道:
如果類型聲明T具有成員方法m1並且存在以T形式聲明的方法m2或T的超類型以使得滿足以下所有條件,則為編譯時錯誤:
- m1和m2具有相同的名稱。
- m2可從T訪問。
- m1的簽名不是m2簽名的子簽名(§8.4.2)。
- m1或某些方法m1覆蓋(直接或間接)具有與m2相同的擦除或某種方法m2覆蓋(直接或間接)。
附錄:關於實現,以及缺乏
與流行的概念相反,方法簽名中的泛型不會被刪除。 泛型以字節碼 (Java虛擬機的指令集)擦除。 方法簽名不是指令集的一部分; 它們被寫入源代碼中指定的類文件中。 (另外,也可以在運行時使用反射查詢此信息)。
想一想:如果類型參數完全從類文件中刪除,那么您選擇的IDE中的代碼完成如何顯示ArrayList.add(E)
采用E
類型的參數,而不是Object
(= E
的擦除)如果您沒有附加JDK源代碼? 當方法參數的靜態類型不是E
的子類型時,編譯器如何知道拋出編譯錯誤?
此代碼是正確的,如JLS 15.12.2.5中選擇最具體的方法所述 。
另外,考慮編碼到接口:
List<String> ss = new ArrayList<String>();
List<Integer> is = new ArrayList<Integer>();
// etc.
正如@McDowell所說,修改后的方法簽名出現在類文件中:
$ javap build/classes/Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
public static void main(java.lang.String[]);
public java.lang.String getFirst(java.util.ArrayList);
public java.lang.Integer getFirst(java.util.ArrayList);
}
請注意,這與@meriton關於類文件的觀察結果並不矛盾。 例如,此片段的輸出
Method[] methods = Test.class.getDeclaredMethods();
for (Method m : methods) {
System.out.println(Arrays.toString(m.getGenericParameterTypes()));
}
顯示main()
的形式參數,以及兩個泛型類型參數:
[class [Ljava.lang.String;]
[java.util.ArrayList<java.lang.String>]
[java.util.ArrayList<java.lang.Integer>]
在Eclipse Helios中適合我。 方法選擇在編譯時進行,編譯器有足夠的信息來執行此操作。
如果javac有錯誤就有可能。 Javac只是軟件,並且像任何其他軟件一樣容易出錯。
另外,Java語言規范非常復雜並且在某些地方有點模糊,所以它也可能在Eclipse人員和javac人之間的解釋上有所不同。
我首先在Eclipse支持渠道上詢問這個問題。 他們通常非常善於接受這些事情並解釋為什么他們認為他們是正確的或者承認他們錯了。
為了記錄,我認為Eclipse也就在這里。
你確定Eclipse也設置為使用Java 1.6嗎?
經過一番研究,我得到了答案:
上面指定的代碼不應該編譯。 ArrayList<String>
和ArrayList<Integer>
,在運行時仍然是ArrayList
。 但是你的代碼不起作用,因為返回類型。 如果為兩個方法設置相同的返回類型,javac將不會編譯...
我讀到Java 1.6中存在一個關於此錯誤的錯誤(已在Java 1.7中修復)。 所有關於返回類型......所以,您需要更改方法的簽名。
Eclipse和javac使用不同的編譯器。 Eclipse使用第三方編譯器將您的代碼轉換為Java VM的字節碼。 Javac使用Java編譯器而不是Sun發布。 因此,相同的代碼可能會產生略微不同的結果。 Netbeans我相信使用Sun的編譯器,所以也在那里檢查它。
要記住的一點是(所有?,當然有些例如NetBeans編輯器這樣做)靜態代碼分析工具不會將返回類型或修飾符(私有/公共等)視為方法簽名的一部分。
如果是這種情況,那么借助於類型擦除,兩個getFirst
方法都會獲得簽名getFirst(java.util.ArrayList)
,從而觸發名稱沖突......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.