參考鏈接: Java中具有泛型的有界類型
1、什么是泛型?
?
泛型,即“參數化類型”。一提到參數,最熟悉的就是定義方法時有形參,然后調用此方法時傳遞實參。那么參數化類型怎么理解呢?顧名思義,就是將類型由原來的具體的類型參數化,類似于方法中的變量參數,此時類型也定義成參數形式(可以稱之為類型形參),然后在使用/調用時傳入具體的類型(類型實參)。?
2、一個小例子?
?
import java.util.ArrayList;
import java.util.List;
?
public static void main(String[] args) {
?
? ? ? ? List list = new ArrayList();
? ? ? ? list.add("hello");
? ? ? ? list.add("hallo");
? ? ? ? list.add(1);
? ? ? ? for (int i = 0; i < list.size(); i++) {
? ? ? ? ? ? String str = (String) list.get(i);
? ? ? ? ? ? System.out.println(str);
? ? ? ? }
? ? }
}?
上面的程序:? 編譯階段正常,而運行時會出現異常?
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String?
存在的問題:? 當把一個對象收入集合中,集合不會記住此對象的類型,當再次從集合中取出此對象時,對象變成了Object類型,但其運行時類型任然為其本身類型。強制轉換時,容易出現異常,正如程序中把Integer轉為String失敗。?
解決方法:? 通過使用泛型來使集合能夠記住集合內元素各類型?
將上面的代碼用泛型來實現?
import java.util.ArrayList;
import java.util.List;
?
public class Main {
? ? public static void main(String[] args) {
? ? ? ? List<String> list = new ArrayList<String>();
? ? ? ? list.add("hello");
? ? ? ? list.add("hallo");
? ? ? ? list.add(1);
? ? ? ? for (int i = 0; i < list.size(); i++) {
? ? ? ? ? ? String str = list.get(i);
? ? ? ? ? ? System.out.println(str);
? ? ? ? }
? ? }
}?
這時,編譯錯誤,無法運行?
Error:(13, 13) java: 對于add(int), 找不到合適的方法
? ? 方法 java.util.Collection.add(java.lang.String)不適用
? ? ? (參數不匹配; int無法轉換為java.lang.String)
? ? 方法 java.util.List.add(java.lang.String)不適用
? ? ? (參數不匹配; int無法轉換為java.lang.String)?
通過List,直接限定了list集合中只能含有String類型的元素,從而在取得集合中的元素時無須進行強制類型轉換,編譯器可以確定集合中存的都是String類型。?
3、泛型的特性?
?
Java中的泛型,只在編譯階段有效。?
在編譯過程中,正確檢驗泛型結果后,會將泛型的相關信息擦出,并且在對象進入和離開方法的邊界處添加類型檢查和類型轉換的方法。?
泛型信息不會進入到運行時階段。?
4、泛型的使用?
?
在泛型使用過程中,操作的數據類型被指定為一個參數,這種參數類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法。?
以幾個小例子來分析一下?
(1) 泛型類?
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable?
用泛型類聲明與建立對象?
ArrayList<String> list = new ArrayList<String>();?
JDK7之后可以寫成:?
ArrayList<String> list = new ArrayList<>();?
(2) 泛型接口?
...
public interface Comparator<T>{
? ? int compare(T o1, T o2);
? ? ...
}?
使用方法?
public class StringComparator implements Comparator<String>{
? ? @Override
? ? public int compare(String s1, String s2){
? ? ? ? return -s1.compareTo(s2);
? ? }
}?
(3) 泛型方法?
public static <T> T elemof(T[] objs,int index){
? ? return objs[index];
}?
使用方法?
public static String elemof(String[] objs,int index){
? ? return objs[index];
}?
5、泛型的上下邊界問題?
?
可以使用extends與super關鍵字、?類型通配符來限制泛型,并且可以結合這三者來模擬共變性與逆變性。?
共變性與逆變性?
如果B是A的子類,而Node< B >可視為一種Node< A >,則稱Node具有共變性(Covariance)或者有彈性的(Flexible)。?
如果B是A的子類,而Node< A >可視為一種Node< B >,則稱Node具有逆變性(Contravariance)。?
Java泛型不支持共變性與逆變性?
(1) 使用?與extends來模擬共變性?
class Fruit{}
class Apple extends Fruit{}
class RedApple extends Apple{}
?
List< ? extends Fruit > list1 = new ArrayList<Fruit>();
List< ? extends Fruit > list2 = new ArrayList<Apple>();
List< ? extends Fruit > list3 = new ArrayList<RedApple>();?
Fruit為上界,即List< ? extends Fruit >類型的對象可以引用持有Fruit及其子類的容器。?
關于一個add方法:?
void add(List< ? extends Fruit > list, Apple a){
? ? ?list.add(a);
}?
如果list引用的是持有RedApple類型的容器,那么將Apple類型向下轉型是不安全的,所以用List< ? extends Fruit >,add方法是受到限制的,即無法向容器中添加任何實際的類型除了null。?
(2) 使用?與super來模擬逆變性?
List< ? super RedApple > list4 = new ArrayList<RedApple>();
List< ? super RedApple > list5 = new ArrayList<Apple>();?
RedApple為下界,即List< ? super RedApple >類型的對象可以引用持有RedApple及其父類的容器。?
此時add方法是沒有限制的,但是也只能是添加Jonathan及其父類型。若事先不知道List< ? super RedApple >引用的對象所持有的類型,則可以做到向上轉型,而向上轉型是安全的。?
6、小結?
?
泛型的本質是:參數化類型?
只在編譯階段有效,泛型使用無錯誤時,擦出泛型的相關信息,并且在對象進入和離開方法的邊界處添加類型檢查和類型轉換的方法,泛型信息不會進入到運行時階段。?
泛型可以被用在類、接口和方法中,而且可以限制泛型的上下界。?
雖然Java泛型不支持共變性與逆變性,但是?與extends可模擬共變性;?與super可模擬逆變性。?
?
參考資料?
《Java學習筆記》 林信良?
Java泛型的邊界問題?
java 泛型詳解-絕對是對泛型方法講解最詳細的,沒有之一