[英]Java generics with wildcard compile in Eclipse, but not in javac
作為Java泛型在Eclipse中而不是在javac中進行編譯的后續措施,我發布了另一個代碼段,該代碼段在Eclipse中可以編譯並正常運行,但是會在javac中引發編譯錯誤。 (這可以防止使用Maven構建摘錄片段的項目。)
獨立的代碼段:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
}
public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
public static class Foo<T> implements Comparable<Foo<T>> {
@Override
public int compareTo(Foo<T> o) {
return 0;
}
}
}
javac中的編譯返回:
Main.java:11: <T>asSortedList(java.util.Collection<T>) in Main cannot be applied to (java.util.Set<Main.Foo<?>>)
List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
^
用Foo<String>
替換Foo<?>
,以上代碼段將在javac中編譯,這意味着問題與使用的通配符有關。 由於Eclipse編譯器應該更寬容,因此代碼段是否可能不是有效的Java?
(我使用javac 1.6.0_37和Eclipse Indigo,編譯器符合級別1.6)
( EDIT1:包括另一個示例,該示例已在EDIT2中刪除。)
EDIT2: 無可辯駁地暗示 ,比較Foo<A>
和Foo<B>
在概念上可能是錯誤的,並且受seh答案的啟發,可以將asSortedFooList
的工作編寫如下:
public static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
(在上面的方法定義中,用Foo<?>
替換了Comparable<T>
。)因此,javac和imho在概念上比較任何Foo<A>
和Foo<B>
似乎都是安全的。 但是,仍然無法編寫通用方法asSortedList
,如果它的類型參數使用通配符進行參數化,則該方法將返回通用集合的排序列表表示形式。 我試圖用S extends Comparable<S>
代替Foo<?>
來“欺騙” javac,並在asSortedFooList
S extends Comparable<S>
,但這是行不通的。
EDIT3:后來Rafaelle指出,由於沒有必要實現Comparable<Foo<T>>
,並且實現Comparable<Foo<?>>
提供了相同的功能,因此設計上存在缺陷,可以通過精細設計解決初始問題。
(最初的原因和好處是, Foo<T>
在某些方面可能並不關心其具體類型,但仍將其實例化為具體類型T
實例用於其他目的。該實例不必用於確定其他Foo
之間的順序,因為它可以在API的其他部分中使用。
具體示例:假設每個Foo都使用T
的不同類型參數實例化。 Foo<T>
每個實例都有一個int
類型的遞增ID,該ID在compareTo
方法的實現中使用。 現在,我們可以對這些類型不同的Foo
排序,而不必關心具體的類型T
(用Foo<?>
),並且仍然可以訪問具體的類型T
的實例,以便以后進行處理。)
在這種情況下,javac是正確的。 從概念上講,您的代碼無法運行,因為該集合可能包含Foo<A>
和Foo<B>
,它們無法相互比較。
您可能希望將集合Set<Foo<X>>
某個類型變量X的Set<Foo<X>>
; 不幸的是我們不能在方法體內引入類型變量。 僅在方法簽名中
<X> void test(){
Set<Foo<X>> setOfFoos = new HashSet<Foo<X>>();
List<Foo<X>> sortedListOfFoos = asSortedList(setOfFoos);
}
您可以通過類似的方法使其工作
<T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c)
class Foo<T> implements Comparable<Foo<?>>
對我來說,這是另一個javac
錯誤。 當您嘗試將Collection<Foo<?>>
發送到帶有簽名的方法時:
public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c)
編譯器注意到形式參數 T
有一個上限,因此請檢查約束條件是否被調用方接受。 type參數是參數化類型Foo<T>
的(通配符)實例化,因此如果Foo<?>
是 Comparable<Foo<?>>
,則測試將通過。 根據通用定義:
class Foo<T> implements Comparable<Foo<T>>
我會說這是真的,因此Eclipse再一次是正確的,而javac
有一個錯誤。 此Angelika Langer的條目從來沒有足夠的關聯。 另請參閱相關的JLS 。
您詢問它是否是類型安全的。 我的回答是,它是類型安全的 ,它表明您的設計存在缺陷。 考慮您對Comparable<T>
接口的虛擬實現,在其中我添加了另外兩個字段:
public static class Foo<T> implements Comparable<Foo<T>> {
private T pState;
private String state;
@Override
public int compareTo(Foo<T> other) {
return 0;
}
}
您始終返回0
,因此不會發現問題。 但是,當您嘗試使其有用時,您有兩種選擇:
T
成員進行比較 String
字段始終是一個String
,因此您不會真正受益於類型變量T
另一方面, T
沒有其他可用的類型信息,因此,在compareTo()
您只能處理一個普通對象,並且type參數同樣無用。 通過實現Comparable<Foo<?>>
您可以實現相同的確切功能。
我不知道這是否是一個問題,但這是一個(不是很好)答案:如果您犧牲了某些類型安全性,則可以編寫
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends Comparable> List<T> asSortedList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
它在eclipse和javac中均可使用。 我知道的唯一風險是,如果有人創建class Foo extends Comparable<Bazz>
您不會在編譯時檢測到它。 但是,如果有人創建Foo extends Comparable<Bazz>
,則殺死他/她。
我發現可以用javac編譯的解決方案,盡管我不滿意無法確切解釋其工作原理,對此我感到不滿意。 它需要引入一個中介功能:
public final class Main {
public static class Foo<T> implements Comparable<Foo<T>> {
@Override
public int compareTo(Foo<T> o) {
return 0;
}
}
public static <T extends Comparable<? super T>>
List<T> asSortedList(Collection<T> c) {
final List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
private static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
return asSortedList(c);
}
public static void main(String[] args) {
final Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
final List<Foo<?>> listOfFoos = asSortedFooList(setOfFoos);
}
}
我認為這是通過逐步執行通配符解析來實現的; asSortedFooList()
捕獲一種類型的已知為Foo
,不論Foo
的類型參數。 將該類型參數綁定到asSortedFooList()
,我們可以調用原始的asSortedList()
(需要做一個修改-請注意Comparable
的type參數的下限),需要將Foo
綁定為從Comparable
繼承的類型。
同樣,這是一個微不足道的,偶然的解釋。 我在這里回答的主要目的只是提供另一種到達目的地的方式。
如果您可以將通配符用法替換為確切的類型(可能是超類型),則代碼將起作用。 更換
List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
與
List<Foo<String>> sortedListOfFoos = Main.<Foo<String>>asSortedList(setOfFoos);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.