什麼是泛型?
- 泛型 , 即 “參數化類型” . 提到參數 , 最熟悉的就是定義方法時有形參 , 然後調用方法傳遞實參 . 那麼參數化類型如何理解? 就是將類型由原來具體的類型參數化 , 類似於方法中的變量參數 , 此時類型也定義成參數形式 , 然後在調用的時候傳入具體的類型 .
下面採用泛型後, 當通過List<’String> , 就相當於給List傳遞了參數類型是String, 限定了集合中只能含有String類型元素 , 集合保存了這個類型, 當add(“123”) , 無需進行類型轉換 , 因爲編譯器明確可以傳遞String類型 . 當然如果傳入Integer類型是不會通過編譯的 .
- java中一種類型 , 被參數化的類型 , 也就是可以通過參數傳遞類型 . 帶參數的類型就是泛型 , 被參數化的類型 .
泛型擦除:
java泛型概念的提出 , 導致泛型只是作用於代碼的編譯階段 , 在編譯過程中 , 對於正確的檢驗泛型結果後 , 會將泛型的相關信息擦除 , 也就是說 , 成功編譯過後的class文件是不包含任何的泛型信息的 . 泛型信息不會進入運行時階段 .
泛型其實只是在編譯器實現的,而虛擬機並不認識泛型類型,所以要在虛擬機中將泛型類型進行擦除 , 也就是說編譯階段使用泛型 , 運行階段取消泛型 ,
擦除是將泛型類型由父類代替的 , 如String變成Object , 其實使用的時候還是帶着強制的類型轉換 , 只不過這是比較安全的轉換 , 因爲在編譯階段已經確保數據的一致性 .
驗證泛型只在編譯器有效:
所有反射操作是在運行期, 既然是true, 說明編譯之後, 程序會採用去泛型化措施 ,
編譯過程中, 正確檢驗泛型結果後, 會將泛型的相關信息擦除, 並且在對象進入和離開方法的邊界處添加類型檢查和類型轉換方法, 也就是說成功編譯後的class方法是不包含任何泛型信息的
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
List commonList = new ArrayList();
// 說明運行期字節碼文件是相同的
System.out.println(stringList.getClass() == commonList.getClass()); // true
}
利用反射跳過泛型檢查:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("zhangsan");
// 利用泛型進行添加
try{
Class clazz = list.getClass();
Method method = clazz.getMethod("add", Object.class);
// 添加Integer類型數據, 因爲反射是在運行期, 說明泛型的檢驗是在編譯期
method.invoke(list, 100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(list);
}
泛型通配符:
對於任意(引用)類型 T,ArrayList<’?> 類型是ArrayList<’T> 的超類型(類似原始類型 ArrayList 和根類型 Object)。
但是ArrayList<’Object>並不是ArrayList<’String>的超類型.
泛型上下限:
T super Number,那麼T就是Number父類級別的類型,
T extends Number 的話,Number是T的父級別類型.
應用:
- 獲取下面test方法的形參中泛型的參數類型.
public class Test {
/**
* 三個參數, 只有兩個是泛型類型參數
* @param map
* @param list
* @param str
*/
public void test(Map<String,Integer> map, List<String> list, String str) {
System.out.println("hello world");
}
public static void main(String[] args) {
try {
Method method = Test.class.getMethod
("test", Map.class, List.class, String.class);
// 獲取方法的形參
Type[] paramterTypes = method.getGenericParameterTypes();
// 遍歷所有類型, 判斷是否是泛型的類型
for(Type type : paramterTypes) {
if(type instanceof ParameterizedType) {
Type[] ts = ((ParameterizedType)type).getActualTypeArguments();
for(Type t : ts) {
// 打印結果:String Integer String
System.out.println(((Class)t).getSimpleName());
}
}
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
- 比如在三大框架整合中, 我們需要將所有增刪改查操作方法放在父類中, UserService操作的實體類型是User, DepartService操作類型是Depart . 那麼如何讓父類BaseService獲得到子類操作的實體類型(Hibernate中增刪改查需要獲取實體類型名稱) ?
父類:
public class BaseService<T> {
private Class<T> clazz;
@SuppressWarnings("unchecked")
public BaseService() {
// 獲取子類的class對象
Class c = (Class<T>) this.getClass();
// 獲取子類的extend類型
Type type = c.getGenericSuperclass();
// 判斷是否是泛型
if(type instanceof ParameterizedType) {
// 獲取泛型的參數類型(默認獲取第一個)
Type t = ((ParameterizedType)type).getActualTypeArguments()[0];
clazz = (Class<T>) t;
System.out.println(clazz.getSimpleName());
}
}
}
UserService:
public class UserService extends BaseService<User> {
public static void main(String[] args) {
new UserService(); // User
}
}
DepartService:
public class DepartService extends BaseService<Depart> {
public static void main(String[] args) {
new DepartService(); // Depart
}
}
泛型好處 :
(1) 類型安全
通過泛型定義的變量類型限制, 編譯器可以很有效的提高java程序的類型安全 .
(2) 消除強制類型轉換
消除強制類型轉換, 使得代碼更加可讀, 並減少出錯的機會, 所有的強制轉換都是自動