Java基礎(chǔ)---泛型

泛型概述以及泛型類

  • 泛型就是類型參數(shù)化,處理的數(shù)據(jù)類型不是固定的,而是可以作為參數(shù)傳入;
  • 泛型的核心: 告訴編譯器想使用什么類型,然后編譯器幫你處理一切;
public class GenericClass {

    private static class Pair<U,V>{
        private U first;
        private V second;

        public Pair(U first, V second) {
            this.first = first;
            this.second = second;
        }
        public U getFirst() {
            return first;
        }
        public V getSecond() {
            return second;
        }
    }

    public static void main(String[] args){
        Pair<String,Integer>pair = new Pair<>("zhangsan", 23);
    }
}

為什么Java不直接使用普通的Object類呢 ?

public class GenericClass2 {

    private static class Pair{ // Generic Class
        private Object first;
        private Object second;

        public Pair(Object first, Object second) {
            this.first = first;
            this.second = second;
        }

        public Object getFirst() {
            return first;
        }

        public Object getSecond() {
            return second;
        }
    }

    public static void main(String[] args){
        Pair pair = new Pair("zhangsan", 23);
        String name = (String) pair.getFirst();
        Integer age = (Integer) pair.getSecond();
    }
}

其實(shí)是可以這樣的,而且Java的內(nèi)部就是這樣實(shí)現(xiàn)的。

  • Java有Java編譯器和Java虛擬機(jī),編譯器將Java源代碼轉(zhuǎn)換為.class文件,虛擬機(jī)加載并運(yùn)行.class文件。
  • 對(duì)于泛型類,Java編譯器會(huì)將泛型代碼轉(zhuǎn)換為普通的非泛型代碼,就像上面的普通Pair類代碼及其使用代碼一樣,將類型參數(shù)T擦除,替換為Object,插入必要的強(qiáng)制類型轉(zhuǎn)換。Java虛擬機(jī)實(shí)際執(zhí)行的時(shí)候,它是不知道泛型這回事的,它只知道普通的類及代碼。
  • 再次強(qiáng)調(diào),Java泛型是通過擦除實(shí)現(xiàn)的,類定義中的類型參數(shù)如T會(huì)被替換為Object,在程序運(yùn)行過程中,不知道泛型的實(shí)際類型參數(shù),比如Pair<Integer>,運(yùn)行中只知道Pair,而不知道Integer。

那為什么還要使用泛型呢? 泛型有兩個(gè)好處:

  • 更好的安全性;
  • 更高的可讀性;

泛型方法

  • 要定義泛型方法,只需要將泛型參數(shù)列表置于返回值前;
  • 注意: 一個(gè)方法是不是泛型的, 和它所在的類是不是泛型沒有任何關(guān)系;
  • 泛型方法調(diào)用的時(shí)候,不需要指定類型參數(shù)的實(shí)際類型,Java編譯器會(huì)推斷出來;
public class GenericMethod {

    public static <T> int indexOf(T[] arr, T ele){ // Generic Method
        for(int i = 0; i < arr.length; i++){
            if(arr[i].equals(ele))
                return i;
        }
        return -1;
    }

    public static void main(String[] args){
        System.out.println(indexOf(new Integer[]{1, 3, 5, 7}, 5));
        System.out.println(indexOf(new String[]{"zhangsan", "lisi", "wangwu"}, "lisi"));
    }
}

實(shí)際上泛型類和泛型方法沒有聯(lián)系:

//泛型類的泛型和泛型方法的泛型沒有一點(diǎn)關(guān)系
public class GenericClassMethod<T> {

    public <T> void testMethod(T t){
        System.out.println(t.getClass().getName());
    }

    public <T> T testMethod1(T t){
        return t;
    }
    
    public static void main(String[] args){
        GenericClassMethod<String>gcm = new GenericClassMethod<>();
        gcm.testMethod("generic");
        Integer res = gcm.testMethod1(new Integer(10));
        System.out.println(res);
    }
}

// java.lang.String
// 10

三、泛型接口

