Java泛型总结---基本用法,类型限定,通配符,类型擦除
一、基本概念和用法
在Java語言處于還沒有出現(xiàn)泛型的版本時,只能通過Object是所有類型的父類和類型強(qiáng)制轉(zhuǎn)換兩個特點的配合來實現(xiàn)類型泛化。例如在哈希表的存取中,JDK1.5之前使用HashMap的get()方法,返回值就是一個Object對象,由于Java語言里面所有的類型都繼承于java.lang.Object,那Object轉(zhuǎn)型為任何對象成都是有可能的。但是也因為有無限的可能性,就只有程序員和運行期的虛擬機(jī)才知道這個Object到底是個什么類型的對象。在編譯期間,編譯器無法檢查這個Object的強(qiáng)制轉(zhuǎn)型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多ClassCastException的風(fēng)險就會被轉(zhuǎn)嫁到程序運行期之中。
泛型是JDK1.5的一項新特性,它的本質(zhì)是將類型參數(shù)化,簡單的說就是將所操作的數(shù)據(jù)類型指定為一個參數(shù),在用到的時候通過傳參來指定具體的類型。在Java中,這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口和泛型方法。
一個泛型類的例子如下:
//將要操作的數(shù)據(jù)類型指定為參數(shù)T public class Box<T> {private T t;public void add(T t) {this.t = t;}public T get() {return this.t;} } //使用的時候指定具體的類型為Integer //那么Box類里面的所有T都相當(dāng)于Integer了 Box<Integer> integerBox = new Box<Integer>();泛型接口和泛型方法的定義和使用示例如下:
//泛型接口 interface Show<T,U> {void show(T t,U u); }class ShowTest implements Show<String,Date> {@Override public void show(String str,Date date) {System.out.println(str);System.out.println(date);} }public static void main(String[] args) { ShowTest showTest = new ShowTest();showTest.show("Hello",new Date()); } //泛型方法 public <T, U> T get(T t, U u) {if (u != null)return t;elsereturn null; }String str = get("Hello", "World");從上面的例子可以看出,用尖括號<>來聲明泛型變量,可以有多個類型變量,例如Show<T, U>,但是類型變量名不能重復(fù),例如Show<T, T>是錯誤的。另外,類型變量名一般使用大寫形式,且比較短(不強(qiáng)制,只是一種命名規(guī)約),下面是一些常用的類型變量:
- E:元素(Element),多用于java集合框架
- K:關(guān)鍵字(Key)
- N:數(shù)字(Number)
- T:類型(Type)
- V:值(Value)
- S:第二類型
- U:第三類型
二、泛型變量的類型限定
類型限定就是使用extends關(guān)鍵字對類型變量加以約束。比如限定泛型參數(shù)只接受Number類或者子類Integer、Float等,可以這樣限定,這樣限定之后,實際參數(shù)只能是Number類或者Number的子類。下面舉例詳細(xì)說明:
//定義一個水果類 //里面有一個示例方法getWeight()可以獲取水果重量 public class Fruit {public int getWeight() {return 10; //這里假設(shè)所有水果重量都是10} } public class Apple extends Fruit {}--------------------------------------------------------------------------------------------//定義泛型類Box,并限定類型參數(shù)為Fruit public class Box<T extends Fruit> {}--------------------------------------------------------------------------------------------//由于Box限定了類型參數(shù),實際類型參數(shù)只能是Fruit或者Fruit的子類 Box<Fruit> integerBox = new Box<Fruit>();//編譯通過 Box<Apple> integerBox = new Box<Apple>();//編譯通過 Box<Integer> integerBox = new Box<Integer>();//編譯器報錯上面代碼用虛線分為三個部分,第一個部分是舉例用的,定義一個水果類Fruit和它的子類Apple;第二部分定義一個泛型類Box,并且限定了泛型參數(shù)為Fruit,限定之后,實際類型只能是Fruit或者Fruit的子類,所以第三部分,實際泛型參數(shù)是Integer就會報錯。
通過限定,箱子Box就只能裝水果了,這是有好處的,舉個例子,比如Box里面有一個getBigFruit()方法可以比較兩個水果大小,然后返回大的水果,代碼如下:
public class Box<T extends Fruit>{public T getBigFruit(T t1, T t2) {// if (!(t1 instanceof Fruit) || !(t2 instanceof Fruit)) {// throw new RuntimeException("T不是水果");// }if (t1.getWeight() > t2.getWeight()) {return t1;}return t2;} }代碼中需要注意兩個地方:一個是注釋的三行,參數(shù)限定之后,沒必要判斷t1和t2的類型了,如果類型不對,在Box實例化的時候就報錯了;另一個是t1.getWeight(),在Box類里面,t1是T類型,T類型限定為Fruit,所以這里可以直接調(diào)用Fruit里面的方法getWeight()(確切的說是可以調(diào)用Fruit里面可以被子類繼承的方法,因為限定之后,實參也可以是Fruit的子類),如果不加限定,那么T就默認(rèn)是Object類型,t1.getWeight()就會報錯因為Object里面沒有這個方法(調(diào)用Object里面的方法是可以的)。這就是是類型限定的兩個好處。
類型也可以使用接口限定,比如,這樣的話,只有實現(xiàn)了MyInterface接口的類才能作為實際類型參數(shù)。下面是類型限定的幾個注意點:
三、通配符
先看三行代碼
Fruit f = new Apple(); Fruit[] farray = new Apple[10]; ArrayList<Fruit> flist = new ArrayList<Apple>();第一行的寫法是很常見的,父類引用指向子類對象,這是java多態(tài)的表現(xiàn)。類似的,第二行父類數(shù)組的引用指向子類數(shù)組對象在java中也是可以的,這稱為數(shù)組的協(xié)變。Java把數(shù)組設(shè)計為協(xié)變的,對此是有爭議的,有人認(rèn)為這是一種缺陷。
雖然Apple[]可以“向上轉(zhuǎn)型”為Fruit[],但數(shù)組元素的實際類型還是Apple,所以只能向數(shù)組中放入Apple或者Apple的子類。在上面的代碼中,向數(shù)組中放入了Fruit對象和Orange對象,對于編譯器來說,這是可以通過編譯的,但是在運行時期,JVM能夠知道數(shù)組的實際類型是Apple[],所以當(dāng)其它對象加入數(shù)組的時候在運行期會拋出異常。
由上可知,協(xié)變的缺陷在于可能的異常發(fā)生在運行期,而編譯期間無法檢查,泛型設(shè)計的目的之一就是避免這種問題,所以泛型是不支持協(xié)變的,也就是說,上面的第三行代碼是編譯不通過的。但是,有時候是需要建立這種“向上轉(zhuǎn)型”的關(guān)系的,比如定義一個方法,打印出任意類型的List中的所有數(shù)據(jù),示例如下:
public void printCollection(List<Object> collection) {for (Object obj : collection) {System.out.println(obj);} }------------------------------------ List<Integer> listInteger =new ArrayList<Integer>(); List<String> listString =new ArrayList<String>();printCollection(listInteger); //編譯錯誤 printCollection(listString); //編譯錯誤因為泛型不支持協(xié)變,即List<Object> collection = new ArrayList<Integer>();無法通過編譯,所以printCollection(listInteger)就會報錯。
這時就需要使用通配符來解決,通配符<?>,用來表示某種特定的類型,但是不知道這個類型到底是什么。例如下面的例子都是合法的:
所以printCollection()方法改成下面這樣即可:
public void printCollection(List<?> collection) {for (Object obj : collection) {System.out.println(obj);} }這就是通配符的簡單用法。需要注意的是,因為不知道 "?" 類型到底是什么,所以List<?> collection中的collection不能調(diào)用帶泛型參數(shù)的方法,但是可以調(diào)用與泛型參數(shù)類型無關(guān)的方法,如下:
collection.add("a"); //錯誤,因為add方法參數(shù)是泛型E collection.size(); //正確,因為無參即與泛型參數(shù)類型E無關(guān) collection.contains("a"); //正確,因為contains參數(shù)是Object類型,與泛型參數(shù)類型E無關(guān)注:collection.add(null);是可以的,除了null其他任何類型都不可以,Object也不行。
通配符的邊界
通配符可以使用extends和super關(guān)鍵字來限制:
- List<? extends Number> 表示不確定參數(shù)類型,但必須是Number類型或者Number子類類型,這是上邊界限定
- List<? super Number> 表示不確定參數(shù)類型,但必須是Number類型或者Number的父類類型,這是下邊界限定
- List<?> 表示未受限的通配符,相當(dāng)于 List<? extends Object>
注意區(qū)分 泛型變量的類型限定 和 通配符的邊界限定:
public class Box{}
List<? extends Number> listInteger =new ArrayList();
泛型變量的類型限定只能使用extends關(guān)鍵字,通配符的邊界限定可以使用extends或super來限定上邊界或下邊界。
四、Java泛型的原理-類型擦除
Java中的泛型是通過類型擦除來實現(xiàn)的偽泛型。類型擦除指的是從泛型類型中清除類型參數(shù)的相關(guān)信息,并且在必要的時候添加類型檢查和類型轉(zhuǎn)換的方法。類型擦除可以簡單的理解為將泛型java代碼轉(zhuǎn)換為普通java代碼,只不過編譯器更直接點,將泛型java代碼直接轉(zhuǎn)換成普通的java字節(jié)碼,看下面的例子:
泛型的Java代碼如下:
class Pair<T> {private T value;public T getValue() {return value;}public void setValue(T value) {this.value = value;} }泛型Java代碼,經(jīng)過編譯器編譯后,會擦除泛型信息,將泛型代碼轉(zhuǎn)換為如下的普通Java代碼:
class Pair {private Object value;public Object getValue() {return value;}public void setValue(Object value) {this.value = value;} }由上面的例子可知,泛型擦除的結(jié)果就是用Object替換T,最終生成一個普通的類。上面的例子替換成Obejct是因為在Pair中,T是一個無限定的類型變量,所以用Object替換。如果T被限定了,比如,那么擦除后就用Number替換泛型類里面的T。多個限定的話,使用第一個邊界的類型變量來作為原始類型。
至此可以知道,類型擦除的過程:
泛型只存在于代碼中,泛型信息在編譯時都會被擦除,所以虛擬機(jī)中沒有泛型,只有普通類和普通方法。
Java泛型的一些注意問題
使用泛型時會有一些問題和限制,大部分是由類型擦除引起的,所以只要記住:泛型擦除后留下的只有原始類型。那么大部分問題都是很容易理解的。比如下面的例子:
public void test(List<String> list){}public void test(List<Integer> list){}兩個方法經(jīng)過泛型擦除后,都只留下原始類型List,所以它們是同一個方法而不是方法的重載,如果這兩個方法在同一個類中同時存在,編譯器是會報錯的。
其他更多問題如下:
這些問題的答案在:java泛型(二)、泛型的內(nèi)部原理:類型擦除以及類型擦除帶來的問題,本文也是學(xué)習(xí)這篇博客和一些其他博客后的一個總結(jié)。
參考文章:
Java泛型-類型擦除
java泛型(一)、泛型的基本介紹和使用
java泛型(二)、泛型的內(nèi)部原理:類型擦除以及類型擦除帶來的問題
Java 泛型總結(jié)(三):通配符的使用
轉(zhuǎn)載于:https://www.cnblogs.com/developerzjy/p/11084197.html
總結(jié)
以上是生活随笔為你收集整理的Java泛型总结---基本用法,类型限定,通配符,类型擦除的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: day2笔记
- 下一篇: Java关于 class类的基础方法