Java 泛型

本文只是做為泛型的入門和基礎的泛型的了解。

什么是泛型?
泛型(generices)是JDK5引入的新特性,這個特性主要是為了創建容器類,這也是泛型出現的重要原因之一。泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。聲明的類型參數在使用時用具體的類型來替換。

1 泛型類

例如我們有一個容器類Container,我們希望,這個容器可以裝一個String類型的變量。代碼如下:

public class Container {

    private String obj;

    public String getObj() {
        return obj;
    }

    public void setObj(String obj) {
        this.obj = obj;
    }
}

那么我們可以這么使用這個容器類,例如:

        Container container = new Container();
        container.setObj("container str.");
        //會輸出"container str."
        System.out.println(container.getObj());

那,如果我們想在Container里面裝入int類型的話。這個類是不適用的,因為它只有1個String類型的變量obj。那就需要另外聲明一個Container類,然后里面包含一個int類型的變量。機智的你應該可以想到可以將Container的obj對象改成Object類型。這樣就可以存入任何的對象了, 因為Object是所有對象的基類。代碼如下:

public class ContainerObj {

    private Object obj;

    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
    }
}

這時候,我們就可以使用ContainerObj來存入任何類型了,只是取出來使用的時候需要進行一下轉換。例如:

        ContainerObj containerObj = new ContainerObj();
        containerObj.setObj("contailObj str");
        //會輸出"contailObj str"
        System.out.println(containerObj.getObj());

       containerObj.setObj(1);
        //會輸出1
        int i = (Integer) containerObj.getObj();
        System.out.println(i);

這樣子做回有一些缺點,例如,要記住存進去的是什么類型,不然取出來的時候,轉換的類型不對的話就會發生異常。

這時候,我們使用泛型就可以達到既可以不限定類型,又可以取的時候不用轉換。例如下面這個類:

public class ContainerGeneric<T> {


    private T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}


//調用 

        //=======使用泛型的類
        ContainerGeneric<Integer> containerInt = new ContainerGeneric<>();
        containerInt.setObj(10);
        //會輸出 10
        System.out.println(containerInt.getObj());

        ContainerGeneric<String> containerStr = new ContainerGeneric<>();
        containerStr.setObj("container str");
        //會輸出"container str"
        System.out.println(containerStr.getObj());

2 泛型接口

泛型接口和泛型類的使用方式差不多,下面看個簡單的例子:

public interface IContainer<T> {
    T getObj();
}


//需要在實現這個接口的時候,指定類型為String
public class ContainerImpl implements IContainer<String> {

    //所以重寫getObj方法的時候,需要返回的是String類型
    @Override
    public String getObj() {
        return "invoke getObj ..";
    }
}

        //=======使用泛型接口
        IContainer iContainer = new ContainerImpl();
        //會輸出"invoke getObj .."
        System.out.println(iContainer.getObj());
3 泛型方法

泛型方法,是在調用方法的時候指明泛型的具體類型。允許將在一個方法使用泛型,而不必整個類使用泛型。在方法返回值之前放上泛型標識<T>就可以了。可以在普通的方法使用泛型,也可以在靜態方法使用泛型,還可以在泛型方法里面使用泛型參數。如下例子:

public class GenericMethod {

    //泛型方法
    public <T> void outTest(){
        System.out.println("outTest is invoke..");
    }

    //泛型參數
    public <T> void outTest(T t, Integer i){
        System.out.println("t = ,"+t+"i="+i);
    }

    //靜態泛型方法
    public static  <T> void staticOutTest(){
        System.out.println("staticOutTest is invoke..");
    }

    public static void main(String[] args) {
        GenericMethod genericMethod = new GenericMethod();
        genericMethod.outTest();//輸出"outTest is invoke.."
        genericMethod.outTest("str",1);//輸出"t = ,stri=1"
        staticOutTest();//輸出"staticOutTest is invoke.."
    }

}
4 邊界符

使用泛型方法的過程中,往往會需要限定方法的參數的類型,而不至于可以傳入任意類型而造成混亂。
例如,我們需要寫一個方法,判斷一個對象是否正數。泛型方法可以這么寫。

    //判斷一個數是否正數
    public static  <T> boolean isPositive (T t){

        System.out.println(t);

        if(t instanceof Integer)
            return  Integer.parseInt(t.toString()) > 0;
        else if(t instanceof Double)
            return  Double.parseDouble(t.toString()) > 0;

        //還需要判斷 Float , Byte , Short , Long 類型
        else
            return false;

    }


//調用:

System.out.println(isPositive(1));// 輸出 1 true
System.out.println(isPositive(0.1d));// 輸出 0.1 true
System.out.println(isPositive("aa"));// 輸出 aa false

因為isPositive方法的參數T是泛型,所以調用的時候可以傳入任意類型。所以在寫方法的時候需要進行所有數字類型的判斷。如果可以限定參數T是數字類型的話,就可以減少很多麻煩和避免程序混亂。這時候可以使用邊界符,邊界符的作用是,限定泛型參數T的類型。使用如下:

    //使用邊界符 判斷一個數是否正數
    public static  <T extends Number> boolean isPositiveBunder (T t){
        System.out.println(t);
        return t.floatValue()>0;

    }