接口也可以是泛型的,例如,Java中的ComparableComparator

public interface Comparable<T> {
    public int compareTo(T o);
}
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);
}

四、extends、<=、參數(shù)類型必須是給定的或者子類型

1、上界為某個(gè)具體類

  • 可以使用extends來限定一個(gè)上界,此時(shí)參數(shù)類型必須是給定的類型或者其子類型;
  • 比如定義一個(gè)NumberPair類,限定兩個(gè)參數(shù)類型必須是Number或者子類型,這樣限定之后,在子類中,first、second變量就可以當(dāng)做Number進(jìn)行處理了,比如調(diào)用Number類中的方法doubleValue()、intValue等;
// 示例代碼,(省略了上面的Pair<U,V>類)
public class GenericExtends {

    private static class NumberPair<U extends Number, V extends Number> extends Pair<U, V>{

        public NumberPair(U first, V second) { // must realize (achieve)
            super(first, second);
        }

        public double getSum(){
            return getFirst().doubleValue() + getSecond().intValue();
        }
    }

    public static void main(String[] args){
        NumberPair<Double, Integer>np = new NumberPair<>(3.3, 3); // <U, V>可以是 Number的子類,即 <=
        System.out.println(np.getSum());
    }
}

2.上界為某個(gè)接口

在泛型方法中,一種常見的限定類型是必須實(shí)現(xiàn)Comparable接口:

  • 下面的例子,要進(jìn)行元素的比較,要求元素必須實(shí)現(xiàn)Comparable接口, 所以給類型參數(shù)設(shè)置了一個(gè)上邊界Comparable 必須實(shí)現(xiàn)Comparable接口;
  • 可以理解為: T是一種數(shù)據(jù)類型,必須實(shí)現(xiàn)Comparable,且必須可以與相同類型的元素進(jìn)行比較;
ublic class GenericExtends2 {

    // 要進(jìn)行元素的比較,要求元素必須實(shí)現(xiàn)Comparable接口
    // 所以給類型參數(shù)設(shè)置了一個(gè)上邊界Comparable,T 必須實(shí)現(xiàn)Comparable接口
    public static <T extends Comparable> T getMax(T[] arr){
        T max = arr[0];
        for(int i = 0; i < arr.length; i++){
            if(arr[i].compareTo(max) > 0){
                max = arr[i];
            }
        }
        return max;
    }

    // 不過上面這么寫會(huì)有警告 因?yàn)镃omparable是一個(gè)泛型接口,它也需要一個(gè)類型參數(shù),所以下面的寫法比較好
    // 理解: T是一種數(shù)據(jù)類型,必須實(shí)現(xiàn)Comparable,且必須可以與相同類型的元素進(jìn)行比較
    public static <T extends Comparable<T> > T getMax2(T[] arr){
        T max = arr[0];
        for(int i = 0; i < arr.length; i++){
            if(arr[i].compareTo(max) > 0){
                max = arr[i];
            }
        }
        return max;
    }
}

3.上界為其他參數(shù)類型

  • 這里模仿ArrayList來創(chuàng)建一個(gè)類, 并想著實(shí)現(xiàn)其中的addAll()方法,但是如果不使用一個(gè)上界的話,會(huì)出現(xiàn)無法添加子類的情況,看下面的代碼,Number的集合理應(yīng)可以添加Integer類型的元素。
public class GenericExtends3 {

    // seems like ArrayList
    private static class DynamicArray<E>{

        private static final int DEFAULT_CAPACITY = 10;
        private int size;
        private Object[] data;

        public DynamicArray() {
            this.data = new Object[DEFAULT_CAPACITY];
        }
        private void ensureCapacity(int minCapacity){  // simulate ArrayList
            int oldCapacity = data.length;
            if(oldCapacity >= minCapacity)
                return;
            int newCapacity = oldCapacity * 2;
            if(newCapacity < minCapacity) //如果擴(kuò)展2倍還是小于minCapacity,就直接擴(kuò)展成為minCapacity
                newCapacity = minCapacity;
            data = Arrays.copyOf(data, newCapacity);
        }

