1. 程式人生 > >java基礎(3)-----泛型

java基礎(3)-----泛型

1.概述(什麼是泛型?)

泛型,即“引數化型別”,顧名思義,將具體的型別引數化,在呼叫的時候再傳入具體的型別

2.一個簡單的例子

public class GenericTest {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("qqyumidi");
        list.add("corn");
        list.add(100);

        for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); // 1
            System.out.println("name:" + name);
        }
    }
}

定義了一個List型別的集合,先向其中加入了兩個字串型別的值,隨後加入一個Integer型別的值。這是完全允許的,因為此時list預設的型別為Object型別。在之後的迴圈中,由於忘記了之前在list中也加入了Integer型別的值或其他編碼原因,很容易出現類似於//1中的錯誤。因為編譯階段正常,而執行時會出現“java.lang.ClassCastException”異常。因此,導致此類錯誤編碼過程中不易發現。

 在如上的編碼過程中,我們發現主要存在兩個問題:

1).當我們將一個物件放入集合中,集合不會記住此物件的型別,當再次從集合中取出此物件時,改物件的編譯型別變成了Object型別,但其執行時型別任然為其本身型別。

2).因此,//1處取出集合元素時需要人為的強制型別轉化到具體的目標型別,且很容易出現“java.lang.ClassCastException”異常。

那麼有沒有什麼辦法可以使集合能夠記住集合內元素各型別,且能夠達到只要編譯時不出現問題,執行時就不會出現“java.lang.ClassCastException”異常呢?答案就是使用泛型。

3.特性

泛型只是在編譯階段有效,執行階段會進行去泛型,也就是編譯成class之後會去泛型化,屬於假泛型

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);

        System.out.println("name class:" + name.getClass());      // com.qqyumidi.Box
        System.out.println("age class:" + age.getClass());        // com.qqyumidi.Box
        System.out.println(name.getClass() == age.getClass());    // true

    }

}

由此,我們發現,在使用泛型類時,雖然傳入了不同的泛型實參,但並沒有真正意義上生成不同的型別,傳入不同泛型實參的泛型類在記憶體上只有一個,即還是原來的最基本的型別(本例項中為Box),當然,在邏輯上我們可以理解成多個不同的泛型型別。

究其原因,在於Java中的泛型這一概念提出的目的,導致其只是作用於程式碼編譯階段,在編譯過程中,對於正確檢驗泛型結果後,會將泛型的相關資訊擦出,也就是說,成功編譯過後的class檔案中是不包含任何泛型資訊的。泛型資訊不會進入到執行時階段。

4.自定義泛型介面、泛型類、泛型方法

4.1泛型類

泛型型別用於類的定義中,被稱為泛型類。通過泛型可以完成對一組類的操作對外開放相同的介面。最典型的就是各種容器類,如:List、Set、Map。

一個最普通的泛型類:

//此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用於表示泛型
//在例項化泛型類時,必須指定T的具體型別
public class Generic<T>{ 
    //key這個成員變數的型別為T,T的型別由外部指定  
    private T key;

    public Generic(T key) { //泛型構造方法形參key的型別也為T,T的型別由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值型別為T,T的型別由外部指定
        return key;
    }
}
//泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別
//傳入的實參型別需與泛型的型別引數型別相同,即為Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);

//傳入的實參型別需與泛型的型別引數型別相同,即為String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型測試","key is " + genericInteger.getKey());
Log.d("泛型測試","key is " + genericString.getKey());
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is 123456
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is key_vlaue

4.2泛型介面

泛型介面與泛型類的定義及使用基本相同。泛型介面常被用在各種類的生產器中,可以看一個例子:

//定義一個泛型介面
public interface Generator<T> {
    public T next();
}

當實現泛型介面的類,未傳入泛型實參時:

/**
 * 未傳入泛型實參時,與泛型類的定義相同,在宣告類的時候,需將泛型的宣告也一起加到類中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不宣告泛型,如:class FruitGenerator implements Generator<T>,編譯器會報錯:"Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

當實現泛型介面的類,傳入泛型實參時:

/**
 * 傳入泛型實參時:
 * 定義一個生產器實現這個介面,雖然我們只建立了一個泛型介面Generator<T>
 * 但是我們可以為T傳入無數個實參,形成無數種類型的Generator介面。
 * 在實現類實現泛型介面時,如已將泛型型別傳入實參型別,則所有使用泛型的地方都要替換成傳入的實參型別
 * 即:Generator<T>,public T next();中的的T都要替換成傳入的String型別。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}

4.3泛型萬用字元

我們知道IngeterNumber的一個子類,同時在特性章節中我們也驗證過Generic<Ingeter>Generic<Number>實際上是相同的一種基本型別。那麼問題來了,在使用Generic<Number>作為形參的方法中,能否使用Generic<Ingeter>的例項傳入呢?在邏輯上類似於Generic<Number>Generic<Ingeter>是否可以看成具有父子關係的泛型型別呢?

為了弄清楚這個問題,我們使用Generic<T>這個泛型類繼續看下面的例子:

public void showKeyValue1(Generic<Number> obj){
    Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);

// showKeyValue這個方法編譯器會為我們報錯:Generic<java.lang.Integer> 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

通過提示資訊我們可以看到Generic<Integer>不能被看作為`Generic<Number>的子類。由此可以看出:同一種泛型可以對應多個版本(因為引數型別是不確定的),不同版本的泛型類例項是不相容的。

回到上面的例子,如何解決上面的問題?總不能為了定義一個新的方法來處理Generic<Integer>型別的類,這顯然與java中的多臺理念相違背。因此我們需要一個在邏輯上可以表示同時是Generic<Integer>Generic<Number>父類的引用型別。由此型別萬用字元應運而生。

我們可以將上面的方法改一下:

public void showKeyValue1(Generic<?> obj){
    Log.d("泛型測試","key value is " + obj.getKey());
}

型別萬用字元一般是使用?代替具體的型別實參,注意了,此處’?’是型別實參,而不是型別形參 。重要說三遍!此處’?’是型別實參,而不是型別形參 ! 此處’?’是型別實參,而不是型別形參 !再直白點的意思就是,此處的?和Number、String、Integer一樣都是一種實際的型別,可以把?看成所有型別的父類。是一種真實的型別。

可以解決當具體型別不確定的時候,這個萬用字元就是 ?  ;當操作型別時,不需要使用型別的具體功能時,只使用Object類中的功能。那麼可以用 ? 萬用字元來表未知型別。

型別萬用字元上限和型別萬用字元下限

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
        
        //getUpperNumberData(name); // 1
        getUpperNumberData(age);    // 2
        getUpperNumberData(number); // 3
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }
    
    public static void getUpperNumberData(Box<? extends Number> data){
        System.out.println("data :" + data.getData());
    }

}

此時,顯然,在程式碼//1處呼叫將出現錯誤提示,而//2 //3處呼叫正常。

型別萬用字元上限通過形如Box<? extends Number>形式定義,相對應的,型別萬用字元下限為Box<? super Number>形式,其含義與型別萬用字元上限正好相反