//調用:
        System.out.println(isPositiveBunder(1));//輸出 1 true
        System.out.println(isPositiveBunder(0.1d));// 輸出 0.1 true
//      System.out.println(isPositiveBunder("aa")); 編譯失敗,已經限定了T只能是實現Number抽象類的類

因為使用了邊界符限定了方法isPositiveBunder的參數T必須是實現了抽象類Number的類型,所以調用的時候,只能傳入實現了Number類的類型。String沒有繼承或者實現Number類型,所以不能通過編譯。由于知道參數 t 一定是實現了Number類,所以可以直接使用 Number類的 floatValue 方法來取值進行判斷。

5 通配符

我們知道,通過繼承,可以實現實例化一個子類賦值給父類或接口。例如:

Number num = new Integer(1);

Integer 類實現了繼承了Number這個抽象類。public final class Integer extends Number 。
但是,使用泛型的時候指定的泛型類并沒有這個繼承的關系,例如

//        List<Number> numberList  = new ArrayList<Integer>();編譯不通過

泛型類ContainerGeneric的參數是Number ,而實例化的時候,指定的參數是 Integer類型。這是不能通過編譯的。使用泛型的時候,默認是沒有父類子類這樣的繼承關系的。但是可以使用通配符,來將子類和父類關聯起來。例如:

          //使用通配符,將泛型參數中的繼承關系關聯起來
        List<? extends Number> numbers = new ArrayList<Integer>();

<? extends Number>表明可以接受的泛型參數可以是繼承Number類的子類。Integer繼承了Number類。所以可以這么寫,并且在泛型中將繼承關系關聯起來了。

6 PECS原則

如果我們想往numbers添加元素會怎么樣?

        //編譯不通過,不能往numbers集合里面添加元素,只能讀取元素
//        numbers.add(1);
//        numbers.add(2);

為什么不能編譯通過呢?我們可以分析一下
使用通配符的時候我們可以將numbers理解為:

        List<? extends Number> numbers  = new ArrayList<Integer>();
        List<? extends Number> numbers  = new ArrayList<Double>();
        List<? extends Number> numbers  = new ArrayList<Float>();
        List<? extends Number> numbers  = new ArrayList<Long>();
        List<? extends Number> numbers  = new ArrayList<Short>();
        List<? extends Number> numbers  = new ArrayList<Byte>();

numbers并不能確定它實際上的類型是什么類型,如果你往numbers添加一個int 元素. 但是可能numbers實際上可能裝載的是Double元素。所以不能往實現了<? extends T>的類里面添加元素,只能讀取元素。

如果我們確實需要添加元素怎么辦呢?可以使用<? super T >。表明,參數類一定是T的父類,T一定是參數類的子類。如下例子:

    List<? super Integer> numberIntList= new ArrayList<Number>();
//  List<? super Number> numberList = new ArrayList<Integer>();編譯不通過,Number不是Integer的子類
    numberIntList.add(10);//可以通過編譯

可以往實現了<? super T >的類添加元素,但是不能獲取元素。為什么呢?我們一樣可以分析一下。同樣的,使用了spuer關鍵字的通配符,我們可以理解為,"?" 一定匹配的是Integer類的父類或者自己本類型。

        List<? super Integer> numberIntList= new ArrayList<Integer>();
        List<? super Integer> numberIntList= new ArrayList<Number>();
        List<? super Integer> numberIntList= new ArrayList<Object>();

如果我們添加元素的話,可以添加Integer類型,或者Number類型,或者Object類型。但是取的時候,編譯器并不能判斷取出來的是什么類型。有可能是Integer,有可能是Number,有可能是Object類型。所以我們無法取出元素。

根據上面的例子,我們可以總結出一條規律源鉆,”Producer Extends, Consumer Super”(PECS):
“Producer Extends” - 如果你需要一個只讀List,用它來produce T,那么使用? extends T。
“Consumer Super” - 如果你需要一個只寫List,用它來consume T,那么使用? super T。
如果需要同時讀取以及寫入,那么我們就不能使用通配符了。

7 類型擦除
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 開發人員在使用泛型的時候,很容易根據自己的直覺而犯一些錯誤。比如一個方法如果接收List作為形式參數,那么如果嘗試...
    時待吾閱讀 1,072評論 0 3
  • 我們知道,使用變量之前要定義,定義一個變量時必須要指明它的數據類型,什么樣的數據類型賦給什么樣的值。 假如我們現在...
    今晚打肉山閱讀 1,000評論 0 1
  • Java泛型總結# 泛型是什么## 從本質上講,泛型就是參數化類型。泛型十分重要,使用該特性可以創建類、接口以及方...
    kylinxiang閱讀 930評論 0 1
  • 泛型的好處 使用泛型的好處我覺得有兩點:1:類型安全 2:減少類型強轉 下面通過一個例子說明: 假設有一個Tes...
    德彪閱讀 1,139評論 0 0
  • 為什么,你們都是這個樣子呢? 桃子狠狠吸了一口指間的香煙,尼古丁隨著渾濁的空氣被吞咽下去,入了心肺。 咳咳,桃子被...
    卿文閱讀 229評論 0 0