Java:泛型

1、序言html

變化一:前端

在引入範型以前,Java中的類型分爲原始類型、複雜類型,其中複雜類型分爲數組和類;引入範型後,一個複雜類型能夠細分紅更多的類型。java

例如,原先的List類型,如今細分紅List<Object>, List<String>等更多的類型。sql

注:List<Object>和List<String>是兩種不一樣的類型, 它們之間沒有繼承關係,即便String繼承了Object。下述代碼是非法的:數據庫

List<String> ls = new ArrayList<String>();
List<Object> lo = ls;

這樣設計的緣由:根據lo的聲明,編譯器容許你向lo中添加任意對象(例如Integer),可是爲其賦值List<String>類型的值,破壞了數據類型的完整性。數組

 

變化二:安全

在引入範型以前,要讓類中的方法支持多個數據類型,就須要對方法進行重載;在引入範型以後,能夠更進一步定義多個參數以及返回值之間的關係。框架

例如,public void write(Integer i, Integer[] ia);及public void write(Double  d, Double[] da);的範型版本爲:public <T> void write(T t, T[] ta);工具

 

2、詳述Java泛型this

在細說Java泛型以前,你們能夠先看一下以下這樣一段代碼,相信你們確定不會感受陌生:

package com.soft;

public class WW {
    public <T> void write(T t, T[] ta){
        
    }
}

在編碼時,可常常見到相似於上面這樣的代碼,接下來將詳述Java泛型。

那麼咱們首先要知道什麼是泛型,泛型即「參數化類型」,顧名思義,就是將類型由原來具體的類型變爲參數化的形式。在定義泛型接口、泛型類和泛型方法的過程當中,咱們常見的T、E、S等形式的參數用於表示泛型形參,用於接收來自外部使用時候傳入的類型實參。

 

A、自定義泛型類和泛型方法

(1)定義帶類型參數的類

package com.soft;

public class MM<T, S extends T> {
    public T say(){
        T t=null;
        return t; 
    }
}

規則:在定義帶類型參數的類時,在緊跟類命以後的<>內,指定一個或多個類型參數的名字,同時也能夠對類型參數的取值範圍進行限定,多個類型參數之間用","進行分隔。

說明:定義完類型參數後,能夠在類中定義位置以後的幾乎任意地方使用類型參數(靜態塊,靜態屬性,靜態方法除外),就像使用普通的類型同樣。

注意:父類定義的類型參數不能被子類繼承。

 

(2)定義帶類型參數方法

package com.soft;

public class WW {
    public <T> void write(T t, T[] ta){
        
    }
    public <S> void read(S s, S[] sa){
        
    }
    public <T, S extends T> T test(T t, S s){
        S ss=null;
     return ss; } }

規則:在定義帶類型參數的方法時,在緊跟可見範圍修飾(如public)以後的<>內,指定一個或多個類型參數的名字,同時也能夠對類型參數的取值範圍進行限定,多個類型參數之間用","進行分隔。

說明: 定義完帶類型參數的方法後,能夠在方法中定義位置以後的任意地方使用類型參數,就像使用普通的類型同樣。

注意:定義帶類型參數的方法,其主要目的是爲了表達多個參數以及返回值之間的關係。例如本例中T和S爲繼承關係,返回值的類型(T)和第一個類型參數(t)的類型(T)相同。

 

補充:

當咱們須要一個在邏輯上能夠用來表示同時是List<Integer>和List<Number>的類型時,類型通配符應運而生。類型通配符通常是使用"?"代替具體的類型實參。

若是僅僅是想實現多態,請優先使用通配符解決。

初始代碼以下:

public <T> void test1(List<T> u){
     ...
}
public <S> void test2(List<S> u){
     ...
}

使用通配符的代碼以下:

public void test(List<?> s){
     ...
 }

 

B、泛型類和泛型方法的使用

(1)對帶類型參數的類進行賦值

 對帶類型參數的類進行賦值有兩種方式:

A、聲明類變量或者實例化時,以下:

List<String> list;
list = new ArrayList<String>();

以下賦值方式會報錯:

List<Integer> a = new ArrayList<Integer>(712);
List<Number> b = a;

List<Number>不能視爲List<Integer>的父類,請參照前面「序言->變化一」中的描述。

B、繼承類或者實現接口時,以下:

public class MyList<E> extends ArrayList<E> implements List<E> {...}

 

(2)對帶類型參數的方法進行賦值

當調用泛型方法時,編譯器自動對類型參數進行賦值,當不能成功賦值時報編譯錯誤,以下:

package com.soft;

import java.util.List;

public class WW {public <T, S extends T> T test(T t, S s){
        return s;
     }
    public <T> T test1(T t, List<T> list){
        return t;
     }
    public <T> T test2(List<T> list1, List<T> list2){
       return null;
    }
     
