1. 程式人生 > >28-泛型--概述+擦除&補償+在集合中的應用+泛型類+泛型方法+泛型介面

28-泛型--概述+擦除&補償+在集合中的應用+泛型類+泛型方法+泛型介面

一、泛型概述

1、泛型:對要操作的資料型別進行指定。是JDK1.5出現的安全機制。泛型是給編譯器使用的技術,用在編譯時期,提高了編譯的安全性(確保型別安全)

2、向集合中新增元素,public boolean add(E e); 任何型別都可以接收(新增的元素被提升為Object型別)。通常,會將元素取出,對元素進行特有的操作。為了避免後期出現安全隱患,在定義容器時,就需要明確容器中儲存元素的型別。而迭代器是從集合獲取到的,集合明確了其中的元素型別,迭代器取出的元素型別就是明確的,就沒有必要再做強轉動作了(編譯時會檢測型別)

注:迭代器的泛型和獲取迭代器物件集合的泛型一致

        //定義容器時,明確容器中儲存元素的型別
        ArrayList<String> list = new ArrayList<String>();
        //新增元素時,型別匹配的才可以存入,否則編譯報錯
        list.add("abc");
//        list.add(true);   //型別不匹配,編譯報錯

        //迭代器取出的元素型別明確
        for (Iterator<String> it = list.iterator(); it.hasNext();){
            //不用再做強轉動作
            String value = it.next();
            System.out.println(value);
        }

3、泛型的好處

(1)將執行時期的問題ClassCastException轉到了編譯時期

(2)避免了強制轉換的麻煩

4、何時使用<> ?

      當操作的引用資料型別不確定的時候,就使用<>,將要操作的引用資料型別傳入即可。<>是一個用於接收具體引用資料型別的引數範圍。在程式中,只要用到了帶有<>的類或者介面,就要明確傳入的具體引用資料型別

注:<>中接收的都是引用資料型別(類/介面),要麼是類名,要麼是介面名。只要出現<>,就要向<>中傳入型別。泛型中不能傳基本資料型別

二、泛型的擦除&補償

1、泛型的擦除:執行時,會將泛型去掉。即 生成的 .class 檔案是不帶泛型的

注:用反射機制可以驗證集合中的泛型,是為了防止錯誤輸入的,只在編譯階段有效

2、執行時為何要擦除泛型?

      為了相容執行時的類載入器。執行時,JVM會去啟動類載入器。但以前的類載入器沒有使用泛型,如果在類檔案中加入泛型,以前的類載入器需要升級。希望以前的類載入器還可以使用,且編譯時已經對型別進行了檢查,型別已經統一,是安全的,執行時就可以把泛型去掉了。所以,只在編譯器中加入了泛型機制

注:類載入器:專門用於讀取類檔案、解析類檔案,並將類檔案載入進記憶體的程式

3、泛型的補償:在執行時,通過獲取元素的型別進行轉換動作,使用者無需再進行強制轉換

4、編譯器只做檢查,之後<>中的型別被擦除,add()中傳入的型別還是Object。取出時,為了避免強轉時出現問題,在類載入器的基礎上編寫了一個補償程式。先獲取指定元素的型別(通過getClass()方法獲取物件所屬的類檔案,就是<>中被擦除的型別),再對其進行型別轉換

三、泛型在集合中的應用

1、API中,只要類或介面後有<>的,使用時都要在後面用<>明確元素型別

2、在定義容器時,就要明確容器中要儲存的元素型別

/**
 * 實現Comparable<Person>介面,使用泛型,該Comparable介面中只能傳入Person型別的物件
 */
public class Person implements Comparable<Person> {

    private int age;
    private String name;

    //......此處省略建構函式和get()、set()方法

    /**
     * 覆蓋Comparable<T>介面中的compareTo(T o)方法,引數型別是Person.因為 implements Comparable<Person> 限制了型別的使用
     *
     * @param p
     * @return
     */
    @Override
    public int compareTo(Person p) {
        //不用再做強轉動作,因為只有匹配的型別才能傳進來
        int temp = this.age - p.age;
        return temp == 0 ? this.name.compareTo(p.name) : temp;
    }

