一、泛型簡介
1.引入泛型的目的
了解引入泛型的動機感挥,就先從語法糖開始了解缩搅。
語法糖
語法糖(Syntactic Sugar),也稱糖衣語法触幼,是由英國計算機學家Peter.J.Landin發(fā)明的一個術語硼瓣,指在計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用堂鲤。Java中最常用的語法糖主要有泛型亿傅、變長參數、條件編譯瘟栖、自動拆裝箱葵擎、內部類等。虛擬機并不支持這些語法半哟,它們在編譯階段就被還原回了簡單的基礎語法結構酬滤,這個過程成為解語法糖。
泛型的目的: Java 泛型就是把一種語法糖寓涨,通過泛型使得在編譯階段完成一些類型轉換的工作盯串,避免在運行時強制類型轉換而出現ClassCastException,即類型轉換異常戒良。
2.泛型初探
JDK 1.5 時才增加了泛型体捏,并在很大程度上都是方便集合的使用,使其能夠記住其元素的數據類型蔬墩。
在泛型(Generic type或Generics)出現之前译打,是這么寫代碼的:
public static void main(String[] args)
{
List list = new ArrayList();
list.add("123");
list.add("456");
System.out.println((String)list.get(0));
}
當然這是完全允許的,因為List里面的內容是Object類型的拇颅,自然任何對象類型都可以放入奏司、都可以取出,但是這么寫會有兩個問題:
1樟插、當一個對象放入集合時韵洋,集合不會記住此對象的類型,當再次從集合中取出此對象時黄锤,該對象的編譯類型變成了Object搪缨。
2、運行時需要人為地強制轉換類型到具體目標鸵熟,實際的程序絕不會這么簡單副编,一個不小心就會出現java.lang.ClassCastException。
所以流强,泛型出現之后痹届,上面的代碼就改成了大家都熟知的寫法:
public static void main(String[] args)
{
List<String> list = new ArrayList<String>();
list.add("123");
list.add("456");
System.out.println(list.get(0));
}
這就是泛型。泛型是對Java語言類型系統(tǒng)的一種擴展打月,有點類似于C++的模板队腐,可以把類型參數看作是使用參數化類型時指定的類型的一個占位符。引入泛型奏篙,是對Java語言一個較大的功能增強柴淘,帶來了很多的好處。
3.泛型的好處
①類型安全。類型錯誤現在在編譯期間就被捕獲到了为严,而不是在運行時當作java.lang.ClassCastException展示出來敛熬,將類型檢查從運行時挪到編譯時有助于開發(fā)者更容易找到錯誤,并提高程序的可靠性第股。
②消除了代碼中許多的強制類型轉換荸型,增強了代碼的可讀性。
③為較大的優(yōu)化帶來了可能炸茧。
二、泛型的使用
1.泛型類和泛型接口
下面是JDK 1.5 以后稿静,List接口梭冠,以及ArrayList類的代碼片段。
//定義接口時指定了一個類型形參改备,該形參名為E
public interface List<E> extends Collection<E> {
//在該接口里控漠,E可以作為類型使用
public E get(int index) {}
public void add(E e) {}
}
//定義類時指定了一個類型形參,該形參名為E
public class ArrayList<E> extends AbstractList<E> implements List<E>{
//在該類里悬钳,E可以作為類型使用
public void set(E e) {
.......................
}
}
這就是泛型的實質:允許在定義接口盐捷、類時聲明類型形參,類型形參在整個接口默勾、類體內可當成類型使用碉渡,幾乎所有可使用普通類型的地方都可以使用這種類型形參。
下面具體講解泛型類的使用母剥。泛型接口的使用與泛型類幾乎相同滞诺,可以比對自行學習。
泛型類
定義一個容器類环疼,存放鍵值對key-value习霹,鍵值對的類型不確定,可以使用泛型來定義炫隶,分別指定為K和V
public class Container<K, V> {
private K key;
private V value;
public Container(K k, V v) {
key = k;
value = v;
}
public K getkey() {
return key;
}
public V getValue() {
return value;
}
public void setKey() {
this.key = key;
}
public void setValue() {
this.value = value;
}
}
在使用Container類時淋叶,只需要指定K,V的具體類型即可伪阶,從而創(chuàng)建出邏輯上不同的Container實例煞檩,用來存放不同的數據類型。
public static void main(String[] args){
Container<String,String> c1=new Container<String ,String>("name","hello");
Container<String,Integer> c2=new Container<String,Integer>("age",22);
Container<Double,Double> c3=new Container<Double,Double>(1.1,1.3);
System.out.println(c1.getKey() + " : " + c1.getValue());
System.out.println(c2.getKey() + " : " + c2.getValue());
System.out.println(c3.getKey() + " : " + c3.getValue());
}
在JDK 1.7 增加了泛型的“菱形”語法:Java允許在構造器后不需要帶完成的泛型信息望门,只要給出一對尖括號(<>)即可形娇,Java可以推斷尖括號里應該是什么泛型信息。
如下所示:
Container<String,String> c1=new Container<>("name","hello");
Container<String,Integer> c2=new Container<>("age",22);
泛型類派生子類
當創(chuàng)建了帶泛型聲明的接口筹误、父類之后桐早,可以為該接口創(chuàng)建實現類,或者從該父類派生子類,需要注意:使用這些接口哄酝、父類派生子類時不能再包含類型形參友存,需要傳入具體的類型。
錯誤的方式:
public class A extends Container<K, V>{}
正確的方式:
public class A extends Container<Integer, String>{}
也可以不指定具體的類型陶衅,如下:
public class A extends Container{}
此時系統(tǒng)會把K,V形參當成Object類型處理屡立。
2.泛型的方法
前面在介紹泛型類和泛型接口中提到,可以在泛型類搀军、泛型接口的方法中膨俐,把泛型中聲明的類型形參當成普通類型使用。 如下面的方式:
public class Container<K, V> {
........................
public K getkey() {
return key;
}
public void setKey() {
this.key = key;
}
....................
}
但在另外一些情況下罩句,在類焚刺、接口中沒有使用泛型時,定義方法時想定義類型形參门烂,就會使用泛型方法乳愉。如下方式:
public class Main{
public static <T> void out(T t){
System.out.println(t);
}
public static void main(String[] args){
out("hansheng");
out(123);
}
}
所謂泛型方法,就是在聲明方法時定義一個或多個類型形參屯远。泛型方法的用法格式如下:
修飾符<T, S> 返回值類型 方法名(形參列表)
{
方法體
}
注意:方法聲明中定義的形參只能在該方法里使用蔓姚,而接口、類聲明中定義的類型形參則可以在整個接口慨丐、類中使用坡脐。
class Demo{
public <T> T fun(T t){ // 可以接收任意類型的數據
return t ; // 直接把參數返回
}
};
public class GenericsDemo26{
public static void main(String args[]){
Demo d = new Demo() ; // 實例化Demo對象
String str = d.fun("湯姆") ; // 傳遞字符串
int i = d.fun(30) ; // 傳遞數字,自動裝箱
System.out.println(str) ; // 輸出內容
System.out.println(i) ; // 輸出內容
}
};
當調用fun()方法時房揭,根據傳入的實際對象挨措,編譯器就會判斷出類型形參T所代表的實際類型。
3.泛型構造器
正如泛型方法允許在方法簽名中聲明類型形參一樣崩溪,Java也允許在構造器簽名中聲明類型形參浅役,這樣就產生了所謂的泛型構造器。
和使用普通泛型方法一樣沒區(qū)別伶唯,一種是顯式指定泛型參數觉既,另一種是隱式推斷,如果是顯式指定則以顯式指定的類型參數為準乳幸,如果傳入的參數的類型和指定的類型實參不符瞪讼,將會編譯報錯。
public class Person {
public <T> Person(T t) {
System.out.println(t);
}
}
public static void main(String[] args){
//隱式
new Person(22);
//顯示
new<String>Person("hello");
}
這里唯一需要特殊注明的就是粹断,如果構造器是泛型構造器符欠,同時該類也是一個泛型類的情況下應該如何使用泛型構造器:
因為泛型構造器可以顯式指定自己的類型參數(需要用到菱形,放在構造器之前)瓶埋,而泛型類自己的類型實參也需要指定(菱形放在構造器之后)希柿,這就同時出現了兩個菱形了诊沪,這就會有一些小問題,具體用法再這里總結一下曾撤。
以下面這個例子為代表
public class Person<E> {
public <T> Person(T t) {
System.out.println(t);
}
}
這種用法:Person<String> a = new <Integer>Person<>(15); 這種語法不允許端姚,會直接編譯報錯!
三挤悉、類型通配符
顧名思義就是匹配任意類型的類型實參渐裸。
類型通配符是一個問號(?)装悲,將一個問號作為類型實參傳給List集合昏鹃,寫作:List<?>(意思是元素類型未知的List)。這個問號(诀诊?)被成為通配符盆顾,它的元素類型可以匹配任何類型。
public void test(List<?> c){
for(int i =0;i<c.size();i++){
System.out.println(c.get(i));
}
}
現在可以傳入任何類型的List來調用test()方法畏梆,程序依然可以訪問集合c中的元素,其類型是Object奈懒。
List<?> c = new ArrayList<String>();
//編譯器報錯
c.add(new Object());
但是并不能把元素加入到其中奠涌。因為程序無法確定c集合中元素的類型,所以不能向其添加對象磷杏。
下面就該引入帶限通配符溜畅,來確定集合元素中的類型。
帶限通配符
簡單來講极祸,使用通配符的目的是來限制泛型的類型參數的類型慈格,使其滿足某種條件,固定為某些類遥金。
主要分為兩類即:上限通配符和下限通配符浴捆。
1.上限通配符
如果想限制使用泛型類別時,只能用某個特定類型或者是其子類型才能實例化該類型時稿械,可以在定義類型時选泻,使用extends關鍵字指定這個類型必須是繼承某個類,或者實現某個接口美莫,也可以是這個類或接口本身页眯。
它表示集合中的所有元素都是Shape類型或者其子類
List<? extends Shape>
這就是所謂的上限通配符,使用關鍵字extends來實現厢呵,實例化時窝撵,指定類型實參只能是extends后類型的子類或其本身。
例如:
//Circle是其子類
List<? extends Shape> list = new ArrayList<Circle>();
這樣就確定集合中元素的類型襟铭,雖然不確定具體的類型碌奉,但最起碼知道其父類短曾。然后進行其他操作。
2.下限通配符
如果想限制使用泛型類別時道批,只能用某個特定類型或者是其父類型才能實例化該類型時错英,可以在定義類型時,使用super關鍵字指定這個類型必須是是某個類的父類隆豹,或者是某個接口的父接口椭岩,也可以是這個類或接口本身。
它表示集合中的所有元素都是Circle類型或者其父類
List<? super Circle>
這就是所謂的下限通配符璃赡,使用關鍵字super來實現判哥,實例化時,指定類型實參只能是extends后類型的子類或其本身碉考。
例如:
//Shape是其父類
List<? super Circle> list = new ArrayList<Shape>();
四塌计、類型擦除
Class c1=new ArrayList<Integer>().getClass();
Class c2=new ArrayList<String>().getClass();
System.out.println(c1==c2);
程序輸出:
true。
這是因為不管為泛型的類型形參傳入哪一種類型實參侯谁,對于Java來說锌仅,它們依然被當成同一類處理,在內存中也只占用一塊內存空間墙贱。從Java泛型這一概念提出的目的來看热芹,其只是作用于代碼編譯階段,在編譯過程中惨撇,對于正確檢驗泛型結果后伊脓,會將泛型的相關信息擦出,也就是說魁衙,成功編譯過后的class文件中是不包含任何泛型信息的报腔。泛型信息不會進入到運行時階段。
在靜態(tài)方法剖淀、靜態(tài)初始化塊或者靜態(tài)變量的聲明和初始化中不允許使用類型形參纯蛾。由于系統(tǒng)中并不會真正生成泛型類,所以instanceof運算符后不能使用泛型類纵隔。