Java基礎|泛型使用姿勢和原理你真的搞懂了嗎?

1 使用泛型的好處

  • 泛型設計程序的主要目的是:使編寫的程序代碼可以被多種不同類型的對象所重用。
  • 並採取一定的語法規則對這個類型進行適當的限定。
  • 採用適當的限定語法規則,在編譯期進行類型轉換的安全檢查(沒有泛型時,強制類型轉換安全檢查是在運行時期),既提升安全性,也提升了性能。

泛型類

  • 泛型類:具有一個或多個類型變量的類。
  • 聲明: 聲明泛型。(一個類型變量T,用尖括號<>括起來,放在類名後面。可以有多個類型變量,eg:BookStore<T,U>。)
  • 類型變量:T 代表泛型的類型變量。形式上使用大寫字母。用具體類型進行替換,就可以實例化泛型類型。
  • 類中定義的泛型類型變量意義:泛型類的類型變量T,可以是該類某個成員方法的返回類型,以及域或是局部變量類型。

泛型方法

  • 泛型方法:具有一個或多個類型變量的方法。
  • 存在於什麼類中:泛型方法既可以定義在普通類中,也可以定義在泛型類中。
  • 聲明符號的位置: ,位置在修飾符之後,方法返回類型之前。

類型變量的限定

  • 類型變量限定:類或方法需要對類型變量加以約束。如限定類型變量T爲實現了Comparable接口的類。.
  • 一個類型變量或通配符可以有多個限定,用“&”分隔(T extends Father&Comparable&Serializable)。遵循繼承規則,可以擁有至多一個類,和多個接口限定。類必須是限定列表中的第一個。

泛型原理(泛型擦除)

  • 虛擬機沒有泛型類型對象,只有普通類的對象。無論何時定義一個泛型類,都自動提供一個相應的原始類型。原始類型的名字就是刪除類型參數後的泛型類型名。
  • 原始類型的名字確定:泛型類型變量擦除後,用限定類型列表中的第一個類型進行替換;如果沒有限定類型,用Object替換。注意類的名字還原爲原始類的名字
  • 泛型的內部機制原理:泛型變量會被擦除替換;爲了保證類型安全性,編譯器會在必要時插入強制類型轉換;當類型擦除後,導致與多態發生衝突時,編譯器會生成一個橋方法來保持多態,橋方法裏其實就是做一些強制類型轉換。
    eg:
public class Book<T extends Comparable & Serializable> implements Serializable{
 private T first;
 private T second;
}

擦除後:

//類的名字還原爲原始類的名字
public class Book implements Serializable{
//泛型變量擦除後,按規則替換爲對應的原始類型名字
private Comparable firt;
private Comparable second;
}

如果調整順序爲:

<T extends Serializable &  Comparable>
  • 這樣做,原始類型用Serializable 替換T。編譯器會在必要時,加入Comparable進行強制類型轉換。
  • 爲了提高效率,應將標記接口(標記接口沒有方法。常見有:Serializable->支持序列化標記,Cloneable->可深度拷貝標記,RandomAccess->集合元素可通過索引快速訪問標記,數組的數據結構有,鏈表的就沒有。)放在限制列表的末尾。

  • 虛擬機中,用方法的簽名和方法的返回類型二者確定一個方法。
  • 編寫的程序,根據方法簽名和方法入參確定一個方法。

通配符類型

  • 通配符類型解決固定的泛型類型(指泛型變量被單一類型實例化)使用的侷限性。
  • 通配符類型中,允許類型參數變化。
  • 通配符“?”同樣可以對類型進行限定。可以分爲子類型限定;超類型限定;無限定。通配符不是類型變量,因此不能在代碼中使用"?"作爲一種類型。
 <? super Book>//指定下限(超類型限定);安全寫,指可安全寫入Book,以及Book的子類;因爲限定最低泛型類型是Book類型
 <? extends Book>//指定上限(子類型限定);安全讀,指可把讀取的值賦值給Book;因爲限定讀取到值的最高類型是Book類型,即是Book或Book的子類型
 ? obj;//error.'?'不能作爲一種類型
 
  • 帶有超類型限定的通配符可以向泛型對象寫入,即可作爲入參類型,爲方法提供參數,但不益 作爲返回參數類型(返回的類型只能賦給Object)。(如示例中,可以寫入Book及其子類型)
  • 帶有子類型限定的通配符可以從泛型對象中讀取,即可以作爲返回參數類型。(如示例中,可以讀取Book及其子類型)
  • 無限定不等於可以傳任何值,相反,作爲方法的參數時,只能傳遞null,作爲方法的返回時,只能賦給Object。

泛型侷限

  • 1 不能用基本類型實例化類型參數。因爲類型擦除之後,一個類對象如Object,不能存儲一個基本類型的值如double類型。
  • 2 運行時類型查詢只適用於原始類型。