    /**
     * 覆蓋Object類中的equals(Object obj)方法,引數是Object型別(固定型別),不能修改。因為父類中沒有定義過泛型
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj){
        //......
    }

}

3、泛型在集合框架中應用的最多

4、泛型中,當引用資料型別不確定時,用泛型表示。泛型中不能傳基本資料型別

5、一般情況下,寫泛型要具備的特點是:左右兩邊的泛型要一致

四、泛型類

1、JDK1.5後,使用泛型來接收類中要操作的引用資料型別 -- 泛型類(自定義泛型類)

2、何時使用?

      當類中操作的引用資料型別不確定時,就使用泛型來表示

3、沒泛型用Object,但泛型這種替代方式比Object安全得多,只是書寫比Object麻煩一點,要寫<>

/**
 * 存入時,用Object型別接收(向上轉型),什麼型別都可以存入 -- 提高擴充套件性
 * 取出時,先強轉再使用,注意報錯:ClassCastException。所以,取出時需要做健壯性判斷
 */
public class Tool {
    private Object object;

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}

/**
 * 在JDK1.5後,使用泛型來接收類中要操作的引用資料型別 -- 泛型類
 * 何時使用?
 * 當類中操作的引用資料型別不確定時,就使用泛型來表示
 */
public class Tool<QQ> {
    private QQ q;

    public QQ getObject() {
        return q;
    }

    public void setObject(QQ object) {
        this.q = object;
    }
}

注:泛型類取出時不用做強轉動作,進一步驗證了:(1)泛型將執行時期的問題轉移到了編譯時期(2)避免了強轉的麻煩

五、泛型方法

1、在不明確型別的情況下,Object其實是一種具體型別。只不過它是所有類的父類,導致它可以有多型方式來接收

2、如果不明確型別,使用者往裡傳什麼型別,就操作什麼型別。這時可以定義泛型,叫做把泛型定義在方法上

eg:public <W> void show(W xxx){ ... }

public class Tool<QQ> {
    private QQ q;

    public QQ getObject() {
        return q;
    }

    public void setObject(QQ object) {
        this.q = object;
    }

    /**
     * 工具類中指定什麼型別(Tool<QQ>),方法中就操作什麼型別(QQ) -- show()方法的引數的泛型跟著物件走
     * 方法的引數用的也是泛型,使用的是類上定義的泛型
     */
    public void show(QQ object) {
        System.out.println("show: " + object);
    }

    /**
     * 將泛型定義在方法上:自動找型別匹配,什麼都能傳入 -- 泛型是在方法上自定義的
     * <W>是定義引數,W是使用引數
     */
    public <W> void show1(W obj) {
        System.out.println("泛型:show: " + obj);  //等同與obj.toString()方法
    }

}

3、當方法靜態時,不能訪問類上定義的泛型。如果靜態方法需要使用泛型,只能將泛型定義在方法上

注:自定義的泛型一定要寫在返回值型別前,修飾符後

public class Tool<QQ> {
    private QQ q;

    public QQ getObject() {
        return q;
    }

    public void setObject(QQ object) {
        this.q = object;
    }

    /**
     * 靜態是不需要物件的,而泛型得需要物件來明確
     * 當方法靜態時,不能訪問類上定義的泛型。如果靜態方法需要使用泛型,只能將泛型定義在方法上
     */
//    public static void method(QQ xxx) {
//        System.out.println("method: " + xxx);
//    }
    public static <W> void method(W xxx) {
        System.out.println("method: " + xxx);
    }

}

4、一旦使用了泛型,變數的型別不確定,就不能使用具體物件的方法。Object中的方法可以使用,因為無論傳入什麼型別,都是Object的子類物件,都具備著Object中的方法(Object中的方法是所有物件的通用方法)

    public void print(QQ str) {
//        System.out.println("print: " + str.length()); //報錯。使用了泛型,變數str的型別不確定,不能使用具體物件的方法
    }

六、泛型介面

1、泛型介面:將泛型定義在介面上(將泛型定義在類上就叫泛型類)

2、定義泛型介面時,一般不會明確具體型別(無意義)

/**
 * 泛型介面:定義時,一般不會明確具體型別
 */
interface Inter<T> {
    public void show(T t);
}

3、泛型介面一般有兩種實現方式。使用哪一種,要看何時能明確型別

(1)實現介面時明確型別

/**
 * 實現介面時,明確型別:Inter<T> --> Inter<String>
 */
class InterImpl implements Inter<String> {
    @Override
    public void show(String str){
        System.out.println(str);
    }
}

public class Test {
    public static void main(String[] args) {
        InterImpl in = new InterImpl();
        in.show("abc");
    }
}

(2)實現介面時型別尚不明確,繼續使用泛型。直到使用子類物件時,才明確型別

/**
 * 實現介面時,型別尚不明確,繼續使用泛型:Inter<T> --> Inter<Q>
 */
class InterImpl<Q> implements Inter<Q> {
    @Override
    public void show(Q q){
        System.out.println(q);
    }
}

public class Test {
    public static void main(String[] args) {
        //使用子類物件時,才明確型別
        InterImpl<Integer> in1 = new InterImpl<Integer>();
        in1.show(1);

        InterImpl<String> in2 = new InterImpl<String>();
        in2.show("abc");
    }
}