Java泛型詳解

一、泛型簡介

1.引入泛型的目的

了解引入泛型的動機,就先從語法糖開始了解。

語法糖

語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機學家Peter.J.Landin發明的一個術語,指在計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。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語言類型系統的一種擴展,有點類似于C++的模板,可以把類型參數看作是使用參數化類型時指定的類型的一個占位符。引入泛型,是對Java語言一個較大的功能增強,帶來了很多的好處。

3.泛型的好處

①類型安全。類型錯誤現在在編譯期間就被捕獲到了,而不是在運行時當作java.lang.ClassCastException展示出來,將類型檢查從運行時挪到編譯時有助于開發者更容易找到錯誤,并提高程序的可靠性。

②消除了代碼中許多的強制類型轉換,增強了代碼的可讀性。

③為較大的優化帶來了可能。

二、泛型的使用

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的具體類型即可,從而創建出邏輯上不同的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);

泛型類派生子類

當創建了帶泛型聲明的接口、父類之后,可以為該接口創建實現類,或者從該父類派生子類,需要注意:使用這些接口、父類派生子類時不能再包含類型形參,需要傳入具體的類型。
錯誤的方式:

public class A extends Container<K, V>{}

正確的方式:

public class A extends Container<Integer, String>{}

也可以不指定具體的類型,如下:

public class A extends Container{}

此時系統會把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也允許在構造器簽名中聲明類型形參,這樣就產生了所謂的泛型構造器。
和使用普通泛型方法一樣沒區別,一種是顯式指定泛型參數,另一種是隱式推斷,如果是顯式指定則以顯式指定的類型參數為準,如果傳入的參數的類型和指定的類型實參不符,將會編譯報錯。

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文件中是不包含任何泛型信息的。泛型信息不會進入到運行時階段。

在靜態方法、靜態初始化塊或者靜態變量的聲明和初始化中不允許使用類型形參。由于系統中并不會真正生成泛型類,所以instanceof運算符后不能使用泛型類。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容

  • 2.6 Java泛型詳解 Java泛型是JDK5中引入的一個新特性,允許在定義類和接口的時候使用類型參數(type...
    jianhuih閱讀 695評論 0 3
  • 一、定義 Java泛型是JDK5中引入的新特性,提供在編譯時類型安全監測機制,本質是參數化類型(即所操作的數據類型...
    MrTrying閱讀 1,191評論 0 2
  • 1. 概述 泛型在java中有很重要的地位,在面向對象編程及各種設計模式中有非常廣泛的應用。 什么是泛型?為什么要...
    pyx0225閱讀 340評論 0 1
  • object 變量可指向任何類的實例,這讓你能夠創建可對任何數據類型進程處理的類。然而,這種方法存在幾個嚴重的問題...
    CarlDonitz閱讀 927評論 0 5
  • 泛型 泛型由來 泛型字面意思不知道是什么類型,但又好像什么類型都是。看前面用到的集合都有泛型的影子。 以Array...
    向日花開閱讀 2,206評論 2 6