1. 程式人生 > >java泛型是如何工作的,為什麼泛型這麼重要

java泛型是如何工作的,為什麼泛型這麼重要

在javaSE8中有很多值得我們興奮的地方,在新的版本中新的或者更新的特徵允許開發者以更有效的、更簡潔的方式寫程式碼。為了完全瞭解一些新特徵的實現,比如lambdas,理解java的核心概念就變得十分重要了,在javaSE8中扮演這個核心角色之一的就是泛型。

這篇文章一開始對泛型進行了一個簡單的介紹,並介紹了一些基本的概念。在介紹完基本概念之後我們將深入泛型的一些具體應用場景,最後我們看一下為什麼泛型對於一些構造是非常重要的組成部分。

這篇文章用到的所有程式碼可以在這裡下載。

什麼是泛型

考慮下邊這個場景:你希望開發一個容器用來傳遞你應用程式中的物件。但是物件的型別並不是每次都相同的,因此,你需要開發一個能夠處理不同型別物件的容器。考慮這個場景,達到這個目標最顯然的方式是容器可以儲存和恢復物件的型別,然後強制轉換這個物件,下邊這段程式碼就有這樣的實現:

public class ObjectContainer {
    private Object obj;

    /**
     * @return the obj
     */
    public Object getObj() {
        return obj;
    }

    /**
     * @param obj the obj to set
     */
    public void setObj(Object obj) {
        this.obj = obj;
    }
    

儘管上邊這段程式碼可以達到我們想要的結果,但是對於我們的目的卻也不是最適合的方式,因為這可能會引起異常的發生,它不是型別安全的並且當恢復物件的時候需要精確的強轉,下邊這段程式碼顯示了容器儲存和恢復值的過程
ObjectContainer myObj = new ObjectContainer();

// store a string
myObj.setObj("Test");
System.out.println("Value of myObj:" + myObj.getObj());
// store an int (which is autoboxed to an Integer object)
myObj.setObj(3);
System.out.println("Value of myObj:" + myObj.getObj());

List objectList = new ArrayList();
objectList.add(myObj);
// We have to cast and must cast the correct type to avoid ClassCastException!
String myStr = (String) ((ObjectContainer)objectList.get(0)).getObj(); 
System.out.println("myStr: " + myStr);

在開發這個容器時,泛型是一個更好的選擇,可以有一個型別用來例項化,或者指定為泛型型別,允許建立的物件儲存指定的型別。泛型型別是一個類或者是一個介面,通過型別引數化,這意味著型別可以通過執行泛型型別呼叫指定,會用具體的型別替代泛型型別。指定的型別就可以用來限制在容器中使用的值,這個值是消除了強制轉化的,同時在編譯時提供了更強的型別檢查

下邊這段程式碼展示了怎麼建立一個容器,但是這次使用了泛型型別作為引數而不是Object型別

public class GenericContainer<T> {
    private T obj;

    public GenericContainer(){
    }
    
    // Pass type in as parameter to constructor
    public GenericContainer(T t){
        obj = t;
    }

    /**
     * @return the obj
     */
    public T getObj() {
        return obj;
    }

