泛型Generic
尚硅谷JavaSE筆記-25

泛型Generic

泛型是JDK5新增的項目

特性

  • 帶泛型的結構在實例化時,可以指明具體的泛型類型,類似於貼<標籤>
  • 指明完後,使用到類的地方全都換成該泛型
  • 用一個比喻,我自訂一個帶<>泛型的校車類,當實例化具體的校車A時,在<>中指明上裝的都是<北一女學生>,那裝進去的就只能是北一女學生,
  • 當我對這台校車A進行各種操作時,就不用考慮裡面人是啥種類(以前都用Object盛裝,然後操作時還要先instanceOf確認種類再強轉),現在跟校車A牽扯的通通都是<北一女學生>不用囉嗦
  • 承上例,我也可以實例化另一台校車B<建中男學生>。一個優勢在於我規劃校車這個類時,先不用考慮具體要裝的是啥類而用泛型,用這個泛型先寫好通用的方法,留著空給實例化時再去決定實際調用的類

使用範例

// 造一個指明泛型為Integer, String的Map
HashMap<Integer, String> stringHashMap = new HashMap<Integer, String>();
stringHashMap.put(1, "小名");
stringHashMap.put(2, "老王");
stringHashMap.put(3, "阿洲"); // 此時如果想放進非Integer,String的東西則報錯

// 轉成EntrySet,叫Map.Entry是因為Entry是一個內部接口
Set<Map.Entry<Integer, String>> entries = stringHashMap.entrySet();
// 通通都自動帶入泛型,而不用再去判斷種類或強轉
// 試著遍歷
Iterator<Map.Entry<Integer, String>> iterator = entries.iterator();
while (iterator.hasNext()) {
    Map.Entry<Integer, String> next = iterator.next();
    Integer key = next.getKey();
    String value = next.getValue();
    System.out.println(key + "=>" + value);
}

聲明

  • 集合接口或集合類都天生帶有泛型的結構,自建的類或接口也能聲明泛型
  • 泛型類可能有多個參數,可以都放在<>內用",“隔開,例如:<E1, E2, E3>
  • 聲明構造器時不用寫泛型
  • 靜態方法不能使用類的泛型(因為泛型在實例化時才決定,當能不能跟static共存)
  • 異常類不能聲明為泛型(因為它祖宗就沒有<>,繼承再怎樣都生不出來)

實例化

  • 實例化時,指定的泛型必須是一個實際類,不能是基本數據類型(有需要就用包裝類)
  • 泛型可以嵌套使用
  • 帶泛型的接口或類在實例化時如果不指定泛型,就當作Object處理,但不等價於Object
  • 要嘛一路都用泛型,要嘛都不要用

泛型方法

  • 在方法中出現了泛型的結構,泛形參數與類的泛型無關

  • 我是為了寫一個"各種類"都能丟進來用的方法,舉例:

    public static <E> List<E> copyFromArrayToList(E[] arr) {
        ArrayList<E> arrayList = new ArrayList<>();
        for (E e : arr) {
            arrayList.add(e);
        }
        return arrayList;
    }
    
  • 如果不在方法前加<E>指明它是泛型方法,系統會把我的形參(E[] arr)的E[]看成一個自訂類。但我這裡要的效果是"E可以是int數組或String數組…等等都能放進來"的意思

  • 所以泛型方法可以是靜態的,因為是在調用時就指明到底是哪個類了

繼承泛型類

  • 泛型不同的引用不能相互賦值,即使本來是父子類關係,例如:List<Object>List<String> 已經生殖隔離了,但可以用通配符”?“再把他們搞回一家人

  • 但如果<>內泛型類一樣,外面是多態關係則無所謂,就是多態的一種體現

  • 子類繼承泛型類時,可以選擇保留或不保留泛型;父類有多個泛型時可以選擇保留任意數量

  • 子類可以選擇用更小的範圍取代父類的泛型,例如原本是<T>,繼承時改成<String>把範圍鎖死

通配符

  • <?>相當於所有泛型的父類

    public void test2() {
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        // list=list2; // 報錯
        list1.add("LKJ");
        list1.add("LKJHKJ");
        list2.add("LKJ222");
    
    //        List<?> listFather = null; // <?>相當於<任何人>的父類
    
        print(list1); // 這下都能調用這個方法了
        print(list2);
    }
    
    public void print(List<?> list) {
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
    
  • 無法對<?>進行寫入,因為根本不知道到底是啥類型,但可以= null

  • 可以對<?>進行讀取,讀出來的數據類型為Object

有限制條件的通配符

  • <? extends 類名>:表示通配的範圍小於等於該類
  • <? super 類名>:表示通配的範圍大於等於該類
  • 舉例:有爺、父、子三個類為繼承關係
    • <? extends 父>表示應用範圍只有父、子,操作數據時跟多態一樣考慮就可以
    • <? super 父>表示應用範圍只有Object、爺、父,操作數據時跟多態一樣考慮就可以

泛型練習

寫一個DAO<>類、在自訂一個User類作為泛型,試著調用

public class DAO<T> {
    private Map<String, T> map = new HashMap<>(); // 注意這個map實例化

    // 保存T類型的物件到Map成員變量中
    public void save(String id, T entity) {
        map.put(id, entity);
    }

    // 從map獲取id對應物件
    public T get(String id) {
        T t = map.get(id);
        return t;
    }

    // 以entity替換map中key為id的內容
    public boolean update(String id, T entity) {
        if (map.containsKey(id)) {
            map.put(id, entity);
            return true;
        }
        return false;
    }

    // 返回map所有存放的T物件
    public List<T> list() {
        // return (List<T>) map.values(); // 這是錯誤的,不行把父Collection轉子List
        // 正確
//        List<T> list = new ArrayList<>();
//        Collection<T> values = map.values();
//        list.addAll(values);
//        return list;

        // 終極簡化
        Collection<T> values = map.values();
        return new ArrayList<>(values);
    }

    // 刪除指定id物件
    public void delete(String id) {
        map.remove(id);
    }

}
public class User {
    private int id;
    private int age;
    private String name;
    // 各種重寫的方法...
// 調用
public class DAOTest {
    public static void main(String[] args) {
        DAO<User> userDAO = new DAO<User>(); // 如果上面寫DAO時沒實例化map,這邊會報錯
        userDAO.save("001", (new User(1, 30, "張3")));
        userDAO.save("002", (new User(2, 20, "張4")));
        userDAO.save("003", (new User(3, 5, "張5")));
// 注意存在map的String id跟User.id是不同的
        userDAO.update("003", new User(4, 50, "dfg"));
        List<User> list = userDAO.list();
        list.forEach(System.out::println); // 注意forEach用法

    }
}

小結

  • 泛型<>中沒有父子類關係
  • map轉list,注意不要直接用map.value得到Collection數組就想轉成子類的List,要嘛用遍歷或addAll將數組裝入list才行

上次修改於 2021-12-10