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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java 泛型总结(三):通配符的使用

發布時間:2025/3/21 java 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 泛型总结(三):通配符的使用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡介

前兩篇文章介紹了泛型的基本用法、類型擦除以及泛型數組。在泛型的使用中,還有個重要的東西叫通配符,本文介紹通配符的使用。

這個系列的另外兩篇文章:

  • Java 泛型總結(一):基本用法與類型擦除
  • Java 泛型總結(二):泛型與數組

數組的協變

在了解通配符之前,先來了解一下數組。Java 中的數組是協變的,什么意思?看下面的例子:

class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class Orange extends Fruit {}public class CovariantArrays {public static void main(String[] args) { Fruit[] fruit = new Apple[10];fruit[0] = new Apple(); // OKfruit[1] = new Jonathan(); // OK// Runtime type is Apple[], not Fruit[] or Orange[]:try {// Compiler allows you to add Fruit:fruit[0] = new Fruit(); // ArrayStoreException} catch(Exception e) { System.out.println(e); }try {// Compiler allows you to add Oranges:fruit[0] = new Orange(); // ArrayStoreException} catch(Exception e) { System.out.println(e); }} } /* Output: java.lang.ArrayStoreException: Fruit java.lang.ArrayStoreException: Orange *///:~

main?方法中的第一行,創建了一個?Apple?數組并把它賦給?Fruit?數組的引用。這是有意義的,Apple?是?Fruit?的子類,一個?Apple?對象也是一種?Fruit?對象,所以一個?Apple?數組也是一種?Fruit?的數組。這稱作數組的協變,Java 把數組設計為協變的,對此是有爭議的,有人認為這是一種缺陷。

盡管?Apple[]?可以 “向上轉型” 為?Fruit[],但數組元素的實際類型還是?Apple,我們只能向數組中放入?Apple或者?Apple?的子類。在上面的代碼中,向數組中放入了?Fruit?對象和?Orange?對象。對于編譯器來說,這是可以通過編譯的,但是在運行時期,JVM 能夠知道數組的實際類型是?Apple[],所以當其它對象加入數組的時候就會拋出異常。

泛型設計的目的之一是要使這種運行時期的錯誤在編譯期就能發現,看看用泛型容器類來代替數組會發生什么:

// Compile Error: incompatible types: ArrayList<Fruit> flist = new ArrayList<Apple>();

上面的代碼根本就無法編譯。當涉及到泛型時, 盡管?Apple?是?Fruit?的子類型,但是?ArrayList<Apple>?不是?ArrayList<Fruit>?的子類型,泛型不支持協變。

使用通配符

從上面我們知道,List<Number> list = ArrayList<Integer>?這樣的語句是無法通過編譯的,盡管?Integer?是?Number的子類型。那么如果我們確實需要建立這種 “向上轉型” 的關系怎么辦呢?這就需要通配符來發揮作用了。

上邊界限定通配符

利用?<? extends Fruit>?形式的通配符,可以實現泛型的向上轉型:

public class GenericsAndCovariance {public static void main(String[] args) {// Wildcards allow covariance:List<? extends Fruit> flist = new ArrayList<Apple>();// Compile Error: can’t add any type of object:// flist.add(new Apple());// flist.add(new Fruit());// flist.add(new Object());flist.add(null); // Legal but uninteresting// We know that it returns at least Fruit:Fruit f = flist.get(0);} }

上面的例子中,?flist?的類型是?List<? extends Fruit>,我們可以把它讀作:一個類型的 List, 這個類型可以是繼承了?Fruit?的某種類型。注意,這并不是說這個 List 可以持有?Fruit?的任意類型。通配符代表了一種特定的類型,它表示 “某種特定的類型,但是?flist?沒有指定”。這樣不太好理解,具體針對這個例子解釋就是,flist?引用可以指向某個類型的 List,只要這個類型繼承自?Fruit,可以是?Fruit?或者?Apple,比如例子中的?new ArrayList<Apple>,但是為了向上轉型給?flist,flist?并不關心這個具體類型是什么。

如上所述,通配符?List<? extends Fruit>?表示某種特定類型 (?Fruit?或者其子類 ) 的 List,但是并不關心這個實際的類型到底是什么,反正是?Fruit?的子類型,Fruit?是它的上邊界。那么對這樣的一個 List 我們能做什么呢?其實如果我們不知道這個 List 到底持有什么類型,怎么可能安全的添加一個對象呢?在上面的代碼中,向?flist?中添加任何對象,無論是?Apple?還是?Orange?甚至是?Fruit?對象,編譯器都不允許,唯一可以添加的是?null。所以如果做了泛型的向上轉型 (List<? extends Fruit> flist = new ArrayList<Apple>()),那么我們也就失去了向這個 List 添加任何對象的能力,即使是?Object?也不行。

另一方面,如果調用某個返回?Fruit?的方法,這是安全的。因為我們知道,在這個 List 中,不管它實際的類型到底是什么,但肯定能轉型為?Fruit,所以編譯器允許返回?Fruit。

了解了通配符的作用和限制后,好像任何接受參數的方法我們都不能調用了。其實倒也不是,看下面的例子:

public class CompilerIntelligence {public static void main(String[] args) {List<? extends Fruit> flist =Arrays.asList(new Apple());Apple a = (Apple)flist.get(0); // No warningflist.contains(new Apple()); // Argument is ‘Object’flist.indexOf(new Apple()); // Argument is ‘Object’//flist.add(new Apple()); 無法編譯} }

在上面的例子中,flist?的類型是?List<? extends Fruit>,泛型參數使用了受限制的通配符,所以我們失去了向其中加入任何類型對象的例子,最后一行代碼無法編譯。

但是?flist?卻可以調用?contains?和?indexOf?方法,它們都接受了一個?Apple?對象做參數。如果查看?ArrayList?的源代碼,可以發現?add()?接受一個泛型類型作為參數,但是?contains?和?indexOf?接受一個?Object?類型的參數,下面是它們的方法簽名:

public boolean add(E e) public boolean contains(Object o) public int indexOf(Object o)

所以如果我們指定泛型參數為?<? extends Fruit>?時,add()?方法的參數變為?? extends Fruit,編譯器無法判斷這個參數接受的到底是?Fruit?的哪種類型,所以它不會接受任何類型。

然而,contains?和?indexOf?的類型是?Object,并沒有涉及到通配符,所以編譯器允許調用這兩個方法。這意味著一切取決于泛型類的編寫者來決定那些調用是 “安全” 的,并且用?Object?作為這些安全方法的參數。如果某些方法不允許類型參數是通配符時的調用,這些方法的參數應該用類型參數,比如?add(E e)。

當我們自己編寫泛型類時,上面介紹的就有用了。下面編寫一個?Holder?類:

public class Holder<T> {private T value;public Holder() {}public Holder(T val) { value = val; }public void set(T val) { value = val; }public T get() { return value; }public boolean equals(Object obj) {return value.equals(obj);}public static void main(String[] args) {Holder<Apple> Apple = new Holder<Apple>(new Apple());Apple d = Apple.get();Apple.set(d);// Holder<Fruit> Fruit = Apple; // Cannot upcastHolder<? extends Fruit> fruit = Apple; // OKFruit p = fruit.get();d = (Apple)fruit.get(); // Returns ‘Object’try {Orange c = (Orange)fruit.get(); // No warning} catch(Exception e) { System.out.println(e); }// fruit.set(new Apple()); // Cannot call set()// fruit.set(new Fruit()); // Cannot call set()System.out.println(fruit.equals(d)); // OK} } /* Output: (Sample) java.lang.ClassCastException: Apple cannot be cast to Orange true *///:~

在?Holer?類中,set()?方法接受類型參數?T?的對象作為參數,get()?返回一個?T?類型,而?equals()?接受一個?Object作為參數。fruit?的類型是?Holder<? extends Fruit>,所以set()方法不會接受任何對象的添加,但是?equals()?可以正常工作。

下邊界限定通配符

通配符的另一個方向是 “超類型的通配符“:?? super T,T?是類型參數的下界。使用這種形式的通配符,我們就可以 ”傳遞對象” 了。還是用例子解釋:

public class SuperTypeWildcards {static void writeTo(List<? super Apple> apples) {apples.add(new Apple());apples.add(new Jonathan());// apples.add(new Fruit()); // Error} }

writeTo?方法的參數?apples?的類型是?List<? super Apple>,它表示某種類型的 List,這個類型是?Apple?的基類型。也就是說,我們不知道實際類型是什么,但是這個類型肯定是?Apple?的父類型。因此,我們可以知道向這個 List 添加一個?Apple?或者其子類型的對象是安全的,這些對象都可以向上轉型為?Apple。但是我們不知道加入?Fruit?對象是否安全,因為那樣會使得這個 List 添加跟?Apple?無關的類型。

在了解了子類型邊界和超類型邊界之后,我們就可以知道如何向泛型類型中 “寫入” ( 傳遞對象給方法參數) 以及如何從泛型類型中 “讀取” ( 從方法中返回對象 )。下面是一個例子:

public class Collections { public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i=0; i<src.size(); i++) dest.set(i,src.get(i)); } }

src?是原始數據的 List,因為要從這里面讀取數據,所以用了上邊界限定通配符:<? extends T>,取出的元素轉型為?T。dest?是要寫入的目標 List,所以用了下邊界限定通配符:<? super T>,可以寫入的元素類型是?T?及其子類型。

無邊界通配符

還有一種通配符是無邊界通配符,它的使用形式是一個單獨的問號:List<?>,也就是沒有任何限定。不做任何限制,跟不用類型參數的?List?有什么區別呢?

List<?> list?表示?list?是持有某種特定類型的 List,但是不知道具體是哪種類型。那么我們可以向其中添加對象嗎?當然不可以,因為并不知道實際是哪種類型,所以不能添加任何類型,這是不安全的。而單獨的?List list?,也就是沒有傳入泛型參數,表示這個 list 持有的元素的類型是?Object,因此可以添加任何類型的對象,只不過編譯器會有警告信息。

總結

通配符的使用可以對泛型參數做出某些限制,使代碼更安全,對于上邊界和下邊界限定的通配符總結如下:

