所謂泛型:就是允許在定義類、接口指定類型形參,這個類型形參在將在聲明變量、創建對象時確定(即傳入實際的類型參數,也可稱為類型實參)
泛型類或接口
“菱形”語法
//定義
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(); // ③
}
}