    public void common(){
         
         Object o = null;
         Integer i = null;
         Number n = null;
         test(n, i);//此時T爲Number, S爲Integer
         test(o, i);//T爲Object, S爲Integer
         
         List<Integer> list1 = null;
         List<Number> list2 = null;
         test1(i, list1);//此時T爲Integer,i爲Integer類型,list1爲List<Integer>類型
         test1(i, list2);//此時T爲Number,i爲Integer類型,list2爲List<Number>類型
test2(list1, list2);//編譯報錯
    }
    
}

請讀者自行分析上述代碼中着色語句爲何會編譯報錯,很簡單的哦O(∩_∩)O哈哈~

 

(3)通配符類型參數進行賦值

在上面兩小節中,對是類型參數賦予具體的值,除此,還能夠對類型參數賦予不肯定值

假設有以下定義:

List<?> unknownList;
List<? extends Number> unknownNumberList;
List<? super Integer> unknownBaseLineIntgerList; 

現對上述定義的變量賦值,以下:

List<String> listString = null;
unknownList = listString;

 

3、類型通配符上限與下限

有時候,咱們會聽到類型通配符上限和類型通配符下限這樣的說法,其實在前面的例子中有下面這樣一小段代碼,在該代碼中已經體現出一些上下限的思想

public <T, S extends T> T test(T t, S s){
      S ss=null;
    return ss;
}

此處咱們進一步說明

package com.soft;

public class GenericTest {

    public static void main(String[] args) {

        Student<String> name = new Student<String>("xiaoming");
        Student<Integer> age = new Student<Integer>(24);
        Student<Number> number = new Student<Number>(1001);

        getData(name);
        getData(age);
        getData(number);
        
        getUpperNumberData(name);//編譯報錯
        getUpperNumberData(age);
        getUpperNumberData(number);
    }

    public static void getData(Student<?> data) {
        System.out.println("data :" + data.getData());
    }
    
    public static void getUpperNumberData(Student<? extends Number> data){//此處限定類型實參只能是Number類及其子類
        System.out.println("data :" + data.getData());
    }

}

class Student<T> {

    private T data;

    public Student() {
    }

    public Student(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

}

我想你們經過上例確定能體會到什麼是類型通配符上限,類型通配符上限經過形如Student<? extends Number>形式定義,相對應的,類型通配符下限爲Student<? super Number>形式,其含義與類型通配符上限正好相反,在此不做過多闡述了。

 

4、注意事項

A、當對類或方法的類型參數進行賦值時,要求對全部的類型參數進行賦值,不然,將獲得一個編譯錯誤。

B、在Java集合框架中,對於參數值是未知類型的容器類,只能讀取其中的元素,不能向其中添加元素,由於其類型是未知,編譯器沒法識別添加元素的類型和容器的類型是否兼容,惟一的例外是NULL

C、可使用帶泛型參數的類聲明數組,卻不能夠用於建立數組

List<Integer>[] iListArray;
iListArray=new ArrayList[10];
iListArray=new ArrayList<Integer>[10];//編譯時錯誤

 

5、實現原理

(1)一個泛型類被其全部的實例共享

現有以下這樣一段代碼,其打印的結果是什麼?

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass());

或許你會說是false,but,you are wrong!它打印出true。咱們能夠發現雖然傳入的類型實參不一樣,但其生成的全部對象實例的類型是同樣的,即一個泛型類的全部實例在運行時具備相同的運行時類(class), 而無論他們的實際類型參數。

事實上,泛型之因此叫泛型,就是由於它對其全部可能的類型參數有一樣的行爲。類的靜態變量和靜態方法在全部的實例間共享,這就是爲何在靜態變量的聲明和初始化時或者在靜態方法或靜態初始化代碼中使用類型參數是不合法的緣由,類型參數是屬於具體實例的

 

(2)擦除(erasure)

  在使用泛型類時,雖然傳入了不一樣的泛型實參,但並無真正意義上生成不一樣的類型,能夠接收不一樣泛型實參的泛型類在內存中只有一個,是原來的最基本的類型(上例中爲List),固然,在邏輯上咱們能夠理解成多個不一樣的泛型類型。

  究其緣由,Java泛型是經過java編譯器的稱爲擦除(erasure)的前端處理來實現的,Java中的泛型只做用於代碼編譯階段,在編譯過程當中正確檢驗泛型結果後,會將泛型的相關信息擦除(erasure),也就是說,成功編譯以後的.class文件中是不包含任何泛型信息的,泛型信息不會進入運行時階段。你能夠把它認爲是(基本上就是)一個從源碼到源碼的轉換,它把泛型版本轉換成非泛型版本。

  基本上,擦除(erasure)去掉了全部的泛型類型信息,全部在尖括號之間的類型信息都被扔掉了,好比說,一個 List<String>類型被轉換爲List,全部對類型變量的引用被替換成類型變量的上限(一般是Object)。對此總結成一句話:泛型類型在邏輯上能夠當作是多個不一樣的類型,但實際上都是相同的基本類型。

 

