請參閱此常見問題以獲取更多資訊:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1
以下是此常見問題解答中的一些要點。
範例(通用類型):
類 Pair<X,Y> {
首先是私有X;
私人Y第二; 公共對(X a1,Y a2){
首先 = a1;
第二個=a2;
}
public X getFirst() { 先回傳; }
public Y getSecond() { 回傳第二個; }
公無效setFirst(X arg){第一= arg; }
公無效setSecond(Y arg){第二= arg; }
}
班級 一對 有兩個型別參數 X 和 是 。 當泛型類型時,它們被類型參數替換 一對 被實例化。例如,在聲明中 對<字串,日期> 類型參數 X 被類型參數替換 細繩 和 是 被替換為 日期 。
標識符的範圍 X 和 是 是 類別的整個定義。 在此範圍內,兩種型別參數 X 和 是 像類型一樣使用(有一些限制)。在上面的範例中,類型參數用作實例方法的參數和傳回類型以及實例欄位的類型。
類型參數可以用邊界來宣告。邊界允許存取類型參數所代表的未知類型的方法。在我們的範例中,我們不呼叫任何未知類型的方法 X 和 是 。因此,這兩個型別參數是無界的。
範例(具體參數化類型):
公共無效列印對( 一對<串,長> 一對) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} 一對<串,長> 限制=新 一對<串,長> (“最大”,1024L);
列印對(限制);
範例(通配符參數化類型):
公共無效列印對( 對<?,?> 一對) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} 對<?,?> 限制=新 一對<串,長> (“最大”,1024L);
列印對(限制);
泛型類型的所有實例化都共用相同的執行時間類型表示,即原始類型的表示。例如,泛型類型的實例化 清單 , 例如 列表<日期> , 列表<字串> , 列表<長> 等在編譯時有不同的靜態類型,但動態類型相同 清單 在運行時。
演員陣容由兩部分組成:
- 編譯器在編譯時執行的靜態型別檢查
- 虛擬機器在運行時執行的動態類型檢查。
靜態部分對無法成功的無意義的轉換進行排序,例如來自 細繩 那 日期 或來自 列表<字串> 那 列表<日期> 。
動態部分使用運行時類型資訊並在運行時執行類型檢查。 它提出了一個 類別轉換異常 如果物件的動態類型不是強制轉換的目標類型(或目標類型的子類型)。具有動態部分的演員的例子是 目的 那 細繩 或來自 目的 那 列表<字串> 。 這些就是所謂的向下轉型,從超類型向下轉型為子類型。
具體參數化類型的範例是 列表<字串> , 地圖<字串,日期> ,但不是 列出<?擴充 Number> 或者 Map<String,?> 。
有時預計 列表<物件> 將是 a 的超類型 列表<字串> , 因為 目的 是一個超類型 細繩 。 這種期望源自於這樣一個事實:數組存在這種類型的關係: 目的[] 是一個超類型 細繩[] , 因為 目的 是一個超類型 細繩 。 (這種類型的關係稱為 協方差 .) 組件類型的父子類型關係擴展到對應的陣列類型。對於泛型類型的實例化,不存在這樣的型別關係。 (參數化類型是 不是 協變。 )
同一泛型類型的實例之間缺乏超級子類型關係會產生各種後果。 這是一個例子。
例子:
無效 printAll(ArrayList<Object> c) {
for (物件 o : c)
System.out.println(o);
} ArrayList<String> 清單 = 新 ArrayList<String>();
....填寫清單...
列印全部(列表); // 錯誤
一個 ArrayList<String> 物件不能作為參數傳遞給需要 ArrayList<Object> 因為這兩種類型是同一泛型類型的實例化,但是對於 不同的 類型參數,因此它們彼此不相容。
具體參數化類型是泛型類型的具體實例。 它們幾乎是相似的類型;只有一些限制。 它們不能用於以下目的:
- 用於建立數組
- 在例外處理中
- 在類別文字中
- 在一個 實例化 表達
保存類型為具體參數化類型的元素的陣列是非法的。
範例(非法數組類型):
靜態無效測試(){
對<整數,整數>[] intPairArr = 新 對<整數,整數>[10] ; // 錯誤
addElements(intPairArr);
Pair<Integer,Integer> 對 = intPairArr[1];
整數 i =pair.getFirst();
對.setSecond(i);
}
靜態無效新增元素( 目的[] objArr) {
objArr[0] = 新 Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("",""); // 應因 ArrayStoreException 失敗
}
編譯器禁止建立其元件類型是特定參數化類型的數組,例如 對<整數,整數> 在我們的例子中。 我們在前面的條目中討論了為什麼編譯器限定一個是合理的 對<整數,整數>[] 作為非法的。 關鍵問題是編譯器和執行時間系統必須確保陣列是一個 同質 相同類型的元素的序列。 其中一種類型檢查,即虛擬機器在執行時執行的陣列儲存檢查,無法偵測到外來元素的違規插入。在範例中,第二個插入 新增元素 方法應該失敗,因為它們將一對字串添加到整數值數組中,但它不會按預期失敗。原因已在前面的條目中討論過。
如果我們不能使用保存類型為特定參數化類型的元素的數組,我們可以使用什麼作為解決方法?
讓我們考慮 3 種可能的解決方法:
- 原始型別數組
- 無界通配符參數化類型的數組
- 集合而不是數組
雖然具體參數化類型的陣列是非法的,但具體參數化類型的集合是允許的。
通配符參數化類型是泛型類型的實例化,其中至少一個型別參數是通配符。 通配符參數化類型的範例是 收藏<?> , 列出<?擴充 Number> , 比較器<?超級弦> 和 對<字串,?> 。通配符參數化類型表示包含泛型類型的特定實例的型別族。 所使用的通配符的類型決定了哪些特定參數化類型屬於該族。 例如,通配符參數化類型 收藏<?> 表示所有實例化的家族 收藏 接口,無論類型參數如何。 通配符參數化類型 列出<?擴充 Number> 表示所有清單類型的族,其中元素類型是以下類型的子類型 數位 。 通配符參數化類型 比較器<?超級弦> 是所有實例化的家族 比較器 作為超類型的類型參數類型的接口 細繩。
通配符參數化類型不是常規意義上的類型(不同於非參數化類別/介面或原始類型)。
通配符參數化類型可用於鍵入(如非參數化類別和介面):
- 作為方法的參數和返回類型
- 作為欄位或局部引用變數的類型
- 作為數組的組件類型作為其他參數化類型的類型參數
- 作為強制轉換中的目標類型
不只類型可以是泛型的,方法也可以是泛型的。靜態和非靜態方法以及建構函數可以具有類型參數。形式類型參數聲明的語法與泛型類型的語法類似。類型參數部分由尖括號分隔,出現在方法的回傳類型之前。其語法和意義與泛型類型的類型參數清單相同。
這是通用的範例 最大限度 計算未知類型元素集合中最大值的方法 一個 。
範例(通用方法):
類別集合{
公共靜態 <A 擴充了 Comparable<A>> A 最大(集合< 一個 > xs) {
迭代器< 一個 > xi = xs.iterator();
一個 w = xi.next();
while (xi.hasNext()) {
一個 x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
返回w;
}
}
這 最大限度 方法有一個型別參數,名為 一個 。 它是該方法所處理的集合的元素類型的佔位符。類型參數有一個界限;它必須是一個子類型的類型 可比<A>
範例(相同的泛型類型,這次 和 邊界):
公共介面 Comparable<T> {
公共 int CompareTo(T arg);
}
公共類別 TreeMap<Key 擴充 Comparable<Key> ,數據>{
私有靜態類別 Entry<K,V> {
K鍵;
V值;
Entry<K,V> 左 = null;
Entry<K,V> 右 = null;
條目<K,V>父級;
}
私有瞬態Entry<Key,Data> root = null;
...
私有 Entry<Key,Data> getEntry(Key key) {
Entry<Key,Data> p = 根;
密鑰 k = 密鑰;
while (p != null) {
int cmp = k。 比較(p.key) ;
如果(cmp==0)
返回p;
否則如果(cmp < 0)
p = p.左;
別的
p = p.右;
}
返回空值;
}
公共布爾包含Key(密鑰密鑰){
回傳 getEntry(key) != null;
}
...
}
在上面的例子中,類型參數 鑰匙 有界限 可比較<Key> 。 界限的規範有兩個作用:
- 它允許存取綁定指定的方法 。 在該範例中,邊界 可比較<Key> 可以訪問 比較 我們要在實作中呼叫的方法 樹狀圖 班級。
- 只有「在邊界內」的類型才能用於泛型類型的實例化。 在範例中,參數化類型例如 TreeMap<Number,String> 會被拒絕,因為類型 數位 不是 的子類型 可比較<數量> 。 參數化類型如 TreeMap<String,String> 會被接受,因為 String 類型在範圍內,也就是 String 的子類型 可比較<字串> .
所有類別、介面和枚舉類型都可以用作類型參數綁定,包括巢狀類型和內部類型。 基本型別和陣列型別都不能用作型別參數綁定。
範例(類型參數邊界):
X0 <T 級擴展 整數 > { ... } // 錯誤
X1 <T 類別擴展 目的[] > { ... } // 錯誤
X2 <T 級擴展 數位 > { ... }
X3 <T 級擴展 細繩 > { ... }
X4 <T 級擴展 可運行 > { ... }
X5 <T 級擴展 線程狀態 > { ... }
X6 <T 級擴展 清單 > { ... }
X7 <T 級擴展 列表<字串> > { ... }
X8 <T 級擴展 列出<?擴充 Number> > { ... }
X9 <T 級擴展 可以與<相媲美嗎?超級號碼> > { ... }
X10<T 類別擴展 Map.Entry<?,?> > { ... }
程式碼範例顯示了基本類型,例如 整數 和數組類型,例如 目的[] 不允許作為類型參數綁定。
類別類型,例如 數位 或者 細繩 ,以及介面類型,例如 可運行 ,允許作為類型參數綁定。
枚舉類型,例如 線程狀態 也允許作為類型參數綁定。 線程狀態 是用作類型參數綁定的巢狀類型的範例。 非靜態內部類型也是允許的。
允許原始類型作為類型參數綁定; 清單 就是一個例子。
參數化類型允許作為類型參數綁定,包括特定的參數化類型,例如 列表<字串> ,有界通配符參數化類型,例如 列出<?擴充 Number> 和 可以與<相媲美嗎?超長> ,以及無界通配符參數化類型,例如 Map.Entry<?,?> 。 作為通配符參數化類型的邊界允許屬於通配符表示的型別族的所有型別作為型別參數。 通配符參數化類型綁定僅提供對欄位和方法的有限存取;限制取決於通配符的類型。
範例(通配符參數化類型作為類型參數綁定):
X< 級 T 擴充 List<?擴充 Number> > {
公共無效一些方法(T t){
t.add(new Long(0L)); // 錯誤
數字 n = t.remove(0);
}
}
類別測試{
公共靜態無效主(字串[] args){
X<ArrayList< 長的 >> x1 = 新 X<ArrayList<Long>>();
X<ArrayList< 細繩 >> x2 = 新 X<ArrayList<String>>(); // 錯誤
}
}
類型的引用變數 時間 (型別參數)被視為通配符類型(型別參數綁定)的參考變數。 在我們的範例中,結果是編譯器拒絕呼叫採用型別參數所代表的「未知」類型的參數的方法,例如 列表.新增 ,因為界限是具有上限的通配符參數化類型。
同時綁定 列出<?擴充 Number> 確定可用作型別參數的型別。編譯器接受屬於該類型族的所有型別參數 列出<?擴充 Number> ,即所有子類型 清單 帶有一個子類型的類型參數 數位 。
一個類型參數可以用作另一個類型參數的邊界。
範例(用作類型參數綁定的類型參數):
三人艙 <T> {
私有 T fst、snd、trd;
公共< U 延伸 T , V 延伸 T , 瓦 延伸 T > 三重(U arg1,V arg2,W arg3){
fst = arg1;
snd = arg2;
trd = arg3;
}
}
在此範例中,類型參數 時間 參數化類別的值用作型別參數的綁定 U , V 和 瓦 該類別的參數化實例方法。
使用類型參數作為其他類型參數的邊界的更多機會包括在泛型類型內定義嵌套類型或在泛型方法內定義局部類別的情況。 甚至允許使用一個型別參數作為同一型別參數部分中另一個型別參數的綁定。
作為類別的邊界允許存取其所有公共成員,即公共欄位、方法和巢狀類型。只有建構子不可訪問,因為不能保證綁定的子類別具有與綁定相同的建構子。
範例(用作類型參數綁定的類別):
公開課 超級班 { // 靜態成員
公共枚舉 EnumType {這個,那個}
公共靜態物件靜態字段;
公共靜態無效靜態方法(){...}// 非靜態成員
公共類別內部類別 { ... }
公共物件非靜態字段;
公共無效非靜態方法(){...}// 建構函式
公共超類別(){...}// 私有成員
私有對象私有欄位;...
}公共最終類 SomeClass<T 擴展 超級班 > {
私有T對象;
公共 SomeClass(T t) { 物件 = t; }公有字串 toString() {
返回
"靜態巢狀型別 : "+T.EnumType.class+"\n"
+"靜態字段 : "+T.staticField+"\n"
+"靜態方法 : "+T.staticMethod()+"\n"
+“非靜態巢狀型別:”+T.InnerClass.class+”\n”
+"非靜態字段 : "+object.nonStaticField+"\n"
+"非靜態方法 : "+object.nonStaticMethod()+"\n"
+"建構子 : "+(new T())+"\n" //錯誤
+"私有成員 : "+object.privateField+"\n" // 錯誤
;
}
}
界線 超級班 提供對其巢狀類型、靜態欄位和方法以及非靜態欄位和方法的存取。 只有建構函數不可存取。 這是因為建構子不是繼承的。每個子類別都定義自己的建構函數,並且不需要支援其超類別的建構函數。 因此不能保證子類別 超級班 將具有與其超類別相同的建構函數。
所處的背景 Enum<E 擴展了 Enum<E>> ” 出現的是聲明 列舉 包中的類 java語言 :
公共抽象類別 Enum<E 擴展了 Enum<E>> {
...
}
類型 列舉 是所有枚舉類型的公共基類。 在 Java 中,枚舉型別例如 顏色 被翻譯成一個類別 顏色 延伸 枚舉<顏色> 。超類的目的 列舉 是提供所有枚舉類型通用的功能。
這是班級草圖 列舉 :
公共抽象類別 Enum< 乙 extends Enum<E>> implements Comparable< 乙 >, Serializable {
私有最終字串名稱;
公共最終字串名稱(){...} 私人最終 int 序數;
公共最終 int ordinal() { ... }protected Enum(String name, int ordinal) { ... }
公有字串 toString() { ... }
公共最終布林值等於(物件其他){...}
公共最終 int hashCode() { ... }
protected Final Object clone() 拋出 CloneNotSupportedException { ... }
公共最終 int 比較( 乙 o) { ... }公共最終類< 乙 > getDeclaringClass() { ... }
公共靜態 <T 擴充 Enum<T>> T valueOf(Class<T> enumType, 字串名稱) { ... }
}
The surprising feature in the declaration “ Enum<E 擴展了 Enum<E>> ” is the fact that the newly defined class 列舉 and its newly defined type parameter 乙 appear in the bound of that same type parameter. It means that the 列舉 type must be instantiated for one of its subtypes. In order to understand why this makes sense, consider that every enum type is translated into a subtype of 列舉 .
Here is the contrived enum type 顏色 :
enum 顏色 {RED, BLUE, GREEN}
The compiler translates it into the following class:
public final class 顏色 extends 枚舉<顏色> {
公共靜態最終顏色[]值(){返回(顏色[])$VALUES.clone(); }
公共靜態顏色值(字串名稱){...} 私有顏色(字串 s,int i){ 超級(s,i); }公共靜態最終顏色紅色;
公共靜態最終顏色藍色;
公共靜態最終顏色綠色;私人靜態最終顏色$VALUES[];
靜止的 {
紅色=新顏色(“紅色”,0);
藍色 = 新顏色("藍色", 1);
綠色 = 新顏色("綠色", 2);
$VALUES = (新顏色[] { 紅、藍、綠 });
}
}
繼承的效果是 顏色 類型繼承了所有實作的方法 枚舉<顏色> 。 其中有 比較 方法。 這 顏色。比較 方法可能應該採取 顏色 作為一個論點。 為了實現這一點,班級 列舉 是通用的,並且 枚舉compareTo 方法需要 列舉 的型別參數 乙 作為一個論點。 結果,輸入 顏色 源自 枚舉<顏色> 繼承了一個 比較 方法需要一個 顏色 as 和 argument,正如它應該的那樣。
如果我們剖析聲明“ Enum<E 擴展了 Enum<E>> 「我們可以看到這個模式有幾個面向。
首先,事實是類型參數綁定是類型本身:“ 列舉 <E 延長 列舉 <E>> 「。它確保只有類型的子類型 列舉 允許作為類型參數。 (理論上,輸入 列舉 可以實例化自身,就像 枚舉<Enum> ,但這肯定不是有意的,而且很難想像這樣的實例化會有用的情況。 )
其次,參數綁定類型是參數化類型 列舉 <E> ,它使用類型參數 乙 作為邊界的類型參數。此聲明確保了子類型和實例化之間的繼承關係 列舉 是這樣的形式 X 擴充了 Enum<X> 」。諸如「的子類型 X 擴充了 Enum<Y> ” 由於型別參數而無法聲明 是 不會在界限內;僅子類型 枚舉<X> 都在界線之內。
第三,有一個事實是 列舉 首先是通用的。 這意味著類別的一些方法 列舉 接受一個參數或傳回未知類型的值(或依賴未知類型)。我們已經知道,這種未知類型稍後將成為一個子類型 X 的 枚舉<X> 。 因此,在參數化類型中 枚舉<X> ,這些方法涉及子類型 X ,並且它們被繼承到子類型中 X 。 這 比較 方法是這種方法的一個例子;它從超類繼承到每個子類,並且在每種情況下都有一個子類特定的簽名。
總而言之,聲明“ Enum<E 擴展了 Enum<E>> ” 可以解讀為: 列舉 是一個泛型類型,只能為其子類型實例化,並且這些子類型將繼承一些有用的方法,其中一些方法採用子類型特定的參數(或以其他方式依賴子類型)。
類別的下界類型參數
類型參數可以有多個邊界,例如 類別 Box<T 擴充 Number> {...} 。但類型參數不能有下限,即諸如此類的構造 類 Box<T 偉大的 編號> {...} 是不允許的。 為什麼不呢? 答案是:這是毫無意義的,因為如果允許的話,它不會給你買任何東西。 讓我們透過探索類型參數的上限意味著什麼來看看為什麼類別的下限類型參數會令人困惑。
類型參數的上限有三個作用:
- 受限實例化。 上限限制可用於泛型類型實例化的類型集。 如果我們聲明一個 類別 Box<T 擴充 Number> {...} 那麼編譯器將確保只有子類型 數位 可以用作類型參數。 也就是說,一個 盒子<數量> 或一個 框<長> 是允許的,但是 盒子<物件> 或者 盒子<字串> 會被拒絕
範例(由於類型參數的上限而限制實例化):
類別 Box<T 擴充 Number> {
私有T值;
公共框(T t) { 值 = t; }
...
} 類別測試{
公共靜態無效主(字串[] args){
Box<Long> boxOfLong = 新 Box<Long>(0L); // 美好的
Box<String> boxOfString = new Box<String>(""); // 錯誤:字串不在範圍內
}
}
- 存取非靜態成員。 上限允許存取上限的所有公共非靜態方法和欄位。 在實施我們的 類別 Box<T 擴充 Number> {...} 我們可以呼叫類別中定義的所有公共非靜態方法 數位 , 例如 intValue() 例如。 如果沒有上限,編譯器將拒絕任何此類呼叫。範例(由於類型參數的上限而存取非靜態成員):
類別 Box<T 擴充 Number> {
私有T值;
公共框(T t) { 值 = t; }
公共 int 增量() { 傳回值.intValue()+1; } // 如果沒有 Number 限制,<= 將會出錯
...
}
- 類型擦除。 最左邊的上限用於類型擦除並替換字節碼中的類型參數。 在我們的 類別 Box<T 擴充 Number> {...} 所有出現的 時間 將被上限取代 數位 。 例如,如果類別 盒子 有一個類型的私有字段 時間 和一個方法 void set(T內容) 若要設定此私有字段,則該字段的類型為 數位 類型擦除後,該方法將被轉換為方法 void set(數字內容) .
範例(在類型擦除中使用類型參數的上限 – 前 類型擦除):
類 Box< T 擴充數字 > {
私人的 時間 價值;
公共信箱( 時間 t) { 值 = t; }
...
}
- 範例(在類型擦除中使用類型參數的上限 –
後
- 類型擦除):
類別框{
私人的 數位 價值;
公共信箱( 數位 t) { 值 = t; }
...
}
- 此外,最左邊的上限出現在其他位置,例如自動插入的強制轉換和橋接方法。
如果類型參數允許有下限,它們會或應該有哪些副作用?如果一個結構如 類 Box<T 偉大的 編號> {...} 被允許了,這意味著什麼? 上限類型參數界限的 3 個副作用 – 受限實例化、存取非靜態成員、類型擦除 – 對於下限意味著什麼?
Restricted Instantiations. The compiler could restrict the set of types that can be used for instantiation of the generic type with a lower bound type parameter. For instance, the compiler could permit instantiations such as 盒子<數量> 和 盒子<物件> from a Box<T 偉大的 Number> and reject instantiations such as 框<長> 或者 Box<Short> . This would be an effect in line with the restrictive side-effect described for upper type parameter bounds.
存取非靜態成員。 A lower type parameter bound does not give access to any particular methods beyond those inherited from class 目的 . In the example of Box<T 偉大的 Number> the supertypes of 數位 have nothing in common, except that they are reference types and therefore subtypes of 目的 . The compiler cannot assume that the field of type 時間 is of type 數位 or a subtype thereof. Instead, the field of type 時間 can be of any supertype of 數位 , 例如 Serializable 或者 目的 . The invocation of a method such as intValue() is no longer type-safe and the compiler would have to reject it. As a consequence, the lower type parameter bound would not give access to an non-static members beyond those defined in class 目的 and thus has the same effect as “no bound”.
Type Erasure . Following this line of logic, it does not make sense to replace the occurences of the type parameter by its leftmost lower bound. Declaring a method like the constructor Box( 時間 t) as a constructor Box( 數位 t) does not make sense, considering that 時間 is replaces by a supertype of 數位 . An 目的 might be rightly passed to the constructor in an instantiation 盒子<物件> and the constructor would reject it. This would be dead wrong. So, type erasure would replace all occurences of the type variable 時間 按類型 目的 ,而不是其下限。 同樣,下限與「無界限」有相同的效果。
你想知道如果兩者都降低意味著什麼嗎? _和_ 允許上限嗎? 就我個人而言,如果您允許的話,我什至不想考慮它,並且更願意將其歸檔為「無法管理」。
底線是:所有這一切都是“ 偉大的 「bound will buy you 是只有超類型的限制 數位 可以用作類型參數。 甚至這一點也常被誤解。 這並不意味著 類別 Box<T 超級 Number> {...} 僅包含超類型的實例 數位 。 剛好相反 – 正如下面的範例所示!
範例(在類型擦除中使用類型參數的上限 – 前 類型擦除):
類 Box< T超級號碼 > {
私人的 時間 價值;
公共信箱( 時間 t) { 值 = t; }
...
}
範例(在類型擦除中使用類型參數的上限 – 後 類型擦除):
類別框{
私人的 目的 價值;
公共信箱( 目的 t) { 值 = t; }
...
}
一個 類 Box<T 偉大的 編號> {...} 將透過類型擦除轉換為 盒子 含有一個 目的 字段及其建構函數將被轉換為 盒子(物件t) 。這與 類 Box<T extends 編號> {...} ,這將被翻譯成 盒子 含有一個 數位 字段及其建構函數將被轉換為 盒子(數量t) 。因此,一個 盒子<數量> 實例化自 類別 Box<T 擴充 Number> {...} 會不同於 盒子<數量> 實例化來自 類別 Box<T 超級 Number> {...} ,這可能會引起混亂。 因此,下限對於類別的型別參數沒有意義。
方法的下界類型參數
與方法及其參數類型結合使用時,具有下限的類型參數有時會很有用。
範例(受益於具有下限的型別參數的方法):
類 Pair<X,Y> {
首先是私有X;
私人Y第二;
...
公共< 一個超級X , B超Y > B addToMap(Map<A,B> map) { // 錯誤:型別參數不能有下限
返回map.put(第一,第二);
}
}
類別測試{
公共靜態無效主(字串[] args){
Pair<String,Long> 對 = new Pair<>("ABC",42L);
Map<CharSequence,Number> 映射 = HashMap<CharSequence,Number>();
Number number =pair.addToMap(map);
}
}
這 加入地圖() 方法將該對的內容新增至地圖。 任何可以容納超類型的映射 X 和 是 會做的。 地圖的 放() 如果映射中已存在該鍵的鍵值條目,則方法會傳回在映射中找到的給定鍵的值。 地圖的回傳值 放() 方法應從返回 加入地圖() 方法。 在這些情況下,人們希望宣告如上所示的方法:映射使用該對的類型參數的超類型和 加入地圖() 方法的傳回類型是映射的值類型。
由於編譯器不允許型別參數的下限,我們需要一個解決方法。
我想到的解決方法是使用通配符,因為通配符可以有下限。 這是使用萬用字元的解決方法。
範例(使用通配符解決上一個範例的方法):
類 Pair<X,Y> {
首先是私有X;
私人Y第二;
...
民眾 目的 加入地圖(地圖< ?超級X , ?超級Y > 地圖){
返回map.put(第一,第二);
}
}
類別測試{
公共靜態無效主(字串[] args){
Pair<String,Long> 對 = new Pair<>("ABC",42L);
Map<CharSequence,Number> 映射 = HashMap<CharSequence,Number>();
數量 數量 = (數字) 對.addToMap(地圖);
}
}
它可以工作,只是無法根據需要聲明返回類型。 這將是以下的超類型 是 編譯器從映射類型捕獲,但沒有指定它的語法。 我們不能將返回類型聲明為“ ?超級Y “, 因為 ” ?超級Y ” 是通配符而不是類型,因此不允許作為返回類型。我們別無選擇,必須使用 目的 相反地作為我們方法的返回類型。 這種相當不特定的返回類型反過來又迫使呼叫者 加入地圖() 方法將回傳值向下轉換 目的 其實際類型。 這並不完全是我們的想法。
另一種解決方法是使用靜態方法。 這是使用靜態方法而不是非靜態方法的解。
範例(使用靜態方法解決上一個範例的方法):
類 Pair<X,Y> {
首先是私有X;
私人Y第二;
...
民眾 靜止的 < 一個 , 乙 ,X 延伸 A ,Y 延伸 B > B addToMap(Pair<X,Y> 對, Map< 一個 , 乙 > 地圖){
return map.put(pair.first,pair.second);
}
}
類別測試{
公共靜態無效主(字串[] args){
Pair<String,Long> 對 = new Pair<>("ABC",42L);
Map<CharSequence,Number> 映射 = HashMap<CharSequence,Number>();
數量 數量 = 一對 .addToMap( 一對 ,地圖);
}
}
通用的 加入地圖() 方法有四個型別參數:兩個佔位符 X 和 是 對於該對的類型和兩個佔位符 一個 和 乙 對於地圖的類型。 一個 和 乙 是超類型 X 和 是 , 因為 X 和 是 聲明為 一個 和 乙 作為它們的上限。 (注意,泛型方法的型別參數 X 和 是 與 一對 班級的 X 和 是 參數。 名字 X 和 是 被重用於泛型方法,使它們易於識別為該對的類型參數。使用四個類型參數,我們可以根據需要聲明精確的回傳類型:它與映射的值類型相同。
範例(在泛型類別的靜態上下文中非法使用類型參數):
類某類 <T> {
// 靜態初始值設定項、靜態欄位、靜態方法
靜止的 {
一些類< 時間 > 測試 = 新 SomeClass< 時間 >(); // 錯誤
}
私人的 靜止的 時間 全局資訊; // 錯誤
民眾 靜止的 時間 取得全域資訊(){ // 錯誤
返回全域資訊;
}
// 非靜態初始化器、非靜態欄位、非靜態方法
{
一些類< 時間 > 測試 = 新 SomeClass< 時間 >();
}
私人的 時間 本地資訊;
民眾 時間 取得本地資訊(){
返回本地資訊;
}
// 靜態巢狀類型
民眾 靜止的 班級 失敗擴展異常{
私人決賽 時間 資訊; // 錯誤
公共失敗( 時間 t) { info = t; } // 錯誤
民眾 時間 getInfo() { 回傳訊息; } // 錯誤
}
私人的 介面 可複製{
時間 複製(); // 錯誤
}
私人的 enum 狀態 {
有效、無效;
私人的 時間 訊息; // 錯誤
公共無效設定資訊( 時間 t) { info = t; } // 錯誤
民眾 時間 getInfo() { 回傳訊息; } // 錯誤
}
// non-static nested types
民眾 班級 Accessor {
民眾 時間 getInfo() { return localInfo; }
}
}
此範例說明類型參數不能在泛型類別的靜態上下文中使用。 它也顯示巢狀介面和枚舉類型被視為類別的靜態型別成員。 只有內部類別(即非靜態巢狀類別)可以使用封閉泛型類別的型別參數。
Interface’s 類型參數的範圍是介面的整個定義,任何欄位或巢狀類型除外。 這是因為介面中定義的欄位和巢狀類型是隱式靜態的。
範例(在泛型介面中非法使用類型參數):
interface SomeInterface <T> {
// field
一些類< 時間 > value = new SomeClass< 時間 >(); // 錯誤
// nested type
班級 Accessor {
民眾 時間 獲取資訊(){ // 錯誤
傳回值.getGlobalInfo();
}
}
// methods
時間 getValue();
}
The example shows that fields of an interface are implicitly static, so that the type parameter cannot be used anywhere in the declaration of a field of a generic interface. Similarly, the nested class is considered a static nested class, not an inner class, and for this reason use of the type parameter anywhere in the nested class is illegal.
Workaround for nested static classes and interfaces:
對於靜態嵌套類別和接口,這不是一個大災難。 作為解決方法,我們可以產生靜態類別或介面本身。
範例(解決方法 – 產生巢狀靜態類型):
class Wrapper <T> {
私人決賽 時間 theObject;
公共包裝( 時間 t) { theObject = t; }
民眾 時間 getWrapper() { return theObject; } 公共不可變 <T> makeImmutable() {
返回新的不可變的 <T> (theObject);
}
公共可變 <T> makeMutable() {
返回新的可變 <T> (theObject);
}
私有靜態 <A> A makeClone(A theObject) { ... }公共靜態最終類別不可變 <A> { // 現在是泛型類別
私人決賽 一個 theObject;公共不可變( 一個 參數){
theObject = makeClone(arg);
}
民眾 一個 getWrappedItem() {
返回 makeClone(theObject);
}
}
公共靜態類別可變 <A> {
... 相似的 ...
}
}
對於巢狀枚舉類型沒有這樣的解決方法,因為它們不能是泛型的。
嵌套靜態類別的解決方法:
如果巢狀靜態類型是一個類,則另一種解決方法是將靜態類別轉換為非靜態內部類別。
範例(解決方法 – 使用內部類別):
class Wrapper <T> {
私人決賽 時間 theObject;
公共包裝( 時間 t) { theObject = t; }
民眾 時間 getWrapper() { return theObject; } 公共可變 makeMutable() {
返回 這。 新的可變(theObject);
}
公共不可變 makeImmutable() {
返回 這。 新的不可變(theObject);
}
私有靜態 <A> A makeClone(A theObject) { ... }公共最終類別不可變{ // 不再是靜態類
私人決賽 時間 theObject;公共不可變( 時間 參數){
theObject = makeClone(arg);
}
民眾 時間 getWrappedItem() {
返回 makeClone(theObject);
}
}
公共類別可變
... 相似的 ...
}
}
這種解決方法會帶來一定的開銷,因為所有內部類別都有一個對外部類型實例的隱藏引用,在本例中它們既不需要也不利用;隱藏的參考只是行李。 通常,內部類別與介面組合在一起,以使內部類別成為封閉類別的私有實作細節。 我們也可以在這裡做同樣的事情。
範例(改進了先前的解決方法):
class Wrapper <T> {
私人決賽 時間 theObject;
公共包裝( 時間 t) { theObject = t; }
民眾 時間 getWrapper() { return theObject; } 公共可變 <T> makeMutable() {
返回 this.new Mutable(theObject);
}
公共不可變 <T> makeImmutable() {
返回 this.new Immutable(theObject);
}
私有靜態 <A> A makeClone(A theObject) { ... }公共介面 不可變<S> {
S getWrappedItem();
}
公共介面 可變<S> {
S getWrappedItem();
無效 setWrappedItem(S arg);
}
私人的 最終類別 ImmutableImplementation 實作 Immutable<T> {私有最終 T theObject;
公共 ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
公用 T getWrappedItem() {
返回 makeClone(theObject);
}
}
私人的 可變實作類別 實現 Mutable<T> {
... 相似的 ...
}
}
如您所見,嵌套的公共介面必須是通用的,而內部私有類別可以使用封閉類別的類型參數。

发表回复