        public void add(E e){
            ensureCapacity(size + 1);
            data[size++] = e;
        }

        public E get(int index){
            return (E)data[index];
        }

        public int size(){
            return size;
        }

        public E set(int index, E e){
            E oldValue = get(index);
            data[index] = e;
            return oldValue;
        }
        
        public void addAll(DynamicArray<E>arr){
            for(int i = 0; i < arr.size; i++){
                add(arr.get(i));
            }
        }
    }

    public static void main(String[] args){
        DynamicArray<Number>numbers = new DynamicArray<>();
        DynamicArray<Integer>ints = new DynamicArray<>();
        ints.add(10);
        ints.add(20);
//        numbers.addAll(ints); // compile error
    }
}

那個(gè)需求感覺上是可以,但是通過反證法可以發(fā)現(xiàn)是行不通的,看下面代碼以及解釋:

DynamicArray<Number>numbers = new DynamicArray<>();
numbers = ints; // 假設(shè)合法
numbers.add(new Double(3.3)); // 那么這一樣也可以,此時(shí)因?yàn)閚umbers和ints指向的同一個(gè)堆區(qū)空間,則ints中出現(xiàn)double類型值,顯然不合理

//再看一個(gè)例子
List<Object>olist = null;
List<String>slist = new ArrayList<>();
olist = slist; // err
//如果上述假設(shè)合理
olist.add(111);
//則slist中就會(huì)出現(xiàn)Integer類型的值,顯然不合理

所以,可以使用上界類型將addAll方法改進(jìn)如下:

//傳入的是T類型,限定為是E類型或者E的子類類型
public <T extends E>void addAll(DynamicArray<T>arr){
    for(int i = 0; i < arr.size; i++){
        add(arr.get(i));
    }
}

五、通配符?

1、有限定類型通配符的簡(jiǎn)單使用

public void addAll(DynamicArray<? extends E>arr){
    for(int i = 0; i < arr.size; i++){
        add(arr.get(i));
    }
}

<? extends E>表示有限定通配符,匹配E或E的某個(gè)子類型,具體是什么子類型是未知的。 看一下public <T extends E>void addAll(DynamicArray<T>arr)public void addAll(DynamicArray<? extends E>arr)的區(qū)別:

  • <T extends E>用于定義類型參數(shù),它聲明了一個(gè)類型參數(shù)T,可放在泛型類中類名的后面、泛型方法返回值前面;
  • <? extends E>用于實(shí)例化類型參數(shù),它用于實(shí)例化泛型變量中的類型參數(shù),只是這個(gè)具體類型是未知的,只知道它是E或E的子類型;

2、無限定類型通配符

簡(jiǎn)單使用: 第一種方式使用通配符,第二種方式使用類型參數(shù),可以達(dá)到同樣的目的:

//使用通配符  
public static int indexOf(DynamicArray<?> arr, Object elm){
    for(int i = 0; i < arr.size(); i++){
        if(arr.get(i).equals(elm))
            return i;
    }
    return -1;
}

//使用類型參數(shù) type parameter
public static <T> int indexOf2(DynamicArray<T> arr, Object elm){
    for(int i = 0; i < arr.size(); i++){
        if(arr.get(i).equals(elm))
            return i;
    }
    return -1;
}

但是通配符也有一些限制

  • 1)、第一條限制: 只能讀,不能寫
    比如 ,下面三行代碼就會(huì)報(bào)錯(cuò) :
public class WildcardCharacter {
    public static void main(String[] args){
        ArrayList<Integer> ints = new ArrayList<>();
        ArrayList<? extends Number> numbers = ints; // 使用extends通配符指定上界

        Integer a = 10;
//        numbers.add(a); // err
//        numbers.add(Object(a)); //err
//        numbers.add(Number(a)); //err
    }
}
  • 解釋: ?表示類型安全無知,? extends Number表示是Number的某個(gè)子類型,但不知道具體子類型,如果允許寫入,Java就無法確保類型安全性,所以干脆禁止;
  • 這種限制關(guān)系是好的,但是這使得很多理應(yīng)可以完成的操作可能會(huì)出現(xiàn)錯(cuò)誤;
    比如: 下面的代碼中最后兩行會(huì)報(bào)錯(cuò),原因就是不能修改?通配符的值:
public static void swap(ArrayList<?> arr, int i, int j){
     Object tmp = arr.get(i);
     arr.set(i, arr.get(j)); // can't change the value
     arr.set(j, tmp);
 }

再看一個(gè)例子:在方法傳遞參數(shù)的時(shí)候,不能往參數(shù)中添加元素:

public class GenericExtends4 {

    private static class Fruit {}
    private static class Apple extends Fruit{}
    private static class Pear extends Fruit{}
    private static class FuShiApple extends Apple{}

    static class Clazz<T extends Fruit>{  //創(chuàng)建的類必須是Fruit的子類//為了自己類中使用這個(gè)類
    }

    public static void main(String[] args) {
        Clazz<Fruit>t = new Clazz<>();  // <= 關(guān)系
        Clazz<Apple>t2 = new Clazz<>();
        Clazz<Pear>t3 = new Clazz<>();
        Clazz<FuShiApple>t4= new Clazz<>();


        //調(diào)用方法
        List<? extends Fruit> list1 = new ArrayList<>();
        add(list1);
        List<Fruit> list2 = new ArrayList<>();
        add(list2);
        List<Apple> list3 = new ArrayList<>();
        add(list3);
        List<? extends Apple> list4 = new ArrayList<FuShiApple>();  //存放Apple以及它的子類
        add(list4);
        List<FuShiApple> list5 = new ArrayList<>();
        add(list5);


        //?為什么錯(cuò)誤 : 因?yàn)?? 等同于? extends Object :不是<= Fruit的 下面兩個(gè)是一樣的
        List<?>list6 = new ArrayList<>();
        List<? extends Object>list7 = new ArrayList<>();
        //add(list6); // err
        //add(list7); // err
    }

    // 為了保證向下兼容的一致性,不能添加元素
    public static void add(List<? extends Fruit> list) {
        /** 不能往里面加這樣的對(duì)象 不能用于添加數(shù)據(jù)
         list.add(new Fruit());
         list.add(new Apple());
         list.add(new Pear());
         */
        list.add(null);
    }
}
  • 2)、第二條限制: 參數(shù)類型間的依賴關(guān)系
    如果參數(shù)類型之間有依賴關(guān)系,也只能用類型參數(shù),比如下面的例子:
// S和D要么相同,要么S是D的子類,否則類型不兼容,有編譯錯(cuò)誤
public static <D,S extends D> void copy(ArrayList<D> dest, ArrayList<S> src){
    for(int i=0; i<src.size(); i++)
        dest.add(src.get(i));
}

// 可以使用通配符簡(jiǎn)化一下
public static <D> void copy2(ArrayList<D> dest, ArrayList<? extends D> src){
    for(int i=0; i<src.size(); i++)
        dest.add(src.get(i));
}
  • 3)、第三條限制: 如果返回值依賴于類型參數(shù),也不能用通配符
//不能使用通配符,只能用類型參數(shù),因?yàn)橐祷?public static <T extends Comparable<T> > T max(ArrayList<T> arr){
    T max = arr.get(0);
    for(int i = 1; i < arr.size(); i++){
        if(arr.get(i).compareTo(max)>0){
            max = arr.get(i);
        }
    }
    return max;
}

那么到底該用通配符還是類型參數(shù)呢?

  • 通配符形式都可以用類型參數(shù)的形式來替代,通配符能做的,用類型參數(shù)都能做。
  • 通配符形式可以減少類型參數(shù),形式上往往更為簡(jiǎn)單,可讀性也更好,所以,能用通配符的就用通配符。
  • 如果類型參數(shù)之間有依賴關(guān)系,或者返回值依賴類型參數(shù),或者需要寫操作,則只能用類型參數(shù)。

通配符形式和類型參數(shù)往往配合使用,比如,上面的copy2方法,定義必要的類型參數(shù),使用通配符表達(dá)依賴,并接受更廣泛的數(shù)據(jù)類型。

六、super、>=、超類型通配符

  • 簡(jiǎn)單的來說,super和extends剛好相反,匹配的是>= E的類型;
  • 相當(dāng)于是規(guī)定了一個(gè)下界,可以匹配 >=的類型;

1、使用場(chǎng)景

看它的使用場(chǎng)景, 在DynamicArray中添加一個(gè)copyTo方法,功能是將當(dāng)前對(duì)象容器中的數(shù)拷貝到傳入的參數(shù)dest容器中:

//add current value to the dest collection
 public void copyTo(DynamicArray<E>dest){
     for(int i = 0; i < dest.size(); i++)
         dest.add(this.get(i));
 }

然后不使用super,看下面的代碼,最后一行就會(huì)報(bào)錯(cuò),但是將Integer數(shù)組拷貝到Number數(shù)組理應(yīng)是可以的:

 public static void main(String[] args){
       DynamicArray<Integer>ints = new DynamicArray<>();
       ints.add(3);
       ints.add(4);

       DynamicArray<Number>nums = new DynamicArray<>();
       ints.copyTo(nums);  // 將ints 中的元素拷貝到nums,本應(yīng)該是可以的,但是如果沒有? super E就不行
   }

使用超類型通配符就可以解決上面的問題:

public void copyTo(DynamicArray<? super E>dest){
    for(int i = 0; i < dest.size(); i++)
        dest.add(this.get(i));
}

2、沒有< T super E>(有<T extend E>)

比較類型參數(shù)限定與超類型通配符,類型參數(shù)限定只有extends形式,沒有super形式,比如前面的copyTo方法,它的通配符形式的聲明為:

public void copyTo(DynamicArray<? super E> dest)

如果類型參數(shù)限定支持super形式,則應(yīng)該是:

public <T super E> void copyTo(DynamicArray<T> dest)

但是,Java并不支持這種語法。對(duì)于有限定的通配符形式<? extends E>,可以用類型參數(shù)限定替代,但是對(duì)于類似上面的超類型通配符,則無法用類型參數(shù)替代。
再看和extends使用方法傳遞參數(shù)的對(duì)比: (在方法傳遞中可以添加自己和子類的數(shù)據(jù), 區(qū)別于extends,extends都不可以添加)

public class GenericSuper2 {

    private static class Fruit {}
    private static class Apple extends Fruit{}
    private static class Pear extends Fruit{}
    private static class FuShiApple extends Apple{}

    static class Clazz<T extends Fruit>{  //創(chuàng)建的類必須是Fruit的子類//為了自己類中使用這個(gè)類

    }

    public static void main(String[] args) {
        List<Apple>list1 = new ArrayList<>();
        add(list1);
        List<Fruit>list2 = new ArrayList<>();
        add(list2);
        List<Object>list3 = new ArrayList<>();
        add(list3);

        //?super的使用
        List<? super Apple>list4 = new ArrayList<>();
        add(list4);
        List<? super Apple>list5 = new ArrayList<>();
        add(list5);
        
        List<FuShiApple>list6 = new ArrayList<>();  // < 的不行
//      add(list6); // err
        
    }

    //只要是Apple的祖先都可以調(diào)用這個(gè)方法 >= 
    public static void add(List <? super Apple> list) {  
        /*** 不能用于添加父類對(duì)象的數(shù)據(jù)
         * list.add(new Fruit());
         */
        //區(qū)別于extends, 可以添加自己和子類的數(shù)據(jù)
        list.add(new Apple());
        list.add(new FuShiApple());
    }
}

七、通配符extends、super比較

通配符比較:

  • 共同點(diǎn): 目的都是為了使方法接口更為靈活,可以接受更為廣泛的類型。

  • <? super E>用于靈活寫入或比較,使得對(duì)象可以寫入父類型的容器(>=),使得父類型的比較方法可以應(yīng)用于子類對(duì)象。

  • <? extends E>用于靈活讀取,使得方法可以讀取E或E的任意子類型的容器對(duì)象。
    Java容器類的實(shí)現(xiàn)中,有很多這種用法,比如,Collections中就有如下一些方法:

public static <T extends Comparable<? super T>> void sort(List<T> list)

public static <T> void sort(List<T> list, Comparator<? super T> c)

public static <T> void copy(List<? super T> dest, List<? extends T> src)

public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)

八、泛型擦除

  • 泛型信息只存在于代碼編譯階段,在進(jìn)入 JVM 之前,與泛型相關(guān)的信息會(huì)被擦除掉,專業(yè)術(shù)語叫做類型擦除。

  • 通俗地講,泛型類和普通類在 java 虛擬機(jī)內(nèi)是沒有什么特別的地方;
    看下面代碼:

public class GenericWipe {
    public static void main(String[] args){

        List<String> slist = new ArrayList<>();
        List<Integer> ilist = new ArrayList<>();

        System.out.println(slist.getClass() == ilist.getClass());
    }
}

這段代碼的輸出結(jié)果是true。正如一開始說的,編譯器會(huì)將T擦除,然后替換成為Object(并不完全正確),在必要的時(shí)候進(jìn)行強(qiáng)制類型轉(zhuǎn)換。

再看以下代碼的輸出結(jié)果:

public class GenericWipe<T> {

    private T obj;

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

    public static void main(String[] args){
        GenericWipe<String>gw = new GenericWipe<>("wipe");
        Class gwClass = gw.getClass();
        System.out.println(gwClass.getName()); // 得到運(yùn)行時(shí)的狀態(tài)信息,運(yùn)行時(shí)是真實(shí)的類型

        System.out.println("--------------------------");

        Field[] fs = gwClass.getDeclaredFields();  //得到在JVM中的類型 
        for ( Field f:fs)
            System.out.println("Field name " + f.getName() + " type:" + f.getType().getName());
    }
}
JavaPrimary.Generic.GenericWipe
--------------------------
Field name obj type:java.lang.Object

第一種類型是Class 的類型是 GenericWipe,并不是 GenericWipe<T> 這種形式,第二種類型是Jvm中的類型; 那是不是泛型類被類型擦除后,相應(yīng)的類型就被替換成 Object 類型呢?這種說法不是完全正確的。

更改一下代碼:

public class GenericWipe<T extends String> {  // <= String

    private T obj;

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

    public static void main(String[] args){
        GenericWipe<String>gw = new GenericWipe<>("wipe");
        Class gwClass = gw.getClass();
        System.out.println(gwClass.getName()); // 得到運(yùn)行時(shí)的狀態(tài)信息,運(yùn)行時(shí)是真實(shí)的類型

        System.out.println("--------------------------");

        Field[] fs = gwClass.getDeclaredFields();  //得到在JVM中的類型
        for ( Field f:fs)
            System.out.println("Field name " + f.getName() + " type:" + f.getType().getName());
    }
}

輸出結(jié)果:

JavaPrimary.Generic.GenericWipe
--------------------------
Field name obj type:java.lang.String

可以看到,第二個(gè)輸出變成了String。所以結(jié)論如下:

  • 在泛型類被類型擦除的時(shí)候,之前泛型類中的類型參數(shù)部分如果沒有指定上限,如<T>則會(huì)被轉(zhuǎn)譯成普通的 Object 類型;
  • 如果指定了上限如 <T extends String> 則類型參數(shù)就被替換成類型上限。
    所以,在反射中,add() 這個(gè)方法對(duì)應(yīng)的 Method 的簽名應(yīng)該是 Object.class。也就是說,如果你要在反射中找到 add 對(duì)應(yīng)的 Method,你應(yīng)該調(diào)用 getDeclaredMethod("add",Object.class) 否則程序會(huì)報(bào)錯(cuò),提示沒有這么一個(gè)方法,原因就是類型擦除的時(shí)候,T 被替換成 Object 類型了。
public class GenericWipe<T> {  // <= String
    // public class GenericWipe<T extends String> {  // <= String
    private T obj;

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

    public void add(T obj){

    }

    public static void main(String[] args){
        GenericWipe<String>gw = new GenericWipe<>("wipe");
        Class gwClass = gw.getClass();
        System.out.println(gwClass.getName()); // 得到運(yùn)行時(shí)的狀態(tài)信息,運(yùn)行時(shí)是真實(shí)的類型

        System.out.println("--------------------------");
        Method[] methods = gwClass.getDeclaredMethods();
        for ( Method m:methods ){
            System.out.println(" method:" + m.toString());
        }
    }
}

更加詳細(xì)的解釋見這里

九、泛型注意事項(xiàng)

1、基本類型不能用于實(shí)例化類型參數(shù),也就是泛型類或者泛型方法中,不接受 8 種基本數(shù)據(jù)類型。

比如:

List<int> li = new ArrayList<>(); // err
List<boolean> li = new ArrayList<>(); // err
List<Integer> li = new ArrayList<>();  //ok
List<Boolean> li1 = new ArrayList<>(); // ok

2、運(yùn)行時(shí)類型信息不適用于泛型

這個(gè)也就是上面說的泛型擦除,泛型不支持運(yùn)行時(shí)的信息(和反射有關(guān))。

instanceof后面是接口或類名,instanceof是運(yùn)行時(shí)判斷,也與泛型無關(guān),所以,Java也不支持類似如下寫法:

if(p1 instanceof Pair<Integer>)

3、Java 不能創(chuàng)建具體類型的泛型數(shù)組

例如下面的list1和list2創(chuàng)建是錯(cuò)誤的,但是后面的?可以,因?yàn)?代表的是未知類型:

public class GenericOther {
    public static void main(String[] args){
//        List<Integer>[] list1 = new ArrayList<Integer>[]; // complier err
//        List<Boolean> list2 = new ArrayList<Boolean>[]; // complier err
        List<?>[] list3 = new ArrayList<?>[10]; // 這個(gè)卻可以 ? 代表的是未知類型
        list3[1] = new ArrayList<String>();
        List<?> tmp = list3[1];
        System.out.println(tmp.get(0));
//        tmp.set(1, 2); complier err
    }
}
  • List<Boolean>List<Boolean> 在 Jvm 中等同于List<Object> ,所有的類型信息都被擦除,程序也無法分辨一個(gè)數(shù)組中的元素類型具體是 List<Integer>類型還是 List<Boolean> 類型。
  • 代表未知類型,涉及的操作都基本上與類型無關(guān),Jvm 不針對(duì)它對(duì)類型作判斷,因此它能編譯通過,但是,它只能讀,不能寫。比如,上面的 tmp 這個(gè)局部變量,它只能進(jìn)行 get() 操作,不能進(jìn)行 add() 操作。
    再從如果可以創(chuàng)建泛型數(shù)組會(huì)出現(xiàn)什么樣的問題來看: 數(shù)組可以進(jìn)行不同類型之間的轉(zhuǎn)換,但是也需要注意使用,使用不當(dāng)就會(huì)造成運(yùn)行時(shí)異常,而如果運(yùn)行創(chuàng)建泛型數(shù)組也會(huì)產(chǎn)生類似的問題,所以Java干脆禁止。
public class NoGenericClassArray {
     private static class Pair { // Generic Class
        private Object first;
        private Object second;
        public Pair(Object first, Object second) {
            this.first = first;
            this.second = second;
        }
        public Object getFirst() {
            return first;
        }
        public Object getSecond() {
            return second;
        }
    }
    public static void main(String[] args) {

        // 數(shù)組是Java直接支持的概念,它知道數(shù)組元素的實(shí)際類型,
        // 它知道Object和Number都是Integer的父類型,所以這個(gè)操作是允許的。
        Integer[] ints = new Integer[10];
        Number[] numbers = ints; //  is ok
        Object[] objs = ints;

        // 雖然Java允許這種轉(zhuǎn)換,但是如果使用不恰當(dāng),就有可能引起運(yùn)行時(shí)異常
        Integer[] ints2 = new Integer[10];
        Object[] objs2 = ints2;
        objs2[0] = "hello"; // RuntimeException

//        Pair<Object, Integer>[] options = new Pair<Object, Integer>[3]; //如果可以,那最后一行就會(huì)不會(huì)編譯錯(cuò)誤,這樣顯然是不行的
//        Object[] objs = options;
//        objs[0] = new Pair<Double, String>(12.34, "hello");
        
    }
}

4、不能通過類型參數(shù)創(chuàng)建對(duì)象

下面的寫法是非法的。

T elm = new T();
T[] arr = new T[10];

如果允許,本來你以為創(chuàng)建的就是對(duì)應(yīng)類型的對(duì)象,但由于類型擦除,Java只能創(chuàng)建Object類型的對(duì)象,而無法創(chuàng)建T類型的對(duì)象。 那如果確實(shí)希望根據(jù)類型創(chuàng)建對(duì)象呢?需要設(shè)計(jì)API接受類型對(duì)象,即Class對(duì)象,并使用Java中的反射機(jī)制,如果類型有默認(rèn)構(gòu)造方法,可以調(diào)用Class的newInstance方法構(gòu)建對(duì)象:

public static <T> T create(Class<T> type){
    try {
        return type.newInstance();
    } catch (Exception e) {
        return null;
    }
}

5、泛型類類型參數(shù)不能用于靜態(tài)變量和方法,泛型類中泛型只能用在成員變量上,只能使用引用類型,在接口中泛型只能只能用在抽象方法中,全局常量不能使用泛型

對(duì)于泛型類聲明的類型參數(shù),可以在實(shí)例變量和方法中使用,但在靜態(tài)變量和靜態(tài)方法中是不能使用的。下面的寫法是非法的:

public class Singleton<T> {
    private static T instance;
    public synchronized static T getInstance(){
        if(instance==null){
             // 創(chuàng)建實(shí)例
        }
        return instance;
    }
}    

如果合法的話,那么對(duì)于每種實(shí)例化類型,都需要有一個(gè)對(duì)應(yīng)的靜態(tài)變量和方法。但由于類型擦除,Singleton類型只有一份,靜態(tài)變量和方法都是類型的屬性,且與類型參數(shù)無關(guān),所以不能使用泛型類類型參數(shù)。
但是,對(duì)于靜態(tài)方法,它可以是泛型方法,可以聲明自己的類型參數(shù),這個(gè)參數(shù)與泛型類的類型參數(shù)是沒有關(guān)系的。

6、子類繼承父類泛型

注意子類繼承泛型的注意事項(xiàng): 可以有四種方式,可以按需實(shí)現(xiàn),或者定義子類自己的泛型等

public class SubClass {

    public abstract class Father<T1,T2> {     //注意實(shí)際過程中一般定義為抽象的父類
        T1 age;
        public abstract void test(T2 name);  //抽象方法
    }

    //1)全部保留
    class C1<T1,T2,A,B> extends Father<T1,T2>{  //除了繼承父類,可以自己"加""富二代"(不是負(fù)二代)

        @Override
        public void test(T2 name) {
            // this.age -->  T1類型
        }
    }

    //2)部分保留
    class C2<T2,A,B> extends Father<Integer,T2>{
        @Override
        public void test(T2 name) {
            // this.age -->  Integer類型
        }
    }

    //不保留: -->按需實(shí)現(xiàn)
    class C3<A,B> extends Father<Integer,String>{
        @Override
        public void test(String name) { //注意這里是String 不是T2
            // this.age -->  Integer類型
        }
    }

    //2)沒有類型 : 擦除 (類似于Object)//相當(dāng)于
    class C4<A,B> extends Father {  //相當(dāng)于  class C4<A,B> extends Father<Object,Object>{}

        @Override
        public void test(Object name) { //注意這里是Object(完全沒有類型(擦除))
            // this.age -->  Object類型
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容