泛型的主要好處就是讓編譯器保留參數的類型信息,執行類型檢查,執行類型轉換(casting)操作,編譯器保證了這些類型轉換(casting)的絕對無誤。
/******* 不使用泛型類型 *******/
List list1 = new ArrayList();
list1.add(8080); //編譯器不檢查值
String str1 = (String)list1.get(0); //需手動強制轉換,如轉換類型與原數據類型不一致將拋出ClassCastException異常
/******* 使用泛型類型 *******/
List<String> list2 = new ArrayList<String>();
list2.add("value"); //[類型安全的寫入數據] 編譯器檢查該值,該值必須是String類型才能通過編譯
String str2 = list2.get(0); //[類型安全的讀取數據] 不需要手動轉換
泛型的類型擦除:
Java 中的泛型只存在于編譯期,在將 Java 源文件編譯完成 Java 字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。
這個過程就稱為類型擦除(type erasure)。
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
System.out.println(list1.getClass() == list2.getClass()); // 輸出結果: true
System.out.println(list1.getClass().getName()); // 輸出結果: java.util.ArrayList
System.out.println(list2.getClass().getName()); // 輸出結果: java.util.ArrayList
在以上代碼中定義的 List<String> 和 List<Integer> 等類型,在編譯之后都會變成 List,而由泛型附加的類型信息對 JVM 來說是不可見的,所以第一條打印語句輸出 true,
第二、第三條打印語句都輸出 java.util.ArrayList,這都說明 List<String> 和 List<Integer> 的對象使用的都是同一份字節碼,運行期間并不存在泛型。
來看一個簡單的例子:
package test;
import java.util.List;
/**
* -----------------------------------------
* @描述 類型擦除
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class GenericsApp {
public void method(List<String> list){
}
/*
* 編譯出錯,這兩個方法不屬于重載,由于類型的擦除,使得這兩個方法的參數列表的參數均為List類型,
* 這就相當于同一個方法被聲明了兩次,編譯自然無法通過了
*
public void method(List<Integer> list){
}
*/
}
以此類為例,在 cmd 中 編譯 GenericsApp.java 得到字節碼,然后再反編譯這份字節碼:

