java泛型(generics)為jdk5引入的新特性,泛型提供了編譯時類型安全檢測機制,可以在編譯時檢測到非法的類型。
泛型的本質是參數化類型,也就是說所操作的資料類型被指定為一個參數。
使用泛型的好處
它的主要目标是保障java的類型安全,簡化程式設計,泛型可以使編譯器知道一個對象限定類型是什麼,所有的強制轉換都為自動和隐式的。
舉個簡單的栗子
public class test1 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("字元串");
list.add(1);
for (Object a : list) {
System.out.println("toString轉換->" + a.toString());
System.out.println("強轉->" + (String)a);
}
}
}
執行結果
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
toString轉換字元串
強轉字元串
toString轉換1
at generics.test1.main(test1.java:13)
雖然編譯的時候沒有報錯,但是運作的時候強轉為String的時候出現類型轉換的錯,可以看出來這種寫法是不安全的。
進一步做點改良(使用泛型後,編譯器報錯,這樣可以預防一些後續編譯通過但運作報錯的情況)
泛型的一個重要特性:跟反射不同的是,泛型檢查隻在編譯期間有效,而反射則是在運作時有效。
下面做個簡單的測試
public class test2 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//測試泛型
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
Class class1 = list1.getClass();
Class class2 = list2.getClass();
if(class1.equals(class2)) {
System.out.println("類型相同");
}
//測試反射
Map<String, String> map = new HashMap<String, String>();
String key = "key";
Integer val = 1;
//通過反射擷取方法
Method m = HashMap.class.getDeclaredMethod("put", new Class[] { Object.class, Object.class });
//invoke意在将方法參數化 動态調用Method類代表的方法 并傳入參數
m.invoke(map, key, val);
System.out.println(map);
System.out.println(map.get(key)); //擷取key為key的值
}
}
image.png
泛型的使用方式
1.泛型類
2.泛型接口
3.泛型方法
泛型類
//T,E,K,V等均表示為泛型
public class Generic<T> {
private T key;//成員變量類型為T,由外部指定
public Generic(T key) {
this.key = key;
}
public T getKey(){ //泛型方法getKey的傳回值類型為T,T的類型由外部指定
return key;
}
public static void main(String[] args) {
//在執行個體化泛型類時 需指定T的具體類型(這邊5就代表integer)
Generic<Integer> s1 = new Generic<Integer>(5);
Generic<String> s2 = new Generic<String>("5");
System.out.println(s1.getClass());//class generics.Generic
System.out.println(s2.getClass());//class generics.Generic
System.out.println(s1.getKey().getClass()); //class java.lang.Integer
System.out.println(s2.getKey().getClass()); //class java.lang.String
/**
* 定義泛型類不一定要傳入泛型類型實參,如果傳入的化會在編譯時做限制,不傳的化可以為任一類型,
* 但處于安全考慮一般都定義否則一些場景容易出現ClassCastException錯誤
*/
Generic t1 = new Generic(5);
Generic t2 = new Generic(0.5);
System.out.println(t1.getKey()); //5
System.out.println(t2.getKey()); //0.5
System.out.println(t1.getKey().getClass()); //class java.lang.Integer
System.out.println(t2.getKey().getClass()); //class java.lang.Double
}
}
}
泛型接口
泛型接口與類定義和使用大緻相同,例如Map接口的一小段代碼
public interface Map<K,V> {
...
V put(K key, V value);
void putAll(Map<? extends K, ? extends V> m);//指明泛型的上邊界
...
}
實作泛型接口,假設實作map
//假如一個類實作了泛型接口,需要将泛型聲明(test3<K,V>) 一起加到類中 否則編譯報錯
public class test3<K,V> implements Map<K,V>{
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
...
}
泛型方法
泛型類,是在執行個體化類的時候指明【泛型】的具體類型;泛型方法,是在調用方法的時候指明泛型的具體類型 。
這樣說有點繞口 舉兩個栗子
/**
泛型類
**/
//定義泛型類 該類
public class Test<T> {
public T key;
public test5(T key) {
this.key = key;
};
}
//通過執行個體化指明【泛型】T的類型為String
Test<String> t = new Test<Sring>("123");
/**
泛型方法
定義泛型方法時 必須在傳回值前面加一個 <T> 來聲明這是一個泛型方法
**/
public class test5<T> {
//在這裡<T>表示傳回的類型是T的 該方法的作用是建立任一指定類型并傳回
public <T> T getObject(Class<T> c) throws IllegalAccessException, InstantiationException {
T t = c.newInstance();//建立泛型對象
return t;
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
test5 t = new test5();
//調用泛型方法【在調用方法的時指明泛型具體類型 這邊用User型】
//在這object就是User的執行個體 這邊測試别用類似java.lang.xxx這樣BootStrap類加載器加載的類 否則輸出為空
Object object = t.getObject(Class.forName("generics.User"));
System.out.println(object.getClass()); //輸出class generics.User
}
}
泛型的上下邊界
使用泛型的時,可以通過傳入泛型類型實參進行上下邊界的限制。
如定義一個泛型類
public class Generic<T extends Number> { //此處為上邊界 意為給定T類型需為Number子類 T super XX 為下邊界
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
如果這樣執行個體化Generic類編譯器會報錯,因為String不是Number的子類。
Generic<String> generic = new Generic<String>("11111");
一些思考
1.下面這個getKey是否為泛型方法
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
并不是,雖然在方法中使用了泛型,這個是類中普通的成員方法,因為它的傳回值是聲明泛型類【Generic】時已經聲明過的泛型【T】,是以在方法中可以繼續使用T這個泛型。
2.經常在使用泛型的代碼中看見通配符 【?】它的作用是什麼,和T的差別在哪呢
比如Map源碼
void putAll(Map<? extends K, ? extends V> m);
在這裡?是通配符,泛指所有類型,常用于不确定類型的情況
? extends T 指T類型或T的子類型
? super T 指T類型或T的父類型
它們的差別在于
"T"是定義類或方法時聲明的東西,"?"是調用時傳入的東西
①T常用于聲明一個泛型類或者泛型方法
②?常用于使用泛型類或泛型方法
聲明泛型類時不能用無界通配符<?>
//error example
class demo<?> {
private ? item;
}
通配符是可以用來使用定義好的泛型的 但是T不能用來繼續使用已經定義好的泛型
當我們在外面使用一個帶泛型T的類或方法時,T應該用一個實際的資料類型替代它,也可以使用?通配符
public class User {
HashMap<T,String> map = new HashMap<String, String>(); //error example
HashMap<?,String> map = new HashMap<String, String>(); //right example
}