認識Java泛型

浮花俱盡發表於2019-01-19

一、泛型的概念

泛型實現了引數化型別的概念,使程式碼可以用於多種型別

二、泛型的目的

  1. 希望類和方法能夠具備最廣泛的表達能力
  2. 用來指定容器要持有什麼型別的物件,而且由編譯器來保證型別的正確性

三、泛型的使用

  1. 普通泛型類

    public class NormalGenericsClass<T> {
        private T key;
        public Generics(T key){
            this.key = key;
        }
        public T getKey() {
            return key;
        }
        public void setKey(T key) {
            this.key = key;
        }
    }
    
    
    // 泛型類例項
    NormalGenericsClass<Integer> a = new NormalGenericsClass<Integer>(0);
    // Java7 開始支援省略後面的引數型別
    NormalGenericsClass<Integer> a = new NormalGenericsClass<>(0);
  2. 普通泛型介面

    // Java中的 Comparator介面
    public interface Comparator<T> {
    
        int compare(T o1, T o2);
    
        boolean equals(Object obj);
    }
    
    // 介面實現例子 java.text.Collator
    public abstract class Collator implements java.util.Comparator<Object>, Cloneable
    {
        //...主體程式碼
    }
  3. 普通泛型方法

    private <T> int normalGenericsMethod(Generics<T> i){ 
    // <T> 表示宣告 T為泛型,寫在返回值型別之前
    // 也可以宣告多個泛型,如:<K,V>
        return 0;
    }
    
    // 這是錯誤的,會提示 Cannot resolve symbol `T`
    /*private int normalGenericsMethod(Generics<T> i){ 
            return 0;
    }*/

    有上界的泛型:
    上界:通過關鍵字extends來表示給定的上界,那麼引數就必須是給定的上界或者其子型別。上界可以是某個具體的類或介面,也可以是其他引數

  4. 上界為具體的類

    public class GenericsUpperBound<T extends Number> extends NormalGenericsClass<T> {
        public GenericsUpperBound(T key){
            super(key);
        }
    }
  5. 上界為具體的介面

    public <T extends Comparator> T compareWith(T[] arr){
        T start = arr[0];
        for(int i = 1; i < arr.length; i++){
            System.out.print(start.equals(arr[i]));
        }
        return start;
    }
  6. 上界為其他型別引數

    public class OtherUpperBound<T>{
        public <T extends E> void otherArgs(NormalGenericsClass<E> a){
            // E是OtherUpperBound的型別引數,T是otherArgs方法的型別引數
            // T的上界限定為E
        }
    }
    
    //例子:
    OtherUpperBound<Number> a = new OtherUpperBound<>;
    OtherUpperBound<Integer> b = new OtherUpperBound<>;
    a.otherArgs(b);

四、萬用字元

  1. 有限定萬用字元

    //重寫6中的方法
    public void otherArgs(NormalGenericsClass<? extends E> a){
       
    }

    取自《Java程式設計的邏輯》8.2
    <T extends E> 和 <? extends E>的區別
    ①<T extends E>:用於定義型別引數,它宣告瞭一個型別引數T,可放在泛型類定義中類名後面、泛型方法返回值前面
    ②<? extends E>:用於例項化型別引數,它用於例項化泛型變數中的型別引數,只是這個型別是未知的,只知道是E或E的某個子型別

  2. 無限定萬用字元

    public int demo(OtherUpperBound<?> a){
        
    }
    //  這兩個等效
    public <T> int demo(OtherUpperBound<T> a){
        
    }

    萬用字元重要限制: 只能讀,不能寫

    取自《Java程式設計的邏輯》8.2
    總結:
    1) 萬用字元形式都可以用型別引數的形式來替代,萬用字元能做的,用型別引數都能做
    2) 萬用字元形式可以減少型別引數,形式上往往更為簡單,可讀性也更好,所以,能用萬用字元的就用萬用字元
    3) 如果型別引數之間有依賴關係,或者返回值依賴型別引數,或者需要寫操作,則只能用型別引數
    4) 萬用字元形式和型別引數往往配合使用,定義必要的型別引數,使用萬用字元表達依賴,並接受更廣泛的資料型別

  3. 超型別萬用字元(無法用型別引數替代)

    public int demo(OtherUpperBound<? super E> a){
        
    }

    取自《Java程式設計的邏輯》8.2
    總結:
    1)萬用字元的目的是為了使方法介面更為靈活,可以接受更為廣泛的型別
    2)<? super E>用於靈活寫入或比較,使得物件可以寫入父型別的容器,使得父型別的比較方法可以應用於子類物件,它不能被型別引數形式替代
    3)<?>和<? extends E>用於靈活讀取,使得方法可以讀取E或E的任意子型別的容器物件,它們可以用型別引數的形式替代,但萬用字元形式更為簡潔

