1. 程式人生 > >Java泛型(一) 泛型的基本使用和介紹

Java泛型(一) 泛型的基本使用和介紹

轉載自:http://blog.csdn.net/lonelyroamer/article/details/7864531

現在開始深入學習java的泛型了,以前一直只是在集合中簡單的使用泛型,根本就不明白泛型的原理和作用。泛型在java中,是一個十分重要的特性,所以要好好的研究下。

一、泛型的基本概念

 泛型的定義:泛型是JDK 1.5的一項新特性,它的本質是引數化型別(Parameterized Type)的應用,也就是說所操作的資料型別被指定為一個引數,在用到的時候在指定具體的型別。這種引數型別可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面和泛型方法。

  泛型思想早在C++語言的模板(Templates)中就開始生根發芽,在Java語言處於還沒有出現泛型的版本時,只能通過Object是所有型別的父類和型別強制轉換兩個特點的配合來實現型別泛化。例如在雜湊表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一個Object物件,由於Java語言裡面所有的型別都繼承於java.lang.Object,那Object轉型為任何物件成都是有可能的。但是也因為有無限的可能性,就只有程式設計師和執行期的虛擬機器才知道這個Object到底是個什麼型別的物件。在編譯期間,編譯器無法檢查這個Object的強制轉型是否成功,如果僅僅依賴程式設計師去保障這項操作的正確性,許多ClassCastException的風險就會被轉嫁到程式執行期之中。

  泛型技術在C#和Java之中的使用方式看似相同,但實現上卻有著根本性的分歧,C#裡面泛型無論在程式原始碼中、編譯後的IL中(Intermediate Language,中間語言,這時候泛型是一個佔位符)或是執行期的CLR中都是切實存在的,List<int>與List<String>就是兩個不同的型別,它們在系統執行期生成,有自己的虛方法表和型別資料,這種實現稱為型別膨脹,基於這種方法實現的泛型被稱為真實泛型。

  Java語言中的泛型則不一樣,它只在程式原始碼中存在,在編譯後的位元組碼檔案中,就已經被替換為原來的原始型別(Raw Type,也稱為裸型別)了,並且在相應的地方插入了強制轉型程式碼,因此對於執行期的Java語言來說,ArrayList<int>與ArrayList<String>就是同一個類。所以說泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為型別擦除,基於這種方法實現的泛型被稱為偽泛型。(型別擦除在後面在學習)

  使用泛型機制編寫的程式程式碼要比那些雜亂的使用Object變數,然後再進行強制型別轉換的程式碼具有更好的安全性和可讀性。泛型對於集合類來說尤其有用。

  泛型程式設計(Generic Programming)意味著編寫的程式碼可以被很多不同型別的物件所重用。

例項分析:

  在JDK1.5之前,Java泛型程式設計是用繼承來實現的。因為Object類是所用類的基類,所以只需要維持一個Object型別的引用即可。就比如ArrayList只維護一個Object引用的陣列:

  1. publicclass ArrayList//JDK1.5之前的
  2. {  
  3.     public Object get(
    int i){......}  
  4.     publicvoid add(Object o){......}  
  5.     ......  
  6.     private Object[] elementData;  
  7. }  
這樣會有兩個問題:

1、沒有錯誤檢查,可以向陣列列表中新增類的物件

2、在取元素的時候,需要進行強制型別轉換

這樣,很容易發生錯誤,比如:

  1. /**jdk1.5之前的寫法,容易出問題*/
  2. ArrayList arrayList1=new ArrayList();  
  3. arrayList1.add(1);  
  4. arrayList1.add(1L);  
  5. arrayList1.add("asa");  
  6. int i=(Integer) arrayList1.get(1);//因為不知道取出來的值的型別,型別轉換的時候容易出錯
這裡的第一個元素是一個長整型,而你以為是整形,所以在強轉的時候發生了錯誤。

所以。在JDK1.5之後,加入了泛型來解決類似的問題。例如在ArrayList中使用泛型:

  1. /** jdk1.5之後加入泛型*/
  2.         ArrayList<String> arrayList2=new ArrayList<String>();  //限定陣列列表中的型別
  3. //      arrayList2.add(1); //因為限定了型別,所以不能新增整形
  4. //      arrayList2.add(1L);//因為限定了型別,所以不能新增整長形
  5.         arrayList2.add("asa");//只能新增字串
  6.         String str=arrayList2.get(0);//因為知道取出來的值的型別,所以不需要進行強制型別轉換

還要明白的是,泛型特性是向前相容的。儘管 JDK 5.0 的標準類庫中的許多類,比如集合框架,都已經泛型化了,但是使用集合類(比如 HashMap 和 ArrayList)的現有程式碼可以繼續不加修改地在 JDK 1.5 中工作。當然,沒有利用泛型的現有程式碼將不會贏得泛型的型別安全的好處。

在學習泛型之前,簡單介紹下泛型的一些基本術語,以ArrayList<E>ArrayList<Integer>做簡要介紹:

整個成為ArrayList<E>泛型型別

