泛型
| 導航 類和物件 主題: |
Java 是一種強型別語言,因此類中的欄位可以這樣型別化
程式碼清單 4.34:Repository.java
public class Repository {
public Integer item;
public Integer getItem() {
return item;
}
public void setItem(Integer newItem) {
item = newItem;
}
}
|
這確保了只有 Integer 物件可以放入欄位中,並且在執行時不會發生 ClassCastException,只會發生編譯時錯誤。不幸的是,它只能用於 Integer 物件。如果你想在另一個使用 String 的上下文中使用相同的類,你必須像這樣泛化型別
程式碼清單 4.35:Repository.java
public class Repository {
public Object item;
public Object getItem() {
return item;
}
public void setItem(Integer newItem) {
item = newItem;
}
public void setItem(String newItem) {
item = newItem;
}
}
|
但是你將在執行時再次遇到 ClassCastException,並且你不能輕鬆地使用你的欄位。解決方案是使用 泛型。
泛型類不會硬編碼欄位、返回值或引數的型別。該類只表示對於給定的物件例項,泛型型別應該相同。泛型型別未在類定義中指定。它是在物件例項化期間指定的。這允許泛型型別在從一個例項到另一個例項時不同。因此,我們應該這樣編寫我們的類
程式碼清單 4.36:Repository.java
public class Repository<T> {
public T item;
public T getItem() {
return item;
}
public void setItem(T newItem) {
item = newItem;
}
}
|
這裡,泛型型別是在類名之後定義的。可以選擇任何新的識別符號。這裡,我們選擇了T,這是最常見的選擇。實際型別是在物件例項化時定義的
程式碼節 4.35:例項化。
Repository<Integer> arithmeticRepository = new Repository<Integer>();
arithmeticRepository.setItem(new Integer(1));
Integer number = arithmeticRepository.getItem();
Repository<String> textualRepository = new Repository<String>();
textualRepository.setItem("Hello!");
String message = textualRepository.getItem();
|
雖然每個物件例項都有自己的型別,但每個物件例項仍然是強型別的
程式碼節 4.36:編譯錯誤。
Repository<Integer> arithmeticRepository = new Repository<Integer>();
arithmeticRepository.setItem("Hello!");
|
一個類可以定義任意數量的泛型型別。為每個泛型型別選擇一個不同的識別符號,並用逗號隔開它們
程式碼清單 4.37:Repository.java
public class Repository<T, U> {
public T item;
public U anotherItem;
public T getItem() {
return item;
}
public void setItem(T newItem) {
item = newItem;
}
public U getAnotherItem() {
return anotherItem;
}
public void setAnotherItem(U newItem) {
anotherItem = newItem;
}
}
|
當使用泛型定義的型別(例如,Collection<T>)不使用泛型(例如,Collection)時,稱為原始型別。
泛型型別可以僅為方法定義
程式碼節 4.37:泛型方法。
public <D> D assign(Collection<D> generic, D obj) {
generic.add(obj);
return obj;
}
|
這裡,在方法宣告的開頭選擇了一個新的識別符號(D)。該型別特定於方法呼叫,並且可以在同一物件例項中使用不同的型別
程式碼節 4.38:泛型方法呼叫。
Collection<Integer> numbers = new ArrayList<Integer>();
Integer number = assign(numbers, new Integer(1));
Collection<String> texts = new ArrayList<String>();
String text = assign(texts, "Store it.");
|
實際型別將由方法引數的型別定義。因此,泛型型別不能僅為返回值定義,因為它不會被解析。有關解決方案,請參見 Class<T> 部分。
問題 4.8:考慮以下類。
問題 4.8:Question8.java
public class Question8<T> {
public T item;
public T getItem() {
return item;
}
public void setItem(T newItem) {
item = newItem;
}
public static void main(String[] args) {
Question8<String> aQuestion = new Question8<String>();
aQuestion.setItem("Open your mind.");
aQuestion.display(aQuestion.getItem());
}
public void display(String parameter) {
System.out.println("Here is the text: " + parameter);
}
public void display(Integer parameter) {
System.out.println("Here is the number: " + parameter);
}
public void display(Object parameter) {
System.out.println("Here is the object: " + parameter);
}
}
|
控制檯上將顯示什麼?
答案 4.8 的控制檯
Here is the text: Open your mind. |
aQuestion.getItem() 的型別為字串。
正如我們上面所看到的,泛型給人的印象是,每個不同的型別引數都會建立一個新的容器型別。我們還看到,除了正常的型別檢查之外,當我們分配泛型變數時,型別引數也必須匹配。在某些情況下,這限制太嚴格。如果我們想放鬆這種額外的檢查怎麼辦?如果我們想定義一個可以儲存任何泛型集合的集合變數,無論它儲存的型別引數是什麼?萬用字元型別用字元 <?> 表示,發音為 未知 或 任意型別。任意型別也可以用 <? extends Object> 表示。任意型別包括介面,不僅僅是類。所以現在我們可以定義一個元素型別與任何東西匹配的集合。見下文
程式碼節 4.39:萬用字元型別。
Collection<?> collUnknown;
|
你可以對可以使用類的型別進行指定限制。例如,<? extends ClassName> 僅允許 ClassName 類或子類的物件。例如,要建立一個只能包含“可序列化”物件的集合,請指定
程式碼節 4.40:可序列化子物件的集合。
Collection<String> textColl = new ArrayList<String>();
Collection<? extends Serializable> serColl = textColl;
|
上面的程式碼是有效的,因為 String 類是可序列化的。使用不可序列化的類會導致編譯錯誤。新增的專案可以作為 Serializable 物件檢索。你可以呼叫 Serializable 介面的方法或將其轉換為 String。以下集合只能包含擴充套件 Animal 類的物件。
程式碼清單 4.38:Dog.java
class Dog extends Animal {
}
|
程式碼節 4.41:子類的示例。
// Create "Animal Collection" variable
Collection<? extends Animal> animalColl = new ArrayList<Dog>();
|
<? super ClassName> 指定對可以使用類的型別進行指定限制。例如,要宣告一個可以比較 Dogs 的 Comparator,請使用
程式碼節 4.42:超類。
Comparator<? super Dog> myComparator;
|
現在假設你定義了一個可以比較 Animals 的比較器
程式碼節 4.43:Comparator。
class AnimalComparator implements Comparator<Animal> {
int compare(Animal a, Animal b) {
//...
}
}
|
由於 Dogs 是 Animals,因此你也可以使用此比較器來比較 Dogs。任何 Dog 超類的比較器也可以比較 Dog;但是,任何嚴格子類的比較器都不能。
程式碼節 4.44:泛型比較器。
Comparator<Animal> myAnimalComparator = new AnimalComparator();
static int compareTwoDogs(Comparator<? super Dog> comp, Dog dog1, Dog dog2) {
return comp.compare(dog1, dog2);
}
|
上面的程式碼是有效的,因為 Animal 類是 Dog 類的超型別。使用不是超型別的類會導致編譯錯誤。
與原始型別(即沒有泛型)相比,無界萬用字元(即 <?>)的優勢在於明確表示引數化型別未知,而不是任何型別。這樣,所有暗示知道型別的操作都被禁止,以避免不安全的操作。考慮以下程式碼
程式碼部分 4.45:不安全操作。
public void addAtBottom(Collection anyCollection) {
anyCollection.add(new Integer(1));
}
|
此程式碼將編譯,但如果集合只包含字串,則此程式碼可能會破壞集合
程式碼部分 4.46:集合損壞。
List<String> col = new ArrayList<String>();
addAtBottom(col);
col.get(0).endsWith(".");
|
程式碼部分 4.46 的控制檯
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer incompatible with java.lang.String at Example.main(Example.java:17) |
如果 addAtBottom(Collection) 方法使用無界萬用字元定義,則可以避免這種情況:addAtBottom(Collection<?>)。使用此簽名,無法編譯依賴於引數化型別的程式碼。只能呼叫集合的獨立方法(clear()、isEmpty()、iterator()、remove(Object o)、size() 等)。例如,addAtBottom(Collection<?>) 可以包含以下程式碼
程式碼部分 4.47:安全操作。
public void addAtBottom(Collection<?> anyCollection) {
Iterator<?> iterator = anyCollection.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next());
}
}
|
從 Java 1.5 開始,java.lang.Class 類是泛型。它是一個有趣的例子,說明如何將泛型用於容器類以外的其他用途。例如,String.class 的型別是 Class<String>,Serializable.class 的型別是 Class<Serializable>。這可用於提高反射程式碼的型別安全性。特別是,由於 Class 中的 newInstance() 方法現在返回 T,因此在反射地建立物件時,您可以獲得更精確的型別。現在,我們可以使用 newInstance() 方法返回具有精確型別的全新物件,而無需進行強制轉換。使用泛型的示例
程式碼部分 4.48:自動強制轉換。
Customer cust = Utility.createAnyObject(Customer.class); // No casting
...
public static <T> T createAnyObject(Class<T> cls) {
T ret = null;
try {
ret = cls.newInstance();
} catch (Exception e) {
// Exception Handling
}
return ret;
}
|
沒有泛型的相同程式碼
程式碼部分 4.49:以前版本。
Customer cust = (Customer) Utility.createAnyObject(Customer.class); // Casting is needed
...
public static Object createAnyObject(Class cls) {
Object ret = null;
try {
ret = cls.newInstance();
} catch (Exception e) {
// Exception Handling
}
return ret;
}
|
Java 長期以來一直因需要在將元素從“容器/集合”類中取出時顯式型別轉換而受到批評。沒有辦法強制執行“集合”類只包含一種型別的物件(例如,在編譯時禁止將 Integer 物件新增到應該只包含 String 的 Collection 中)。從 Java 1.5 開始,這成為可能。在 Java 演變的前幾年,Java 沒有真正的競爭對手。微軟 C# 的出現改變了這一局面。Java 泛型更適合與 C# 競爭。其他語言中也存在類似於 Java 泛型的構造,有關詳細資訊,請參閱 泛型程式設計。泛型在 Java 語言語法中是在 1.5 版本中新增的。這意味著使用泛型的程式碼將無法在 Java 1.4 及更低版本中編譯。泛型的使用是可選的。為了與預泛型程式碼向後相容,可以使用沒有泛型型別規範(<T>)的泛型類。在這種情況下,當您從泛型物件檢索物件引用時,您將必須手動將其從型別 Object 強制轉換為正確的型別。
Java 泛型類似於 C++ 模板,因為它們都是出於相同的原因新增的。Java 泛型和 C++ 模板的語法也類似。但是,也存在一些差異。C++ 模板可以看作是一種宏,因為會為每個引用的泛型型別生成新的程式碼副本。所有模板的額外程式碼都在編譯時生成。相反,Java 泛型是內建在語言中的。相同的程式碼用於每個泛型型別。例如
程式碼部分 4.50:Java 泛型。
Collection<String> collString = new ArrayList<String>();
Collection<Integer> collInteger = new ArrayList<Integer>();
|
這兩個物件在執行時顯示為相同型別(都是 ArrayList)。泛型型別資訊在編譯期間被擦除(型別擦除)。例如
程式碼部分 4.51:型別擦除。
public <T> void method(T argument) {
T variable;
…
}
|
透過擦除轉換為
程式碼部分 4.52:轉換。
public void method(Object argument) {
Object variable;
…
}
|
問題 4.9:考慮以下類。
問題 4.9:Question9.java
import java.util.ArrayList;
import java.util.Collection;
public class Question9 {
public static void main(String[] args) {
Collection<String> collection1 = new ArrayList<String>();
Collection<? extends Object> collection2 = new ArrayList<String>();
Collection<? extends String> collection3 = new ArrayList<String>();
Collection<? extends String> collection4 = new ArrayList<Object>();
Collection<? super Object> collection5 = new ArrayList<String>();
Collection<? super Object> collection6 = new ArrayList<Object>();
Collection<?> collection7 = new ArrayList<String>();
Collection<? extends Object> collection8 = new ArrayList<?>();
Collection<? extends Object> collection9 = new ArrayList<Object>();
Collection<? extends Integer> collection10 = new ArrayList<String>();
Collection<String> collection11 = new ArrayList<? extends String>();
Collection collection12 = new ArrayList<String>();
}
}
|
哪些行會生成編譯錯誤?
答案 4.9:Answer9.java
import java.util.ArrayList;
import java.util.Collection;
public class Answer9 {
public static void main(String[] args) {
Collection<String> collection1 = new ArrayList<String>();
Collection<? extends Object> collection2 = new ArrayList<String>();
Collection<? extends String> collection3 = new ArrayList<String>();
Collection<? extends String> collection4 = new ArrayList<Object>();
Collection<? super Object> collection5 = new ArrayList<String>();
Collection<? super Object> collection6 = new ArrayList<Object>();
Collection<?> collection7 = new ArrayList<String>();
Collection<? extends Object> collection8 = new ArrayList<?>();
Collection<? extends Object> collection9 = new ArrayList<Object>();
Collection<? extends Integer> collection10 = new ArrayList<String>();
Collection<String> collection11 = new ArrayList<? extends String>();
Collection collection12 = new ArrayList<String>();
}
}
|
- 第 9 行:
Object不擴充套件String。 - 第 10 行:
String不是Object的超類。 - 第 13 行:無法例項化
ArrayList<?>。 - 第 15 行:
Integer不擴充套件String。 - 第 16 行:無法例項化
ArrayList<? extends String>。