五、泛型的侷限性

  1. 父類實現了一個泛型介面,子類希望自定義泛型介面中的方法,只能重寫父類的實現

    class Base implements Comparable<Base>
    
    class Child extends Base
    
    // 希望重寫Comparable的比較方法
    /*
        class Child extends Base implements Comparable<Child>
        // 錯誤,因為型別擦除的原因,實際實現的都是Comparable介面,介面不允許被實現兩次
    */
    
    // 正確重寫Comparable的比較方法的方式
    class Child extends Base {
        @Override
        public int compareTo(Base o){
            if(!(o instanceof Child){
                throw new IllegalArgumentException();
            }
            Child c = (Child)o;
            \實現程式碼
            return 0;
        }
        \其他程式碼
    }
  2. 型別引數不能作為靜態變數和靜態方法的型別

    Class Normal<T>{
        public static demo1(T param){
            // 錯誤的方法
        }
        public static <E> void demo2(E param){
            // 正確的方法
        }
    }
  3. 不能通過型別引數建立物件

    T a = new T(); // error Type parameter `T` cannot be instantiated directly

六、泛型的使用細節

Java中的泛型是通過型別擦除實現的,型別引數在編譯時會被替換為Object

  1. 對於不同傳入的型別實參,生成的相應物件例項一樣

    Generics<Integer> demo = new Generics<>(123);
    Generics<String> demo2 = new Generics<>("test");
    System.out.println(demo.getClass() == demo2.getClass()); // true
  2. 靜態方法和泛型
    靜態方法無法訪問類上定義的泛型;如果靜態方法操作的引用資料型別不確定的時候,必須要將泛型定義在方法上。即:如果靜態方法要使用泛型的話,必須將靜態方法也定義成泛型方法

    /**
      如果在類中定義使用泛型的靜態方法,需要新增額外的泛型宣告(將這個方法定義成泛型方法)
      即使靜態方法要使用泛型類中已經宣告過的泛型也不可以。
      如:public static void show(T t){..},此時編譯器會提示錯誤資訊
         "StaticGenerator cannot be refrenced from static context"
    */
    public static <T> void show(T t){
    }
  3. 泛型的上下邊界新增,必須與泛型的宣告一起

    //在泛型方法中新增上下邊界限制的時候,必須在許可權宣告與返回值之間的<T>上新增上下邊界,  
    //即在泛型宣告的時候新增
    //public <T> T showKeyName(Generic<T extends Number> container)
    編譯器會報錯:"Unexpected bound"
    public <T extends Number> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        T test = container.getKey();
        return test;
    }
  4. 基本型別不能用於例項化型別引數
    泛型要求能包容的是物件型別,基本型別在java中不屬於物件
  5. 執行時型別查詢只適用於原始型別

    NormalGenericsClass<Integer> a = new NormalGenericsClass<>(0);
    
    a instanceof NormalGenericsClass<Integer>;
    // Error Illegal generic type for instanceof
    
    a instanceof NormalGenericsClass<T>;
    // Error Cannot resolve symbol `T`
    
    a instanceof NormalGenericsClass; // Pass
    a instanceof NormalGenericsClass<?>; // Pass
    
    a.getClass(); // class com.example.demo.generics.NormalGenericsClass
  6. 型別推斷只對賦值操作有效
    Eg:

    public class New{
        public static <K,V> Map<K,V> map(){
            return new HashMap<K,V>();
        }
    }
    
    /**
           -- 方法中傳參
     這時編譯器不會執行型別判斷。在這種情況下,編譯器認為:呼叫泛型方法後,  
     其返回值被賦給一個Object型別的變數
    */
    public class Test{
        public static void main(String args[]){
            fun(New.map());
        }
    }
    
    public class Test{
        public static void main(String args[]){
            fun(New.<String, Integer)map()); // 顯示地指明型別
        }
    }

參考資料:
[1]《Java程式設計的邏輯》
[2]《Thinking in Java》
[3] https://blog.csdn.net/s10461/…
[4] https://www.cnblogs.com/lwbqq…

相關文章