日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java泛型总结---基本用法,类型限定,通配符,类型擦除

發(fā)布時(shí)間:2024/4/15 java 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java泛型总结---基本用法,类型限定,通配符,类型擦除 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、基本概念和用法

在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):

  • 不管限定是類還是接口,統(tǒng)一都使用extends關(guān)鍵字
  • 可以使用&符號(hào)給出多個(gè)限定,例如:<U extends Number & MyInterface1 & MyInterface2>
  • 多個(gè)限制只能有一個(gè)類名,其他都是接口名,且類名在最前面。
  • 三、通配符

    先看三行代碼

    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è)類型到底是什么。例如下面的例子都是合法的:

    List<?> collection1 = new ArrayList<Fruit>(); List<?> collection2 = new ArrayList<Number>(); List<?> collection3 = new ArrayList<String>(); List<?> collection4 = new ArrayList<任意類型>(); // 對(duì)比不合法的 List<Fruit> flist = new ArrayList<Apple>();

    所以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ū)分 泛型變量的類型限定通配符的邊界限定

  • 泛型變量的類型限定,是在定義泛型類的時(shí)候?qū)β暶鞯姆盒蛥?shù)進(jìn)行限定(限定的是形式參數(shù))
    public class Box{}
  • 通配符的邊界限定,是在定義化泛型類的引用的時(shí)候?qū)?shí)際泛型參數(shù)進(jìn)行限定(限定的是實(shí)際參數(shù))
    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ù)。
  • 將所有移除的泛型參數(shù)用其限定的最左邊界類型替換。(多個(gè)限定的話,其他限定一定是接口,而且實(shí)際參數(shù)一定實(shí)現(xiàn)了這些接口,否則不合法,編譯不通過(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)題如下:

  • 先檢查,在編譯,以及檢查編譯的對(duì)象和引用傳遞的問(wèn)題
  • 自動(dòng)類型轉(zhuǎn)換
  • 類型擦除與多態(tài)的沖突和解決方法
  • 泛型類型變量不能是基本數(shù)據(jù)類型
  • 運(yùn)行時(shí)類型查詢
  • 異常中使用泛型的問(wèn)題
  • 數(shù)組(這個(gè)不屬于類型擦除引起的問(wèn)題)
  • 類型擦除后的沖突
  • 泛型在靜態(tài)方法和靜態(tài)類中的問(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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。