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

歡迎訪問 生活随笔!

生活随笔

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

java

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

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

一、基本概念和用法

在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ù)。下面是類型限定的幾個注意點:

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

    先看三行代碼

    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)就會報錯。
    這時就需要使用通配符來解決,通配符<?>,用來表示某種特定的類型,但是不知道這個類型到底是什么。例如下面的例子都是合法的:

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

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

  • 泛型變量的類型限定,是在定義泛型類的時候?qū)β暶鞯姆盒蛥?shù)進(jìn)行限定(限定的是形式參數(shù))
    public class Box{}
  • 通配符的邊界限定,是在定義化泛型類的引用的時候?qū)嶋H泛型參數(shù)進(jìn)行限定(限定的是實際參數(shù))
    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。多個限定的話,使用第一個邊界的類型變量來作為原始類型。
    至此可以知道,類型擦除的過程:

  • 移除所有的類型參數(shù)。
  • 將所有移除的泛型參數(shù)用其限定的最左邊界類型替換。(多個限定的話,其他限定一定是接口,而且實際參數(shù)一定實現(xiàn)了這些接口,否則不合法,編譯不通過,所以用最左邊界類型替換)
  • 泛型只存在于代碼中,泛型信息在編譯時都會被擦除,所以虛擬機(jī)中沒有泛型,只有普通類和普通方法。


    Java泛型的一些注意問題

    使用泛型時會有一些問題和限制,大部分是由類型擦除引起的,所以只要記住:泛型擦除后留下的只有原始類型。那么大部分問題都是很容易理解的。比如下面的例子:

    public void test(List<String> list){}public void test(List<Integer> list){}

    兩個方法經(jīng)過泛型擦除后,都只留下原始類型List,所以它們是同一個方法而不是方法的重載,如果這兩個方法在同一個類中同時存在,編譯器是會報錯的。

    其他更多問題如下:

  • 先檢查,在編譯,以及檢查編譯的對象和引用傳遞的問題
  • 自動類型轉(zhuǎn)換
  • 類型擦除與多態(tài)的沖突和解決方法
  • 泛型類型變量不能是基本數(shù)據(jù)類型
  • 運行時類型查詢
  • 異常中使用泛型的問題
  • 數(shù)組(這個不屬于類型擦除引起的問題)
  • 類型擦除后的沖突
  • 泛型在靜態(tài)方法和靜態(tài)類中的問題
  • 這些問題的答案在: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)容,希望文章能夠幫你解決所遇到的問題。

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