    /**
     * @param obj the obj to set
     */
    public void setObj(T t) {
        obj = t;
    }
}

最顯著的區別是類的定義中包含<T>,並且類的欄位 obj 不再是Object型別了,而是泛型型別T。類的定義包含了型別引數區,引入在本類中將要用到的型別引數。T是一個和泛型型別關聯的引數。

為了使用這個泛型容器,你必須指定容器的型別。因此下邊這段程式碼將會用型別Integer來指定泛型容器的型別。

GenericContainer<Integer> myInt =  new GenericContainer<Integer>();

如果我們試著在上邊例項化的這個容器中存入其他型別,這段程式碼便不會編譯通過:
myInt.setObj(3);  // OK
myInt.setObj("Int"); // Won't Compile

使用泛型的好處

我們在上邊的例子中已經看到了使用泛型的一些好處。更強的型別檢查是最重要的特徵之一,因為它省去了檢查執行時型別錯誤的異常處理。另一個好處就是消除了強制轉換的存在,這意味著你可以寫更少的程式碼,因為編譯器清楚的知道在集合中儲存的型別。例如下邊這段程式碼,我們來看一下在集合中儲存物件例項和使用GenericContainer儲存物件例項的區別
List myObjList = new ArrayList();

// Store instances of ObjectContainer
for(int x=0; x <=10; x++){
    ObjectContainer myObj = new ObjectContainer();
    myObj.setObj("Test" + x);
    myObjList.add(myObj);
}
// Get the objects we need to cast
for(int x=0; x <= myObjList.size()-1; x++){
    ObjectContainer obj = (ObjectContainer) myObjList.get(x); 
    System.out.println("Object Value: " + obj.getObj());
}

List<GenericContainer> genericList = new ArrayList<GenericContainer>();

// Store instances of GenericContainer
for(int x=0; x <=10; x++){
    GenericContainer<String> myGeneric = new GenericContainer<String>();
    myGeneric.setObj(" Generic Test" + x);
    genericList.add(myGeneric);
}
// Get the objects; no need to cast to String

for(GenericContainer<String> obj:genericList){
    String objectString = obj.getObj();
    // Do something with the string...here we will print it
    System.out.println(objectString);
}

注意但我們使用ArrayList的時候,我們可以通過使用(<GenericContainer>) 指定集合的型別來表明我們儲存的是GenericContainer型別例項,集合僅會儲存GenericContainer例項(或者他的子類),當在集合中取資料的時候沒有必要再進行強制轉換了。在集合中使用泛型揭示了泛型的另一個好處,他們允許我們利用泛型演算法來定製適合我們任務的泛型,集合的API使用了泛型,不適用泛型,集合API永遠也不會適應引數化的型別

深入泛型

下邊這些會更深入的探索泛型的一些特徵

你怎麼使用泛型

泛型的用法有非常多的種類,這篇文章的第2個例子說明了產生泛型物件型別的一個用例。在類或者介面級別上學習泛型的語法是一個很好的開始,看看這個程式碼,類的簽名包含型別引數區,它被包含在名字後的<>中,例如:
public class GenericContainer<T> {
...

型別引數,也叫作型別變數,用來作為指定在執行時將要分配給類的型別的佔位符,可以有一個或者是多個型別引數,也可以通過類被使用。習慣上講,型別引數是一個大寫字母組成的,用來指定被定義的引數型別,下邊的列表包含的標準的一些用法:
E: Element
K: Key
N: Number
T: Type
V: Value
S, U, V, and so on: Second, third, and fourth types in a multiparameter situation

在上邊的例子中,T指定了將要配分配的型別,那麼GenericContainer在例項化的時候可以被指定為任何型別。注意T引數通過類使用表明在例項化的時候指定型別,當使用下邊這段程式碼的時候,每一個T引數都會被替代為String型別
GenericContainer<String> stringContainer = new GenericContainer<String>();

泛型在建構函式中同樣也是非常有用的,用來為類欄位初始化傳遞引數,GenericContainer有一個建構函式允許在例項化的時候傳遞任何引數:
GenericContainer gc1 = new GenericContainer(3);
GenericContainer gc2 = new GenericContainer("Hello");

注意沒有指定型別的泛型叫做原生型別,例如,建立GenericContainer一個原生型別,請看下邊這段程式碼:
GenericContainer rawContainer = new GenericContainer();

原生型別有時對向後相容是很有用的,但是在每一段程式碼中使用卻不是一個很好的習慣,原生型別消除了在編譯時期的型別檢查,是程式碼更容易出錯。

泛型的多型別

有時在介面或者類中使用多於一個泛型型別是很有好處的,多種型別的引數可以被用在類或者介面中通過替換在尖括號中的引數。下邊的程式碼展示了這個概念,它使用了兩種型別:T和S。 如果我們看看之前列出的引數列表,T是第一個型別,S是第二個型別,這兩個型別用來利用泛型儲存多種型別的值
public class MultiGenericContainer<T, S> {
    private T firstPosition;
    private S secondPosition;
   
    public MultiGenericContainer(T firstPosition, S secondPosition){
        this.firstPosition = firstPosition;
        this.secondPosition = secondPosition;
    }
    
    public T getFirstPosition(){
        return firstPosition;
    }
    
    public void setFirstPosition(T firstPosition){
        this.firstPosition = firstPosition;
    }
    
    public S getSecondPosition(){
        return secondPosition;
    }
    
    public void setSecondPosition(S secondPosition){
        this.secondPosition = secondPosition;
    }
    
}

MultiGenericContainer 類用來儲存兩種不同的物件,每一種物件的型別可以在例項化的時候指定,容器的使用如下:
MultiGenericContainer<String, String> mondayWeather =
        new MultiGenericContainer<String, String>("Monday", "Sunny");
MultiGenericContainer<Integer, Double> dayOfWeekDegrees = 
        new MultiGenericContainer<Integer, Double>(1, 78.0);

String mondayForecast = mondayWeather.getFirstPosition();
// The Double type is unboxed--to double, in this case. More on this in next section!
double sundayDegrees = dayOfWeekDegrees.getSecondPosition();

說明:本文有選擇性的翻譯了文章中的一部分,關於全文的內容,讀者可以訪問下邊的連結檢視原文。

原文地址:http://www.oracle.com/technetwork/articles/java/juneau-generics-2255374.html

其他資料:https://dzone.com/articles/5-things-you-should-know-about-java-generics

 http://www.journaldev.com/1663/java-generics-tutorial-example-class-interface-methods-wildcards-and-much-more

 http://blog.csdn.net/wangjian223344/article/details/16846165

http://www.imooc.com/article/1935