所謂泛型:就是允許在定義類別、介面指定類型形參,這個類型形參在會在宣告變數、建立物件時決定(即傳入實際的型別參數,也可稱為型別實參)
泛型類別或介面
「菱形」語法複製程式碼如下:
//定義
public interface List<E> extends Collection<E>
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
//使用
List<String> list = new ArrayList();
//Java7以後可以省略後面尖括號的型別參數
List<String> list = new ArrayList<>();
從泛型類別派生子類別
複製代碼代碼如下:
//方式1
public class App extends GenericType<String>
//方式2
public class App<T> extends GenericType<T>
//方式3
public class App extends GenericType
偽泛型
不存在真正的泛型類,泛型類對Java虛擬機來說是透明的.JVM並不知道泛型類的存在,換句話說,JVM處理泛型類和普通類沒什麼區別的.因此在靜態方法、靜態初始化區塊、靜態變數裡面不允許使用類型形參。
- 以下方式都是錯誤的複製碼程式碼如下:
private static T data;
static{
T f;
}
public static void func(){
T name = 1;
}
下面的範例可以從側面驗證不存在泛型類別複製程式碼程式碼如下:
public static void main(String[] args){
List<String> a1 = new ArrayList<>();
List<Integer> a2 = new ArrayList<>();
System.out.println(a1.getClass() == a2.getClass());
System.out.println(a1.getClass());
System.out.println(a2.getClass());
}
輸出複製程式碼如下:
true
class java.util.ArrayList
class java.util.ArrayList
類型通配符
首先必須明確一點,假如Foo是Bar的父類,但是List<Foo>並不是List<Bar>的父類.為了表示各種泛型的父類,Java使用"?"來表示泛型通配.即List<?>來表示各種泛型List的父類別.帶這種通配符List泛型不能設定(set)元素,只能取得(get)元素。因為程式無法確定List中的類型,所以不能新增物件。但取得的物件肯定是Object類型。
以下方法會編譯出錯:
複製代碼代碼如下:
List<?> list = new ArrayList<>();
list.add(new Object());
主意幾點:
1.List<String>物件不能被當成List<Object>物件使用,也就是說:List<String>類別並不是List<Object>類別的子類別。
2.陣列和泛型有所不同:假設Foo是Bar的一個子型別(子類別或子介面),那麼Foo[]依然是Bar[]的子型別;但G<Foo>不是G<Bar>的子類型。
3.為了表示各種泛型List的父類,我們需要使用類型通配符,類型通配符是一個問號(?),將一個問號作為類型實參傳給List集合,寫作:List<?>(意思是未知類型元素的List)。這個問號(?)被稱為通配符,它的元素類型可以匹配任何類型。
通配符的上限
List<? extends SuperType>表示所有SuperType泛型List的父類別或本身。有通配符上限的泛型不能有set方法,只能有get方法。
設定通配符上限能解決以下問題:Dog是Animal子類,有個getSize方法要取得傳入List的個數,程式碼如下複製程式碼如下:
abstract class Animal {
public abstract void run();
}
class Dog extends Animal {
public void run() {
System.out.println("Dog run");
}
}
public class App {
public static void getSize(List<Animal> list) {
System.out.println(list.size());
}
public static void main(String[] args) {
List<Dog> list = new ArrayList<>();
getSize(list); // 這裡編譯報錯
}
}
這裡程式設計出錯的原因是List<Animal>並不是List<Dog>的父類別。解一可以把getSize方法中形參List<Animal>改為List<?>,不過這樣的話在每次get物件的時候都要強制型別轉換,比較麻煩。使用通配符上限很好的解決了這個問題,可以把List<Animal>改為List<? extends Animal>,編譯就不會錯了,也不用型別轉換。
通配符的下限
List<? super SubType>表示SubType泛型List的下限。有通配符上限的泛型不能有get方法,只能有set方法。
泛型方法
如果定義類別、介面是沒有使用類型形參,但定義方法時想自己定義類型形參,這也是可以的,JDK1.5也提供了泛型方法的支援。泛型方法的方法簽名比普通方法的方法簽名多了類型形參聲明,類型形參聲明以尖括號括起來,多個類型形參之間以逗號(,)隔開,所有類型形參聲明放在方法修飾符和方法傳回值類型之間.語法格式如下:
複製代碼代碼如下:
修飾符傳回值類型方法名稱(類別形列表){
//方法體
}
泛型方法允許類型形參被用來表示方法的一個或多個參數之間的類型依賴關係,或方法傳回值與參數之間的類型依賴關係。如果沒有這樣的類型依賴關係,就不應該使用泛型方法。 Collections的copy方法就使用泛型方法:
複製代碼代碼如下:
public static <T> void copy(List<? super T> dest, List<? extends T> src){ ...}
這個方法要求src類型必須是dest類型的子類別或本身。
擦除和轉換
在嚴格的泛型程式碼裡,帶有泛型宣告的類別總是應該帶著型別參數。但為了與舊的Java程式碼保持一致,也允許在使用帶有泛型聲明的類別時不指定類型參數。如果沒有為這個泛型類別指定型別參數,則該型別參數被稱為一個raw type(原始型別),預設是該宣告該參數時指定的第一個上限型別。
當把一個具有泛型資訊的物件賦給另一個沒有泛型資訊的變數時,則所有在尖括號之間的類型資訊都被丟掉了。比如說一個List<String>型別轉換為List,則該List對集合元素的型別檢查變成了成型變數的上限(即Object),這種情況被為擦除。
範例複製程式碼如下:
class Apple<T extends Number>
{
T size;
public Apple()
{
}
public Apple(T size)
{
this.size = size;
}
public void setSize(T size)
{
this.size = size;
}
public T getSize()
{
return this.size;
}
}
public class ErasureTest
{
public static void main(String[] args)
{
Apple<Integer> a = new Apple<>(6); // ①
// a的getSize方法傳回Integer對象
Integer as = a.getSize();
// 把a物件賦給Apple變量,遺失尖括號裡的類型訊息
Apple b = a; // ②
// b只知道size的型別是Number
Number size1 = b.getSize();
// 下面程式碼造成編譯錯誤
Integer size2 = b.getSize(); // ③
}
}