1. 程式人生 > 程式設計 >關於JAVA泛型那點事

關於JAVA泛型那點事

相逢便是,路過點個^.^


概述

Java 泛型(generics)是 JDK 5 中引入的一個新特性,在面向物件程式設計及各種設計模式中有非常廣泛的應用。泛型提供了編譯時型別安全檢測機制,該機制允許程式設計師在編譯時檢測到非法的型別。泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。

注意引數的型別只能是引用型別,不能是原始型別(int,char,double 這種),據說是當初設計師偷懶!!!

根據慣例,型別引數是單個大寫字母,該字母用於指示所定義的引數型別。下面列出每個用例的標準型別引數:

  • T 型別
  • E 元素
  • K 鍵
  • V 值
  • N 數字
  • S、U、V 等:多引數情況中的第 2、3、4 個型別

泛型類別

泛型類

泛型類的宣告和非泛型類的宣告類似,除了在類名後面添加了型別引數宣告部分,型別可以有多個:。最典型的就是各種容器類,如:List,Set,Map

List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
複製程式碼

舉個例子

通常我們在開發中會定義一個統一的介面返回型別,根據傳入的引數型別 T 不同可以返回不同的資料型別,其中 T 可以用其他標識代替,如K,V,U

public class HttpResult<T{

    private T data;

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

    public T getData() {
        return data;
    }

    public void setDatastatic main(String[] args) {
        HttpResult<String> strResult = new HttpResult<>("String型別");
        HttpResult<Boolean> booleanResult = new
 HttpResult<>(true);
        HttpResult<Integer> integerResult = new HttpResult<>(100);
        System.out.println("str:" + strResult.getData());
        System.out.println("boolean:" + booleanResult.getData());
        System.out.println("integer:" + integerResult.getData());
    }
}
複製程式碼

原始類 (raw class)

如果我們使用一個泛型類,但是不帶型別引數,那麼我們使用的是原始類。

List list = new ArrayList();
list.add(new Object());
list.add(99);
list.add("123");
複製程式碼

泛型方法

  1. 泛型方法,和泛型類相似,是在呼叫方法的時候指明泛型的具體型別 。
  2. 所有泛型方法宣告都有一個型別引數宣告部分(由尖括號分隔: )方法返回型別之前
  3. static 方法和static 域均不可以引用類的型別變數,因為存在泛型擦除,後面講
  4. 只有宣告瞭型別引數的方法才是泛型方法,泛型類中使用了泛型的方法不是泛型方法

擴充套件上面的例子

private int code;

    private String msg;

    /**
     * 具體返回資料,定義為泛型
     * T 可以為其他任意標識 E,K,V等
     */

    private T data;

    /**
     * 這個不是泛型方法,雖然用到了 T
     * 但是返回型別前面沒有使用 型別宣告
     */

    (int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    /**
     * 這個是泛型方法
     * 返回型別前面有型別宣告<T>,T 可以為其他標識
     * !注意 這裡的型別宣告和類的型別申明都是 <T>,但這2個宣告是不相關的,
     * static 方法和static 域均不可以引用類的型別變數,應為存在泛型擦除。
     * 為了防止混淆,這裡可以使用其他標識D代替:
     * public static <D> HttpResult<D> success(D data){
     *     return new HttpResult<>(200, "success", data);
     * }
     */

    static <T> HttpResult<T> successreturn 200"success", data);
    }
複製程式碼

泛型介面

和定義泛型類相似。

interface GenericService<getOwn();
}
複製程式碼

介面實現未傳入具體型別的,仍然以泛型的形式實現

GenericServiceImpl<Timplements T> {

    /**
     * 仍然顯示泛型
     */

    @Override
    returnSelf(T oldEntity) {
        System.out.println(oldEntity.getClass());
        return oldEntity;
    }
}
複製程式碼

介面實現傳入具體型別的,顯示具體型別

Integer> Integer> {

    /**
     * 這裡用傳入的Integer型別替換掉原來的泛型
     */

    public Integer (Integer oldEntity) {
        System.out.println(oldEntity.getClass());
        return oldEntity;
    }
}
複製程式碼

萬用字元

定義基本關係

// 基本樹形結構
BaseTree {}

// 部門樹
DepartmentTree extends // 選單樹繼承基本樹結構
MenuTree BaseTree {}
複製程式碼

無界萬用字元 ?

問號 (?) 萬用字元可用於使用泛型程式碼表示未知型別。萬用字元可用於引數、欄位、區域性變數和返回型別。但最好不要在返回型別中使用萬用字元,因為確切知道方法返回的型別更安全。

假設我們想編寫一個方法來驗證指定的List中是否存在指定的物件。我們希望該方法接受兩個引數:一個是未知型別的 List,另一個是任意型別的物件。

