泛型的使用能使類型名稱作為類或者接口定義中的參數,就像一般的參數一樣,使得定義的類型通用性更強。
泛型的優勢:
java編譯器對於泛型代碼的類型檢查更加嚴格,能夠發現普通代碼中的一些運行時錯誤。
//如下代碼未使用泛型,需要進行類型的轉化 List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); //泛型的使用可以不適用類型轉化 List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast
通過使用泛型,使得一類不同的類型能夠進行通用的運算。
泛型是將類型參數化的類或者接口。
一般泛型的聲明類似如下:
class name<T1, T2, ..., Tn> { /* ... */ }
尖括號中的參數就是類型參數,參數由逗號隔開,類型參數可以是任何非基本數據類型的任何類型,泛型的接口聲明與上述泛型類類似。
一般情況下,泛型類型參數的名稱是單個大寫字母,和變量名稱鮮明地區分開來。
最通用的參數類型參數名稱為:
在javaSE API中這些名稱被廣泛使用
如需引用一個泛型,首先要進行一個泛型的調用,如下所示:
//傳入類型參數,如下為String作為類型參數 ArrayList<String> list;
上述代碼可以看做個調用方法類似,不過是以類型為參數,這一過程叫參數化類型,實例化泛型的語法如下:
//一下聲明和實例化一步完成 ArrayList<String> list=new ArrayList<String>();
java7以後,只要編譯器能夠根據代碼的上下文判斷類型參數,就可以將泛型的構造器的類型實參留空(<>)由於空的尖括號形狀就像鑽石,所以非正式的成為鑽石,如上述代碼可以簡寫為:
//注意構造器內的參數已經省略 ArrayList<String> list=new ArrayList<>();
泛型的類型參數也可以是參數話的泛型,如:
//省略構造器類型參數 ArrayList<List<String>> list=new ArrayList<>();
原始類型是指沒有類型參數的泛型。例如一下聲明一個原始類型:
//ArrayList是一個泛型,因此List變量是原始類型 ArrayList list=new ArrayList();
原始類型的舊的java版本的遺產,因為許多API類如集合類在JDK5之前不是泛型,為了向下兼容,將一個參數化的泛型對象賦值給一個原始類型是允許的:
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; // OK
不過反過來,將一個原始類型賦值給一個參數化的泛型,編譯器將會給出警告:
Box rawBox = new Box(); // rawBox是 Box<T>的原始類型 Box<Integer> intBox = rawBox; // 警告: unchecked conversion
如果使用原始類型去調用隊形的泛型的泛型方法,同樣也會得到警告:
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; rawBox.set(8); // warning: unchecked invocation to set(T)
編譯器警告表明原始類型繞過了類型的檢查,而將可能出錯的風險留到了運行時,所以盡量不要使用原始類型。
如之前所提到的,當泛型和傳統語法混用時,你將會遇到一些如下的警告消息:
Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
“unchecked”(未檢查的)這一術語表明編譯器沒有足夠的關於類型信息來執行檢查以確保類型的安全,編譯器默認警用未檢查警告,但是會給出提示,如果想要啟用未檢查警告,在編譯時加入參數 -Xlint:unchecked。
如果想要完全禁用未檢查警告,可以編譯時加入參數-Xlint:-unchecked(注意與上述參數區別)或者使用注釋@SuppressWarnings("unchecked")。
泛型方法是指引入自身的參數類型的方法,就像泛型的類型參數一樣,不過方法的類型參數的使用范圍僅限於方法自身。可以定義靜態和非靜態的泛型方法,同時也可以定義使用泛型構造器。
泛型方法的語法含有位於方括號內的類型參數,位於方法返回類型之前,當然,非泛型的類也可以包含泛型方法。
如下舉例說明泛型方法的聲明:
public class Util { //以下方法為泛型方法 public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } } public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; } }
完整的調用泛型方法的語法為:
//以下語句利用了上述定義的類 Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "pear"); boolean same = Util.<Integer, String>compare(p1, p2);
當然,如果編譯其能夠推測出類型,可省略泛型方法的類型參數:
Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "pear"); boolean same = Util.compare(p1, p2);
有時候你可能需要限定泛型的類型參數,比如限定某個泛型的類型參數只能為Number或者是其子類或者繼承類。
聲明類型參數的限定,就是在參數名稱之後跟上extends關鍵詞,然後跟上其上限,如Number,在這裡extends通常意義上是指類的extends和接口的implements。,如一下方法的聲明:
public <U extends Number> void inspect(U u){ System.out.println("T: " + t.getClass().getName()); System.out.println("U: " + u.getClass().getName()); }
一個類型參數可以有多重限定,即extends關鍵詞後跟多個上限,用符號&隔開:
class D <T extends A & B & C> { /* ... */ }
java中可以將一個類型的對象賦值給另外一個類型的變量,如果兩個類型相兼容的話,如可以將Integer類型的對象賦值給類型為Object的變量。在面向對象的術語中,這是一種叫做“是一個”的關系,如Integer類型是一個Object類型,因此允許上述的賦值。方法包括泛型的方法的參數的傳遞也是如此,如
//如下進行類型參數化的ArrayList類型的元素類型為Number ArrayList<Number> list=new ArrayLIst<Number>(); //其add方法參數類型也是Number,也可以使用其子類Integer的實例 list.add(new Integer(10));
但是對於泛型的子類型關系,與普通的類型是有區別的,如下圖所示:
箭頭表示子類型的關系,如Integer是Number的子類型,而Box<Integer>則不是Box<Number>的子類型,Box<Integer>和Box<Number>的共同的父類型是Object。
你可以通過繼承或者實現一個泛型來成為該泛型一個子類型。類或者接口的類型參數之間的關系是由extends和implements語句決定的。
比如集合類,ArrayList<E>實現List<E>,List<E>繼承Collection<E>所以ArrayList<String>是List<String>的子類型,List<String>是Collection<String>的子類型,只要不變更類型參數,這種繼承關系就會保留:
現在自定義一個新的繼承List接口的接口:
//注意類型參數的名稱 interface PayloadList<E,P> extends List<E> { void setPayload(int index, P val); ... }
類型參數化的類型則由如下關系: