面試必問系列:3、談談你對泛型的理解

01、泛型是什麼?

​ A:泛型其實就是在定義類、接口、方法的時候不侷限地指定某一種特定類型,而讓類、接口、方法的調用者來決定具體使用哪一種類型的參數。

​ B:比如一個水杯生產的時候不用指定它將來幹什麼?而是由將來的使用者決定放入什麼。

​ C:其實就是一句話:我是一個泛型隊列,狗可以站進來,貓也可以站進來,但最好不要既站貓,又站狗!

​ 別讓貓狗站在隊列裏

注:在Java中,經常用T、E、K、V等形式的參數來表示泛型參數。

T:代表一般的任何類。
E:代表 Element 的意思,或者 Exception 異常的意思。
K:代表 Key 的意思。
V:代表 Value 的意思,通常與 K 一起配合使用。



02、不使用泛型,放入貓狗

 

package fanxing;

import java.util.HashMap;
import java.util.Map;

public class MaoGou {
    class Dog {
    }
    class Cat {
    }
    public static void main(String[] args) {
        MaoGou maoGou = new MaoGou();
        Map map = new HashMap();
        map.put("dog", maoGou.new Dog());
        map.put("cat", maoGou.new Cat());
        Cat cat = (Cat) map.get("dog");
        Dog dog = (Dog) map.get("cat");
        System.out.println(dog + "dog");
        System.out.println(cat + "cat");
    }
}

Exception in thread "main" java.lang.ClassCastException:
fanxing.MaoGou$Dog cannot be cast to fanxing.MaoGou$Cat
編譯階段沒有問題,運行時報錯,就是放入的是貓,不能取出的是狗


2.1那如何解決上述問題呢?用泛型!!!
直接在map定義的時候指定要放入的對象

package fanxing;
import java.util.HashMap;
import java.util.Map;
public class MaoGou {
    class Dog {
    }
    class Cat {
    }
    public static void main(String[] args) {
        MaoGou maoGou = new MaoGou();
        Map map = new HashMap();
//        map.put("dog", maoGou.new Dog());
//        map.put("cat", maoGou.new Cat());
//        Cat cat = (Cat) map.get("dog");
//        Dog dog = (Dog) map.get("cat");
//        System.out.println(dog + "dog");
//        System.out.println(cat + "cat");
        Map<String, Cat> map2 = new HashMap<>();
        map2.put("cat2", maoGou.new Cat());
        Cat cat2 = map2.get("cat2");
        System.out.println("cat2    " + cat2);
    }

}

 

03、泛型的存在階段是編譯階段還是運行階段?
有人說,Java的泛型做的只是表面功夫——泛型信息存在於編譯階段(狗隊在編譯時不允許站貓),運行階段就消失了(運行時的隊列裏沒有貓的信息,連狗的信息也沒有)——這種現象被稱爲“類型擦除”。

package fanxing;
import java.util.HashMap;
import java.util.Map;
public class MaoGou {
    class Dog {
    }
    class Cat {
    }
    public static void main(String[] args) {
        MaoGou maoGou = new MaoGou();
        Map map = new HashMap();
        Map<String, Cat> catMap = new HashMap<>();
        Map<String, Dog> dogMap = new HashMap<>();
        System.out.println(catMap.getClass());
        System.out.println(dogMap.getClass());
    }

}


輸出結果:
class java.util.HashMap
class java.util.HashMap
分析:
也就是說,Java代碼在運行的時候並不知道catMap的鍵位上放的是Cat,dogMap的鍵位上放的是Dog。
    即滿足了上述說的《類型擦除》


3.1 那麼,試着想一些可怕的事情:
既然運行時泛型的信息被擦除了,而反射機制是在運行時確定類型信息的,那麼利用反射機制,是不是就能夠在鍵位爲Cat的Map上放一隻Dog呢?

package fanxing;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class MaoGou {
    class Dog { }
    class Cat { }
    public static void main(String[] args) {
        MaoGou maoGou = new MaoGou();
        Map map = new HashMap();

        Map<String, Cat> catMap = new HashMap<>();
        try {
            Method method =catMap.getClass().getDeclaredMethod("put",Object.class,Object.class);
            method.invoke(catMap,"dog",maoGou.new Dog());
            System.out.println(catMap);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}


輸出結果:{dog=fanxing.MaoGou$Dog@3f99bd52}
看到沒?我們竟然在鍵位爲Cat的Map上放了一隻Dog!注:Java的設計者在JDK 1.5時才引入了泛型,但爲了照顧以前設計上的缺陷,同時兼容非泛型的代碼,
不得不做出了一個折中的策略:編譯時對泛型要求嚴格,運行時卻把泛型擦除了——要兼容以前的版本,
還要升級擴展新的功能,真的很不容易!
 

04、自定義泛型

package fanxing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class PetHouse<T> {
    private List<T> list;

    public PetHouse() {

    }

    public void add(T item) {
        list.add(item);
    }

    public T get() {
        return list.get(0);
    }

    public static void addTest(List<?> list) {
        Object o = new Object();
        // list.add(o); // 編譯報錯
        // list.add(1); // 編譯報錯
        // list.add("ABC"); // 編譯報錯
        list.add(null);
    }


}


05、泛型通配符
? 通配符類型 無邊界的通配符(Unbounded Wildcards), 就是<?>, 比如List<?>
       無邊界的通配符的主要作用就是讓泛型能夠接受未知類型的數據. 
<? extends T> 表示類型的上界,表示參數化類型的可能是T 或是 T的子類
<? super T> 表示類型下界(Java Core中叫超類型限定),
    表示參數化類型是此類型的超類型(父類型),直至Object
注意: 你可以爲一個泛型指定上邊界或下邊界, 但是不能同時指定上下邊界.

上界限定符接受 extends 後面類的本身與其子類, 下界限定符接受 super 後面類的本身與其父類。無限定通配符接受任何類。

5.1 ?不能使用add()方法,NULL除外

public static void addTest(List<?> list) {
    Object o = new Object();
    // list.add(o); // 編譯報錯
    // list.add(1); // 編譯報錯
    // list.add("ABC"); // 編譯報錯
    list.add(null);
}



所以“?”聲明的集合,不能往此集合中添加元素,所以它只能作爲生產者(亦即它只能被迭代),如下:

  public static void main(String[] args) {
        List<?> names = new ArrayList<String>() {
            {
                for (int i = 0; i < 10; i++) {
                    add("A" + i);
                }
            }
        };
        System.out.println(names.toString());
        //  只能以Object迭代元素
        for (Object name : names) {
            System.out.println(name);
        }
    }



5.2 “? extends T”也不能添加元素 只能存指定類型或者其子類

public static void main(String[] args) {
        List<? extends String> names = new ArrayList<String>() {
            {
                for (int i = 0; i < 10; i++) {
                    add("A" + i);
                }
            }
        };
        System.out.println(names.toString());
        // 相比?更能準確定位元素類型
        for (String name : names) {
            System.out.println(name);
        }

    }


​ 因爲Integer Double Long都是Number的子類

 List<? extends Number> numbers =new ArrayList<Number>(){
            {
                add(1);
                add(1.2);
                add((long)1);
                
                //add("2"); 報錯
            }
        };

 

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