Java在JDK 5中引入了泛型,使用起來方便了很多,下面是一段很常見的泛型使用:
List<String> list = new ArrayList<String>();
泛型方法使用
不僅集合中可以使用,在定義類、接口和方法的時候也是經常使用的,但是關於泛型方法使用的場景還是不太多。下面從求兩個數的最大數的實現上來看一下泛型類和泛型方法的簡單使用:
泛型類(接口)
package me.codeboy.test;
/**
* generic test
* Created by yuedong on 9/12/16.
*/
public class MathTest<T extends Comparable> {
public static void main(String[] args) {
MathTest<Integer> mathTest1 = new MathTest<Integer>();
MathTest<Double> mathTest2 = new MathTest<Double>();
System.out.println(mathTest1.max(1, 2));
System.out.println(mathTest2.max(2.0, 3.0));
}
private T max(T t1, T t2) {
return t1.compareTo(t2) > 0 ? t1 : t2;
}
}
泛型方法
/**
* generic test
* Created by yuedong on 9/12/16.
*/
public class MathTest {
public static void main(String[] args) {
MathTest mathTest = new MathTest();
System.out.println(mathTest.max(1, 2));
System.out.println(mathTest.max(2.0, 3.0));
}
private <T extends Comparable> T max(T t1, T t2) {
return t1.compareTo(t2) > 0 ? t1 : t2;
}
}
靜態泛型方法
/**
* generic test
* Created by yuedong on 9/12/16.
*/
public class MathTest {
public static void main(String[] args) {
System.out.println(max(1, 2));
System.out.println(max(2.0, 3.0));
}
private static <T extends Comparable> T max(T t1, T t2) {
return t1.compareTo(t2) > 0 ? t1 : t2;
}
}
泛型方法優缺點
優點很明顯,代碼簡潔多了,或者可以說比普通的泛型泛型更為簡潔,網上有一段關於Android中頻繁使用 findViewById 方法的靜態泛型方法實現,被稱為見過最牛逼的Android代碼,但是事物都有兩面性,靜態泛型方法也有相應的缺點,再看一段代碼:
import java.util.ArrayList;
import java.util.List;
/**
* test entry
* Created by yuedong on 9/10/16.
*/
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("test");
//普通轉換
ArrayList<String> result1 = (ArrayList<String>) list;
//靜態泛型轉換
String result2 = convert(list);
}
private static <T> T convert(Object a) {
return (T) a;
}
}
上述代碼是 編譯通過,運行異常,為什麼會出現這種現象呢?這是因為Java的泛型方法屬於偽泛型,在編譯的時候將進行類型擦除。普通的泛型方法在類構建時已經明確制定了對應的類型,而在靜態泛型方法中,類型是無法直接推測的,缺少了明確的類型,最終造成類型轉化異常。
Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.String
原理探索
看到了上面的結果,不禁想了解下泛型方法在類型擦除後最終轉換成了什麼,反編譯上述靜態泛型方法編譯後的class文件如下:
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String test
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: checkcast #2 // class java/util/ArrayList
21: astore_2
22: aload_1
23: invokestatic #6 // Method convert:(Ljava/lang/Object;)Ljava/lang/Object;
26: checkcast #7 // class java/lang/String
29: astore_3
30: return
}
可以看到convert函數最終轉化後對應的字節碼為 Method convert:(Ljava/lang/Object;)Ljava/lang/Object; 參數為Object類型,返回也為Object類型,而在接下來的 checkcast 操作中,由於 List 和 String 類型的不同,所以拋出了異常。
private static <T extends List> T convert(Object a) {
return (T) a;
}
對於上述的代碼反編譯後對應 Method convert:(Ljava/lang/Object;)Ljava/util/List; 中,可以看到此時參數為Object類型,返回為List類型。
小結
盡管Java中的泛型是偽泛型,但是泛型可以使代碼更加的簡潔,只是在使用 普通泛型方法 和 靜態泛型方法 時需要特別注意類型轉化。