泛型

1.定義

泛型最精準的定義:==參數化類型==。具體點說就是處理的數據類型不是固定的,而是能夠做爲參數傳入。定義泛型類、泛型接口、泛型方法,這樣,同一套代碼,能夠用於多種數據類型。(看到過有人面試問這個,感受這個解釋泛型最言簡意賅)java

2.實例

2.1 標識符號的字母規範.

雖然標識符號能夠隨意取,但爲了提升可讀性,通常遵循如下規則.面試

E — Element,經常使用在java Collection裏,如:List,Iterator,Set K,V — Key,Value,表明Map的鍵值對 N — Number,數字 T — Type,類型,如String,Integer等等數組

2.2 泛型類

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;
    }
}
複製代碼

泛型的字母能夠隨意取,可是本身能夠規範點,Key,Value 能夠用(K, V)Element 能夠用E。bash

2.3 泛型接口

public interface Generator<T> {
    public T next();
}
複製代碼

2.4 泛型方法

public < E > void printArray( E[] inputArray )
   {
      // 輸出數組元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
複製代碼

3.泛型通配符

3.1 上界限定通配符 <? extends E>

接收E類型以及其子類 被該泛型通配符限制的數組==只能取不能存==。由於該泛型爲其子類,不能肯定究竟幾級子類,所以不能加數據。只能取,取出的數據能保證必定能轉成E類型。ide

3.2 下界限定通配符 <?super E>

接收E類型以及其父類型。所以==只能存不能取==。若是要取的話只能是保證爲Object類型的數據,這樣沒意義。而存的話能存E以及E的子類。函數

3.3 無限通配符 <?>

無界通配符意味着可使用任何對象,所以使用它相似於使用原生類型。但它是有做用的,原生類型能夠持有任何類型,而無界通配符修飾的容器持有的是某種具體的類型。舉個例子,在List類型的引用中,不能向其中添加Object, 而List類型的引用就能夠添加Object類型的變量。post

4.擦除帶來的問題

在泛型代碼內部,沒法得到任何有關泛型參數類型的信息。
複製代碼

1 泛型類型不能顯式地運用在運行時類型的操做當中,例如:轉型、instanceof 和 new。由於在運行時,全部參數的類型信息都丟失了。ui

public class Erased<T> {
    private final int SIZE = 100;
    public static void f(Object arg) {
        //編譯不經過
        if (arg instanceof T) {
        }
        //編譯不經過
        T var = new T();
        //編譯不經過
        T[] array = new T[SIZE];
        //編譯不經過
        T[] array = (T) new Object[SIZE];
    }
}
複製代碼

2 泛型聲明的對象在類內部不能獲取某個類的特定方法。代碼以下this

class HasF {
    public void f() {
        System.out.println("HasF.f()");
    }
}
public class Manipulator<T> {
    private T obj;

    public Manipulator(T obj) {
        this.obj = obj;
    }

    public void manipulate() {
        obj.f(); //沒法編譯 找不到符號 f()
    }

    public static void main(String[] args) {
        HasF hasF  = new HasF();
        Manipulator<HasF> manipulator = new Manipulator<>(hasF);
        manipulator.manipulate();

    }
複製代碼

在這個例子中因爲泛型在代碼內部是沒法知道其確切類型,於是也就不能知道obj 是否有f()方法。上面這個問題解決方法能夠給T範型加入上邊界即 T extends HasF,這樣就解決這個問題了。spa

5.擦除補償

5.1 類型判斷問題

class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
    Class<T> kind;
    public ClassTypeCapture(Class<T> kind) {
        this.kind = kind;
    }
    public boolean f(Object arg) {
        return kind.isInstance(arg);
    }
    public static void main(String[] args) {
        ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);
        System.out.println(ctt1.f(new Building()));
        System.out.println(ctt1.f(new House()));
        ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.print(ctt2.f(new House()));
    }
}

複製代碼

泛型參數的類型沒法用instanceof關鍵字來作判斷。因此咱們使用類類型來構造一個類型判斷器,判斷一個實例是否爲特定的類型。

5.2 建立類型實例

Erased.java中不能new T()的緣由有兩個,一是由於擦除,不能肯定類型;而是沒法肯定T是否包含無參構造函數。

爲了不這兩個問題,咱們使用顯式的工廠模式:

interface IFactory<T> {
    T create();
}

class Foo2<T> {
    private T x;

    public <F extends IFactory<T>> Foo2(F factory) {
        x = factory.create();
    }
}

class IntegerFactory implements IFactory<Integer> {
    @Override
    public Integer create() {
        return new Integer(0);
    }
}

class Widget {
    public static class Factory implements IFactory<Widget> {
        @Override
        public Widget create() {
            return new Widget();
        }
    }
}

public class FactoryConstraint {
    public static void main(String[] args) {
        new Foo2<Integer>(new IntegerFactory());
        new Foo2<Widget>(new Widget.Factory());
    }
}

複製代碼

經過特定的工廠類實現特定的類型可以解決實例化類型參數的需求。

5.3 建立泛型數組

通常不建議建立泛型數組。儘可能使用ArrayList來代替泛型數組。可是在這裏仍是給出一種建立泛型數組的方法。

public class GenericArrayWithTypeToken<T> {
    private T[] array;

    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int sz) {
        array = (T[]) Array.newInstance(type, sz);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

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

    public static void main(String[] args) {
        GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>(Integer.class, 10);
        Integer[] ia = gai.rep();
    }
}

複製代碼

這裏咱們使用的仍是傳參數類型,利用類型的newInstance方法建立實例的方式。

6.其餘注意點

  1. 任何基本類型都不能做爲類型參數
  2. 靜態變量是被泛型類的全部實例所共享的。對於聲明爲MyClass的類,訪問其中的靜態變量的方法仍然是 MyClass.myStaticVar。不論是經過new MyClass仍是new MyClass建立的對象,都是共享一個靜態變量。(擦除致使) 3.泛型的類型參數不能用在Java異常處理的catch語句中。由於異常處理是由JVM在運行時刻來進行的。因爲類型信息被擦除,JVM是沒法區分兩個異常類型MyException(String)和MyException(Integer)的。對於JVM來講,它們都是 MyException類型的。也就沒法執行與異常對應的catch語句。

參考文章:

  1. www.jianshu.com/p/4caf2567f…
  2. www.jianshu.com/p/7a1b09e62…
  3. juejin.im/post/584d36…
相關文章
相關標籤/搜索