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