泛型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