checkList(List<?> myList, T obj){
    if(myList.contains(obj)){
        System.out.println("The list contains the element: " + obj);
    } else {
        System.out.println("The list does not contain the element: " + obj);
    }
}
複製程式碼

上限萬用字元

限制型別為特定型別或者特定型別的子類
當獲取資料是會隱式的轉為其基類(或者Object基類)

    /**
     * 只能傳 BaseTree 或者它的子類 DepartmentTree、 menuTree
     *
     * @param tree 傳入的樹結構
     */

    static <T extends BaseTree> UpperBoundType(T tree) {
        if (tree instanceof DepartmentTree) {
            System.out.println("depTree");
        } else instanceof MenuTree) {
            System.out.println("menuTree");
        } else {
            System.out.println("baseTree");
        }
        return tree;
    }
複製程式碼
/**
     * 指定型別 T 返回型別 T
     */
    UpperBoundList(List<T> treeList) {
        T tree = treeList.get(0);
        /**
     * 使用 <? extends BaseTree>
     * 獲取列表元素時只能使用父類去接受,
     * 但還是可以獲取到子類的型別
     */

    static BaseTree (List<? extends BaseTree> treeList) {
        // 這裡只能使用BaseTree去接收引數,要用子型別需要強轉
        BaseTree tree = treeList.get(0);
        // 傳入DepartmentTree 或者 MenuTree時,下面的依然可以執行
        "depTree");
            DepartmentTree base = (DepartmentTree) tree;
        } "menuTree");
            MenuTree ment = (MenuTree) tree;
        } "baseTree");
            BaseTree base = (BaseTree) tree;
        }
        return tree;
    }
複製程式碼

多介面限制

同時繼承類並實現多個介面的可以用 & 連線

MultiExtend<T Number & Comparable<T> & Cloneable> {

}
複製程式碼

下限萬用字元

限制型別為特定型別或者特定型別的超類

/**
     * 只能傳MenuTree或者它的父類的List
     * 接收型別為Object
     * 需要強轉到對應的型別
     */
    LowerBoundType(List<? super MenuTree> menuTree) {
        Object obj = menuTree.get(if (obj "menuTree");
            MenuTree ment = (MenuTree) obj;
        } instanceof BaseTree) {
            System.out.println("baseTree");
            BaseTree base = (BaseTree) obj;
        }
    }
複製程式碼

泛型擦除

泛型類可以由編譯器通過所謂的型別擦除過程而轉變成非泛型類。這樣,編譯器就生成了一種與泛型類同名的原始類,但是型別引數都被刪除了。對應的型別變數由他們的型別界限來代替,上限萬用字元的為其父類,下限萬用字元的為Object。

泛型的限制

基本型別

基本型別(int,double)不能用做型別引數,必須使用包裝類(Integer,Double)

static 語境

在一個泛型類中,static方法和static域都不可以引用類的型別變數,因為類在型別擦除後就不存在型別變量了。

泛型類的例項化

不能建立一個泛型型別的例項化,下面程式碼是錯誤的:

T obj = new T();
複製程式碼

在型別擦除後T由它的限界代替,可能是Object(甚至是抽象類),因此對new沒有呼叫意義。

泛型陣列

同樣不能建立一個泛型陣列,下面程式碼是錯誤的:

T [] arr = new T(10);
複製程式碼

在型別擦除後T由它的限界代替,很可能是Object T,於是對T[]的型別轉換將無法進行,應為Object[] IS-NOT-A T[]。
一般不建議建立泛型陣列,儘量使用ArrayList來代替泛型陣列。下面提供了一種建立泛型陣列的方法

GenericArrayFactory<private T[] array;

    /**
     * 初始化陣列
     *
     * @param componentType 陣列的類別
     * @param length        容量
     */

    init(Class<T> componentType, int length) {
        // 這裡會有一個型別轉換警告 Object to T[]
        //noinspection unchecked
        array = (T[]) Array.newInstance(componentType, length);
    }

    /**
     * 賦值
     *
     * @param index 索引
     * @param item  值
     */

    putint index, T item) {
        array[index] = item;
    }

    /**
     * 獲取陣列
     *
     * @return arr
     */

    public T[] getArray() {
        return array;
    }

    (String[] args) {
        GenericArrayFactory<Integer> genericArrayFactory = new GenericArrayFactory<>();
        // 初始化Integer型別的陣列
        genericArrayFactory.init(Integer.class, 3);
        genericArrayFactory.put(0,250); word-wrap: inherit !important; word-break: inherit !important;" class="hljs-number">9);
        genericArrayFactory.put(1,250); word-wrap: inherit !important; word-break: inherit !important;" class="hljs-number">8);
        genericArrayFactory.put(2,250); word-wrap: inherit !important; word-break: inherit !important;" class="hljs-number">7);
        Integer[] intArray = genericArrayFactory.getArray();
        for (Integer integer : intArray) {
            System.out.println(integer);
        }
    }
}
複製程式碼