更深入地瞭解泛型:
感謝原文作者:http://blog.csdn.net/lonelyroamer/article/details/7864531
作者還有兩篇更深入的:
java泛型(二)、泛型的內部原理:類型擦除以及類型擦除帶來的問題
現在開始深入學習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引用的數組:
- public class ArrayList//JDK1.5之前的
- {
- public Object get(int i){......}
- public void add(Object o){......}
- ......
- private Object[] elementData;
- }
1、沒有錯誤檢查,可以向數組列表中添加類的對象
2、在取元素的時候,需要進行強制類型轉換
這樣,很容易發生錯誤,比如:
- /**jdk1.5之前的寫法,容易出問題*/
- ArrayList arrayList1=new ArrayList();
- arrayList1.add(1);
- arrayList1.add(1L);
- arrayList1.add("asa");
- int i=(Integer) arrayList1.get(1);//因爲不知道取出來的值的類型,類型轉換的時候容易出錯
所以。在JDK1.5之後,加入了泛型來解決類似的問題。例如在ArrayList中使用泛型:
- /** jdk1.5之後加入泛型*/
- ArrayList<String> arrayList2=new ArrayList<String>(); //限定數組列表中的類型
- // arrayList2.add(1); //因爲限定了類型,所以不能添加整形
- // arrayList2.add(1L);//因爲限定了類型,所以不能添加整長形
- arrayList2.add("asa");//只能添加字符串
- 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)就是具有一個或多個類型變量的類。定義一個泛型類十分簡單,只需要在類名後面加上<>,再在裏面加上類型參數:
Pair類引入了一個類型變量T,用尖括號<>括起來,並放在類名的後面。泛型類可以有多個類型變量。例如,可以定義Pair類,其中第一個域和第二個域使用不同的類型:
public class Pair<T,U>{......}
注意:類型變量使用大寫形式,且比較短,這是很常見的。在Java庫中,使用變量E表示集合的元素類型,K和V分別表示關鍵字與值的類型。(需要時還可以用臨近的字母U和S)表示“任意類型”。
2、泛型接口的定義和使用
- interface Show<T,U>{
- void show(T t,U u);
- }
- class ShowTest implements Show<String,Date>{
- @Override
- public void show(String str,Date date) {
- System.out.println(str);
- System.out.println(date);
- }
- }
- public static void main(String[] args) throws ClassNotFoundException {
- ShowTest showTest=new ShowTest();
- showTest.show("Hello",new Date());
- }
3、泛型方法的定義和使用
泛型類在多個方法簽名間實施類型約束。在 List<V> 中,類型參數 V 出現在 get()、add()、contains() 等方法的簽名中。當創建一個 Map<K, V> 類型的變量時,您就在方法之間宣稱一個類型約束。您傳遞給 add() 的值將與 get() 返回的值的類型相同。
類似地,之所以聲明泛型方法,一般是因爲您想要在該方法的多個參數之間宣稱一個類型約束。
舉個簡單的例子:
- public static void main(String[] args) throws ClassNotFoundException {
- String str=get("Hello", "World");
- System.out.println(str);
- }
- public static <T, U> T get(T t, U u) {
- if (u != null)
- return t;
- else
- return null;
- }
三、泛型變量的類型限定
在上面,我們簡單的學習了泛型類、泛型接口和泛型方法。我們都是直接使用<T>這樣的形式來完成泛型類型的聲明。
有的時候,類、接口或方法需要對類型變量加以約束。看下面的例子:
有這樣一個簡單的泛型方法:
- public static <T> T get(T t1,T t2) {
- if(t1.compareTo(t2)>=0);//編譯錯誤
- return t1;
- }
可我的本意就是要比較t1和t2,怎麼辦呢?這個時候,就要使用類型限定,對類型變量T設置限定(bound)來做到這一點。
我們知道,所有實現Comparable接口的方法,都會有compareTo方法。所以,可以對<T>做如下限定:
- public static <T extends Comparable> T get(T t1,T t2) { //添加類型限定
- if(t1.compareTo(t2)>=0);
- return t1;
- }
類型限定在泛型類、泛型接口和泛型方法中都可以使用,不過要注意下面幾點:
1、不管該限定是類還是接口,統一都使用關鍵字 extends
2、可以使用&符號給出多個限定,比如
- public static <T extends Comparable&Serializable> T get(T t1,T t2)
- public static <T extends Object&Comparable&Serializable> T get(T t1,T t2)