從圖中可以看出,經反編譯后的源碼中 method 方法的參數變成了 List 類型,說明泛型的類型被擦除了,字節碼文件中不存在泛型,也就是說,運行期間泛型并不存在,它在
編譯完成之后就已經被擦除了。
泛型類型的子類型:
泛型類型跟其是否是泛型類型的子類型沒有任何關系。
List<Object> list1;
List<String> list2;
list1 = list2; // 編譯出錯
list2 = list1; // 編譯出錯
在 Java 中,Object 類是所有類的超類,自然而然的 Object 類是 String 類的超類,按理,將一個 String 類型的對象賦值給一個 Object 類型的對象是可行的,
但是泛型中并不存在這樣的邏輯,泛型類型跟其是否子類型沒有任何關系。
泛型中的通配符(?):
由于泛型類型與其子類型存在不相關性,那么在不能確定泛型類型的時候,可以使用通配符(?),通配符(?)能匹配任意類型。
List<?> list;
List<Object> list1 = null;
List<String> list2 = null;
list = list1;
list = list2;
限定通配符的上界:
ArrayList<? extends Number> collection = null;
collection = new ArrayList<Number>();
collection = new ArrayList<Short>();
collection = new ArrayList<Integer>();
collection = new ArrayList<Long>();
collection = new ArrayList<Float>();
collection = new ArrayList<Double>();
? extends XX,XX 類是用來限定通配符的上界,XX 類是能匹配的最頂層的類,它只能匹配 XX 類以及 XX 類的子類。在以上代碼中,Number 類的實現類有:
AtomicInteger、AtomicLong、 BigDecimal、 BigInteger、 Byte、 Double、 Float、 Integer、 Long、 Short ,因此以上代碼均無錯誤。
限定通配符的下界:
ArrayList<? super Integer> collection = null;
collection = new ArrayList<Object>();
collection = new ArrayList<Number>();
collection = new ArrayList<Integer>();
? super XX,XX 類是用來限定通配符的下界,XX 類是能匹配的最底層的類,它只能匹配 XX 類以及 XX 類的超類,在以上代碼中,Integer 類的超類有:
Number、Object,因此以上代碼均能通過編譯無誤。
通過反射獲得泛型的實際類型參數:
java.lang.Class 類從 Java 1.5 起(如果沒記錯的話),提供了一個 getGenericSuperclass() 方法來獲取直接超類的泛型類型
package test;
import java.lang.reflect.ParameterizedType;
/**
* -----------------------------------------
* @描述 泛型的實際類型參數
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class Base<T> {
private Class<T> entityClass;
//代碼塊,也可將其放置到構造子中
{
entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
//泛型的實際類型參數的類全名
public String getEntityName(){
return entityClass.getName();
}
//泛型的實際類型參數的類名
public String getEntitySimpleName(){
return entityClass.getSimpleName();
}
//泛型的實際類型參數的Class
public Class<T> getEntityClass() {
return entityClass;
}
}
(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 相當于:
//代碼塊,也可將其放置到構造子中
{
//entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
try {
Class<?> clazz = getClass(); //獲取實際運行的類的 Class
Type type = clazz.getGenericSuperclass(); //獲取實際運行的類的直接超類的泛型類型
if(type instanceof ParameterizedType){ //如果該泛型類型是參數化類型
Type[] parameterizedType = ((ParameterizedType)type).getActualTypeArguments();//獲取泛型類型的實際類型參數集
entityClass = (Class<T>) parameterizedType[0]; //取出第一個(下標為0)參數的值
}
} catch (Exception e) {
e.printStackTrace();
}
}
注意,獲取 Class 實例的時候是用 getClass(),而不是用 Base.class,獲取 Class 的方式有三種,這是其中的兩種,還有一種是 Class.forName("類全名"),如需了解反射的基礎知識
請前往上一篇隨筆 java 反射基礎
那么,Base.class 與 getClass(),這兩個方法來獲取類的字節碼的時候,Base.class 是寫死了的,它得到的永遠是 Base 類的字節碼,
而 getClass() 方法則不同,在上面代碼注釋中的第一、二行注釋我用了“實際運行的類”6個字,這幾個字很重要,是一定要理解的。
為了方便大家的理解,下面插加一個小例子來加以說明 類.class 與 getClass() 兩種方法來獲取類的字節碼有什么區別:
package test;
/**
* -----------------------------------------
* @描述 超類
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class Father {
public Father (){
System.out.println("Father 類的構造子:");
System.out.println("Father.class :" + Father.class);
System.out.println("getClass() :" + getClass());
}
}
package test;
/**
* -----------------------------------------
* @描述 超類的子類(超類的實現類)
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class Children extends Father{
}
package test;
/**
* -----------------------------------------
* @描述 測試類
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
new Children(); //實際運行的類是Children(Father類的子類或者說是實現類)
}
}
后臺打印輸出的結果:
Father 類的構造子:
Father.class :class test.Father
getClass() :class test.Children
從打印出的結果看來,類.class 與 getClass() 的區別很明了了,getClass() 獲取的是實際運行的類的字節碼,它不一定是當前類的 Class,有可能是當前類的子類的 Class,具體是哪
個類的 Class,需要根據實際運行的類來確定,new 哪個類,getClass() 獲取的就是哪個類的 Class,而 類.class 獲取得到的 Class 永遠只能是該類的 Class,這點是有很大的區別的。
如果 getClass() 理解了,那 clazz.getGenericSuperclass() 也應該能夠理解了的,千萬不要以為 clazz.getGenericSuperclass() 獲取得到的是 Object 類那就成了,
實際上假如當前運行的類是 Base 類的子類,那么 clazz.getGenericSuperclass() 獲取得到的就是 Base 類。
(Class<T>) parameterizedType[0],怎么就知道第一個參數(parameterizedType[0])就是該泛型的實際類型呢?很簡單,因為 Base<T> 的泛型的類型
參數列表中只有一個參數,所以,第一個元素就是泛型 T 的實際參數類型。
package test;
/**
* -----------------------------------------
* @描述 測試類
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
Base<String> base = new Base<String>();
System.out.println(base.getEntityClass()); //打印輸出 null
// System.out.println(base.getEntityName()); //拋出 NullPointerException 異常
// System.out.println(base.getEntitySimpleName()); //拋出 NullPointerException 異常
}
}
從打印的結果來看,Base 類并不能直接來使用,為什么會這樣?原因很簡單,由于 Base 類中的 clazz.getGenericSuperclass() 方法,如果隨隨便便的就確定 Base 類的泛型的類型
參數,則很可能無法通過 Base 類中的 if 判斷,導致 entityClass 的值為 null,像這里的 Base<String>,String 的 超類是 Object,而 Object 并不能通過 if 的判斷語句。
Base 類不能夠直接來使用,而是應該通過其子類來使用,Base 應該用來作為一個基類,我們要用的是它的具體的子類,下面來看下代碼,它的子類也不是隨便寫的:
package test;
/**
* -----------------------------------------
* @描述 Base類的實現類
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class Child extends Base<Child>{
}
從上面代碼來看,Base 的泛型類型參數就是 Base 的子類本身,這樣一來,當使用 Base 類的子類 Child 類時,Base 類就能準確的獲取到當前實際運行的類的 Class,來看下怎么使用
package test;
/**
* -----------------------------------------
* @描述 測試類
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-25 <p>
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
Child child = new Child();
System.out.println(child.getEntityClass());
System.out.println(child.getEntityName());
System.out.println(child.getEntitySimpleName());
}
}
后臺打印輸出的結果:
class test.Child
test.Child
Child
好了,文章接近尾聲了,如果你能理解透這個例子,你可以將這個思想運用到 DAO 層面上來,以 Base 類作為所有 DAO 實現類的基類,在 Base 類里面實現數據庫的 CURD 等基本
操作,然后再使所有具體的 DAO 類來實現這個基類,那么,實現這個基類的所有的具體的 DAO 都不必再實現數據庫的 CURD 等基本操作了,這無疑是一個很棒的做法。
(通過反射獲得泛型的實際類型參數)補充:
泛型反射的關鍵是獲取 ParameterizedType 接口,再調用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可獲得實際綁定的類型。
由于去參數化(擦拭法),也只有在 超類(調用 getGenericSuperclass 方法) 或者成員變量(調用 getGenericType 方法)或者方法(調用 getGenericParameterTypes 方法)
像這些有方法返回 ParameterizedType 類型的時候才能反射成功。
上面只談到超類如何反射,下面將變量和方法的兩種反射補上:
通過方法,反射獲得泛型的實際類型參數:
package test;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
/**
* -----------------------------------------
* @描述 測試類
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-26 <p>
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
/**
* 泛型編譯后會去參數化(擦拭法),因此無法直接用反射獲取泛型的參數類型
* 可以把泛型用做一個方法的參數類型,方法可以保留參數的相關信息,這樣就可以用反射先獲取方法的信息
* 然后再進一步獲取泛型參數的相關信息,這樣就得到了泛型的實際參數類型
*/
try {
Class<?> clazz = Test.class; //取得 Class
Method method = clazz.getDeclaredMethod("applyCollection", Collection.class); //取得方法
Type[] type = method.getGenericParameterTypes(); //取得泛型類型參數集
ParameterizedType ptype = (ParameterizedType)type[0];//將其轉成參數化類型,因為在方法中泛型是參數,且Number是第一個類型參數
type = ptype.getActualTypeArguments(); //取得參數的實際類型
System.out.println(type[0]); //取出第一個元素
} catch (Exception e) {
e.printStackTrace();
}
}
//聲明一個空的方法,并將泛型用做為方法的參數類型
public void applyCollection(Collection<Number> collection){
}
}
后臺打印輸出的結果:
class java.lang.Number
通過字段變量,反射獲得泛型的實際類型參數:
package test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
/**
* -----------------------------------------
* @描述 測試類
* @作者 fancy
* @郵箱 fancydeepin@yeah.net
* @日期 2012-8-26 <p>
* -----------------------------------------
*/
public class Test {
private Map<String, Number> collection;
public static void main(String[] args){
try {
Class<?> clazz = Test.class; //取得 Class
Field field = clazz.getDeclaredField("collection"); //取得字段變量
Type type = field.getGenericType(); //取得泛型的類型
ParameterizedType ptype = (ParameterizedType)type; //轉成參數化類型
System.out.println(ptype.getActualTypeArguments()[0]); //取出第一個參數的實際類型
System.out.println(ptype.getActualTypeArguments()[1]); //取出第二個參數的實際類型
} catch (Exception e) {
e.printStackTrace();
}
}
}
后臺打印輸出的結果:
class java.lang.String
class java.lang.Number