//類型查詢只適用於原始類型,故下面的示例都錯誤
 if(b instanceof BookStore<T>)//error
 if(b instanceof BookStore<MathBook>)//error
 //warinning->實際轉換爲了BookStore原始類型
 BookStore<MathBook> book = (BookStore<MathBook>)b;
 /***.getClass()方法也是返回的是原始類型。**/
 BookStore<ArithmeticBook> aritBook = new BookStore<>();
 BookStore<MathBook> mathBook = new BookStore<>();
 if(aritBook.getClass().equals(mathBook.getClass())) {
  //都是得到原始類型,故一定是相等的。
 }
  • 3 不能創建參數化類型數組(可以創建參數化類型數組,再進行強制轉換);
  • 4 可向參數個數可變的方法傳遞一個泛型類型實例。但會得到一個警告。(可變長度參數實際上就是數組,此時泛型規則對此有所放鬆。)
  • 5 泛型類型變量,不能被實例化;也不能在靜態域或方法中引用類型變量;也不能

捕獲或拋出泛型變量實例。

  obj = new T();//error
  private static T obj;//error
  try{...}
  catch(T e)//error
  {
      
  }

代碼示例
泛型類:

/**
 * @author gao tianci
 * @version $Id: Pair.java, v 0.1 2017年8月13日 上午11:11:55 gao tianci Exp $
 * 
 * 泛型類:具有一個或多個類型變量的類。
 * 聲明:<T> 聲明泛型。(一個類型變量T,用尖括號<>括起來,放在類名後面。可以有多個類型變量,eg:BookStore<T,U>。)
 * 類型變量:T 代表泛型的類型變量。形式上使用大寫字母。用具體類型進行替換,就可以實例化泛型類型。
 * 類中定義的泛型類型變量意義:泛型類的類型變量T,可以是該類某個成員方法的返回類型,以及域或是局部變量類型。
 */
public class BookStore<T> {
    //T 泛型類型變量作爲域變量類型
    private T first;
    private T second;

    /**
     * 構造器
     */
    public BookStore() {
        super();
        first = null;
        second = null;
    }

    /**構造器
     * T 泛型類型變量作爲成員方法的局部變量類型
     * @param first
     * @param second
     */
    public BookStore(T first, T second) {
        super();
        this.first = first;
        this.second = second;
    }
    //T 泛型類型變量作爲成員方法的返回類型
    public T getFirst() {
        return first;
    }

    public T getSecond() {
        return second;
    }

    public void setFirst(T first) {
        this.first = first;
    }

    public void setSecond(T second) {
        this.second = second;
    }
}

通用泛型方法工具類

import java.util.Collection;
import org.apache.commons.collections4.CollectionUtils;
/**
 * @author gao tianci
 * @version $Id: BookStoreUtil.java, v 0.1 2017年8月14日 下午12:42:24 gao tianci Exp $
 */
public class BookStoreUtil {
    /**
     * @param b
     * @return
     */
    public static boolean hasNulls(BookStore<?> b) {
        return b.getFirst() == null || b.getSecond() == null;
    }

    /**
     * @param b
     */
    public static void swap(BookStore<?> b) {
        swapHelper(b);
    }

    /**
     * 泛型方法
     * @param b
     */
    public static <T> void swapHelper(BookStore<T> b) {
        T t = b.getFirst();
        b.setFirst(b.getSecond());
        b.setSecond(t);
    }

    /**
     * 向參數個數可變的方法,傳遞一個泛型類型實例。
     * 實際args是一個泛型數組。虛擬機也必須對應建立一個泛型數組。泛型對此有所放鬆。會給出一個警告。可使用@SafeVarargs標註。
     * @param collection
     * @param args
     * @return
     */
    @SafeVarargs
    public static <T> Collection<T> addAll(Collection<T> collection, T... args) {
        if (CollectionUtils.isNotEmpty(collection) && args.length > 0) {
            //添加
            for (T item : args) {
                collection.add(item);
            }
            return collection;
        }
        return null;
    }
}

測試類

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * @author gao tianci
 * @version $Id: Test.java, v 0.1 2017年8月15日 下午12:24:46 gao tianci Exp $
 */
public class Test {
    @org.junit.Test
    public void test1() {
        @SuppressWarnings("unchecked")
        /**
         * 創建泛型數組。
         * 直接創建不允許。
         * BookStore<String>[] books = new BookStore<String>[2];///error
         * 可聲明通配類型數組,然後進行類型轉換。從而達到創建泛型數組。
         * 建議最好使用:ArrayList<T> 集合來滿足泛型數組的需求。
        */
        BookStore<String>[] books = (BookStore<String>[]) new BookStore<?>[2];
        books[0] = new BookStore<>("1", "2");
        books[1] = new BookStore<>("3", "4");

        //泛型數組轉換爲對應的List集合
        List<BookStore<String>> arrays = Arrays.asList(books);
        arrays.forEach(item -> System.out.println(item.getFirst() + "," + item.getSecond()));
        System.out.println("---------");
    }

    @org.junit.Test
    public void test2() {
        Collection<BookStore<String>> collection = new ArrayList<>();
        collection.add(new BookStore<>("lala", "la"));
        @SuppressWarnings("unchecked")
        BookStore<String>[] books = (BookStore<String>[]) new BookStore<?>[2];
        books[0] = new BookStore<>("gaga", "ga");
        books[1] = new BookStore<>("haha", "ha");
        Collection<BookStore<String>> resultCollection = BookStoreUtil.addAll(collection, books[0], books[1]);
        resultCollection.forEach(item -> System.out.println(item.getFirst() + "," + item.getSecond()));
    }
}

測試結果

1,2
3,4
------
lala,la
gaga,ga
haha,ha

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章