  • 使用?List<? extends C> list?這種形式,表示 list 可以引用一個?ArrayList?( 或者其它 List 的 子類 ) 的對象,這個對象包含的元素類型是?C?的子類型 ( 包含?C?本身)的一種。
  • 使用?List<? super C> list?這種形式,表示 list 可以引用一個?ArrayList?( 或者其它 List 的 子類 ) 的對象,這個對象包含的元素就類型是?C?的超類型 ( 包含?C?本身 ) 的一種。

大多數情況下泛型的使用比較簡單,但是如果自己編寫支持泛型的代碼需要對泛型有深入的了解。這幾篇文章介紹了泛型的基本用法、類型擦除、泛型數組以及通配符的使用,涵蓋了最常用的要點,泛型的總結就寫到這里。

參考

  • Java 編程思想

from:?https://segmentfault.com/a/1190000005337789?

總結

以上是生活随笔為你收集整理的Java 泛型总结(三):通配符的使用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 探花系列在线观看 | 粉豆av| 国产情侣91 | 99热超碰在线 | 秋霞啪啪片 | 亚洲在线色 | 污黄视频网站 | 日韩一级二级 | 在线观看成年人网站 | gogo人体做爰大胆视频 | 国产肥白大熟妇bbbb视频 | 亚洲女人18毛片水真多 | 亚洲成人av中文字幕 | 日本囗交做爰视频 | 人妻精油按摩bd高清中文字幕 | 天天干天天日 | 色哟哟免费视频 | 成人午夜精品一区二区 | 18p在线观看 | 黑人极品ⅴideos精品欧美棵 | 久久婷婷综合国产 | 日韩αv| 国产盗摄av| 亚洲欧洲日韩综合 | 在线观看三级视频 | 午夜毛片在线观看 | 亚洲图片中文字幕 | 黄色wwwww | 国产精品久久国产愉拍 | 成人国产免费视频 | 日本天堂在线播放 | 欧美精品中文 | 日韩有码中文字幕在线观看 | 自拍视频在线播放 | 色男人在线| 新婚若妻侵犯中文字幕 | 91在线最新 | 亚洲人天堂 | 久久影院午夜理论片无码 | 久久不卡日韩美女 | 黄网站在线观看视频 | 老司机福利院 | 天天干 夜夜操 | 美女高潮网站 | 国语对白做受按摩的注意事项 | 伊人精品在线视频 | 人人干97| 老司机精品导航 | 日韩在线视频免费 | 青青草免费观看 | 亚洲AV综合色区无码国产播放 | 欧美日韩国产片 | 神马午夜国产 | 欧美丰满艳妇bbwbbw | 人人干av| 午夜视频www | 91免费短视频 | 免费av动漫 | 永久免费不卡在线观看黄网站 | 四虎5151久久欧美毛片 | 日韩成人一区二区视频 | 天天插天天爽 | 国产精品久久久久久久蜜臀 | 男女做爰猛烈高潮描写 | 日本www在线观看 | 欧美xx视频 | 成人无码av片在线观看 | 欧美第十页 | xxxxⅹxxxhd日本8hd | 91麻豆精品国产91久久久久久久久 | 中文字幕一区不卡 | 国产精品久久二区 | 性视频网 | 操亚洲| 欧美日韩激情在线一区二区三区 | 国产日韩欧美中文 | 免费av播放 | 东京热无码av一区二区 | 男人在线天堂 | 91亚洲欧美激情 | 成人激情视频在线观看 | japanese21ⅹxx日本 | 91婷婷在线| 国产成人一区二区三区 | 日韩精品免费一区二区 | 久久久久99人妻一区二区三区 | 免费av地址 | 三级亚洲 | av黄色在线免费观看 | 亚洲com | 亚洲国产日韩欧美一区二区三区 | 欧美性生活一区二区 | 伊人手机视频 | 亚洲视频免费播放 | 成人国产欧美 | 一区二区三区日韩精品 | 中文字幕不卡视频 | 天天看片中文字幕 | 国产深夜福利在线 |