(3)類型轉換

類型參數在運行時並不存在,這意味着它們不會添加任什麼時候間或者空間上的負擔,這很好,不幸的是,這也意味着你不能依靠它們進行類型轉換,下面這段代碼有以下圖所示的報錯信息:

public <T> T badCast(T t, Object o) {
      return (T) o; // unchecked warning
}

相似的,以下的類型轉換將獲得一個unchecked warning,由於運行時環境不會爲你作這樣的檢查。

Collection cs = new ArrayList<String>();
Collection<String> cstr = (Collection<String>) cs;

 

(4)instanceof

泛型類被其全部實例(instances)共享的另外一個暗示就是檢查一個實例是否是某一個特定類型的泛型類是沒有意義的,以下圖所示:

 

6、Class泛型的使用

  Java5中的一個變化是類java.lang.Class是泛型化的(Class<T>),這是把泛型擴展到容器類以外的一個頗有意思的例子。 你極可能會問,Class的類型參數T表明什麼?它表明Class對象表明的類型,好比說,Class對象String.class的類型爲Class<String>,Class對象Serializable.class的類型爲Class<Serializable>,此處T表明什麼請自行體會。

  Java5爲何會有這樣的變化,這能夠被用來提升你反射代碼的類型安全。特別的,因Class的newInstance()方法返回一個T,你能夠在使用反射建立對象時獲得更精確的類型。好比說,假定你要寫一個工具方法來進行數據庫查詢,給定一個SQL語句,請返回一個數據庫中符合查詢條件的對象集合(collection),你該怎麼作?

解決方案:

(1)定義接口:

interface Factory<T> {
      public T[] make();
}

(2)定義查詢方法:

public <T> Collection<T> select(Factory<T> factory, String statement) { 
       Collection<T> result = new ArrayList<T>();
       /* run sql query using jdbc */
       for ( int i=0; i<10; i++ ) { /* iterate over jdbc results */
            T item = factory.make();
            /* use reflection and set all of item’s fields from sql results */
            result.add( item );
       }
       return result;
}

(3)調用(匿名類方案 ):

select(new Factory<EmpInfo>(){ 
          public EmpInfo make() { 
            return new EmpInfo();
          }
       } , 」selection string」);

除了上述方式外,也能夠聲明一個類來支持Factory接口

class EmpInfoFactory implements Factory<EmpInfo> { ...
    public EmpInfo make() { return new EmpInfo();}
}

select(getMyEmpInfoFactory(), "selection string");//調用語句,其中getMyEmpInfoFactory()方法返回自定義的支持Factory接口的類

解說:上述兩種解決方案雖然可行,卻很不天然,其缺點是它們須要下面的兩者之一:

  a.爲每一個要使用的類型定義冗長的匿名工廠類;

  b.爲每一個要使用的類型聲明一個工廠類並傳遞其對象給調用處

此時能夠選擇使用class類型參數,它能夠被反射使用,沒有泛型的代碼可能以下:

Collection emps = sqlUtility.select(EmpInfo.class, 」select * from emps」);
public static Collection select(Class c, String sqlStatement) { 
    Collection result = new ArrayList();
    /* run sql query using jdbc */
    for ( /* iterate over jdbc results */ ) { 
        Object item = c.newInstance();
        /* use reflection and set all of item’s fields from sql results */
        result.add(item);
    }
    return result;
}

上述方案雖然能達到目的,卻不能給咱們返回一個精確類型的集合,因Java5及之後版本中Class是泛型的,咱們能夠按下面這種方式寫:

Collection<EmpInfo> emps=sqlUtility.select(EmpInfo.class, 」select * from emps」);
public static <T> Collection<T> select(Class<T>c, String sqlStatement) { 
    Collection<T> result = new ArrayList<T>();
    /* run sql query using jdbc */
    for ( /* iterate over jdbc results */ ) { 
        T item = c.newInstance();
        /* use reflection and set all of item’s fields from sql results */
        result.add(item);
    } 
    return result;
}

上述方案經過一種類型安全的方式獲得了咱們想要的集合,這項技術是一個很是有用的技巧。

 

 

7、參考資料

本文僅簡單闡述了Java泛型的部份內容,此處貼出一些優質文章以供讀者閱覽

(1)http://www.cnblogs.com/lwbqqyumidi/p/3837629.html

相關文章
相關標籤/搜索