ArrayList<E>中的 E稱為型別變數或者型別引數

整個ArrayList<Integer> 稱為引數化的型別

ArrayList<Integer>中的integer稱為型別引數的例項或者實際型別引數

·ArrayList<Integer>中的<Integer>念為typeof   Integer

ArrayList稱為原始型別

二、泛型的使用

泛型的引數型別可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面和泛型方法。下面看看具體是如何定義的。

1、泛型類的定義和使用

一個泛型類(generic class)就是具有一個或多個型別變數的類。定義一個泛型類十分簡單,只需要在類名後面加上<>,再在裡面加上型別引數:

  1. class Pair<T> {  
  2.     private T value;  
  3.         public Pair(T value) {  
  4.                 this.value=value;  
  5.         }  
  6.         public T getValue() {  
  7.         return value;  
  8.     }  
  9.     publicvoid setValue(T value) {  
  10.         this.value = value;  
  11.     }  
  12. }  
現在我們就可以使用這個泛型類了:
  1. publicstaticvoid main(String[] args) throws ClassNotFoundException {  
  2.         Pair<String> pair=new Pair<String>("Hello");  
  3.         String str=pair.getValue();  
  4.         System.out.println(str);  
  5.         pair.setValue("World");  
  6.         str=pair.getValue();  
  7.         System.out.println(str);  
  8.     }  

Pair類引入了一個型別變數T,用尖括號<>括起來,並放在類名的後面。泛型類可以有多個型別變數。例如,可以定義Pair類,其中第一個域和第二個域使用不同的型別:

public class Pair<T,U>{......}

注意:型別變數使用大寫形式,且比較短,這是很常見的。在Java庫中,使用變數E表示集合的元素型別,K和V分別表示關鍵字與值的型別。(需要時還可以用臨近的字母U和S)表示“任意型別”。

2、泛型介面的定義和使用

定義泛型介面和泛型類差不多,看下面簡單的例子:
  1. interface Show<T,U>{  
  2.     void show(T t,U u);  
  3. }  
  4. class ShowTest implements Show<String,Date>{  
  5.     @Override
  6.     publicvoid show(String str,Date date) {  
  7.         System.out.println(str);  
  8.         System.out.println(date);  
  9.     }  
  10. }  
測試一下:
  1. publicstaticvoid main(String[] args) throws ClassNotFoundException {  
  2.         ShowTest showTest=new ShowTest();  
  3.         showTest.show("Hello",new Date());  
  4.     }  

3、泛型方法的定義和使用

泛型類在多個方法簽名間實施型別約束。在 List<V> 中,型別引數 V 出現在 get()、add()、contains() 等方法的簽名中。當建立一個 Map<K, V> 型別的變數時,您就在方法之間宣稱一個型別約束。您傳遞給 add() 的值將與 get() 返回的值的型別相同。

類似地,之所以宣告泛型方法,一般是因為您想要在該方法的多個引數之間宣稱一個型別約束。

舉個簡單的例子:

  1. publicstaticvoid main(String[] args) throws ClassNotFoundException {  
  2.         String str=get("Hello""World");  
  3.         System.out.println(str);  
  4.     }  
  5.     publicstatic <T, U> T get(T t, U u) {  
  6.         if (u != null)  
  7.             return t;  
  8.         else
  9.             returnnull;  
  10.     }  

三、泛型變數的型別限定

在上面,我們簡單的學習了泛型類、泛型介面和泛型方法。我們都是直接使用<T>這樣的形式來完成泛型型別的宣告。

有的時候,類、介面或方法需要對型別變數加以約束。看下面的例子:

有這樣一個簡單的泛型方法:
  1. publicstatic <T> T get(T t1,T t2) {  
  2.         if(t1.compareTo(t2)>=0);//編譯錯誤
  3.         return t1;  
  4.     }  
因為,在編譯之前,也就是我們還在定義這個泛型方法的時候,我們並不知道這個泛型型別T,到底是什麼型別,所以,只能預設T為原始型別Object。所以它只能呼叫來自於Object的那幾個方法,而不能呼叫compareTo方法。

可我的本意就是要比較t1和t2,怎麼辦呢?這個時候,就要使用型別限定,對型別變數T設定限定(bound)來做到這一點。

我們知道,所有實現Comparable介面的方法,都會有compareTo方法。所以,可以對<T>做如下限定:

  1. publicstatic <T extends Comparable> T get(T t1,T t2) { //新增型別限定
  2.         if(t1.compareTo(t2)>=0);  
  3.         return t1;  
  4.     }  

型別限定在泛型類、泛型介面和泛型方法中都可以使用,不過要注意下面幾點:

1、不管該限定是類還是介面,統一都使用關鍵字 extends

2、可以使用&符號給出多個限定,比如

  1. publicstatic <T extends Comparable&Serializable> T get(T t1,T t2)  
3、如果限定既有介面也有類,那麼類必須只有一個,並且放在首位置
  1. publicstatic <T extends Object&Comparable&Serializable> T get(T t1,T t2)