摘要:好了,有了這樣的背景知識(shí),我們可以來看一下上界通配了,在中,可以使用來界定一個(gè)上界,的意思是所有屬于的子類,是上界,不能突破天界啊,我們具體化一下,的意思就是,所有的子類都可以匹配這個(gè)通配符。
1、上界通配符
首先,需要知道的是,Java語言中的數(shù)組是支付協(xié)變的,什么意思呢?看下面的代碼:
static class A extends Base{ void f() { System.out.println("A.f"); } } static class B extends A { void f() { System.out.println("B.f"); } } static class C extends B { void f() { System.out.println("C.f"); } } static { A[] arrayA = new A[3]; arrayA[0] = new A(); arrayA[1] = new B(); arrayA[2] = new C(); for (A item : arrayA) { item.f(); } } //output A.f B.f C.f
我們明明讓數(shù)組的類型為A,但是向其中加入B、C也是可以行得通的,為什么呢?我們發(fā)現(xiàn)B繼承了A,屬于A的子類,C繼承了B,屬于B的子類,Java中的繼承是可以傳遞的,所以C依然屬于A的子類,所以B和C都是A的子類,另外一點(diǎn),在Java中,類型向上轉(zhuǎn)換是非常自然的,不需要強(qiáng)制轉(zhuǎn)換會(huì)自動(dòng)進(jìn)行,也就是說,B和C的實(shí)例都可以自動(dòng)轉(zhuǎn)換為類型A的實(shí)例。好了,有了這樣的背景知識(shí),我們可以來看一下上界通配了,在java中,可以使用 extends Type> 來界定一個(gè)上界, extends Type>的意思是所有屬于Type的子類,Type是上界,不能突破天界啊,我們具體化一下, extends A>的意思就是,所有A的子類都可以匹配這個(gè)通配符。所有我們的B、C的實(shí)例以及他們的子類的實(shí)例都可以匹配,但是Base就不可以,因?yàn)锽ase是A的父類,而我們的上界是A啊,所以當(dāng)然不能是Base了。很自然的,我們有下面的代碼:
A a = new B(); A b = new C(); C c = new C(); List extends A> list = new ArrayList(); list.add(a); list.add(b); list.add(c);
我們覺得很自然這樣做是無可厚非的,對(duì)吧?但是編譯器很顯然不允許我們這樣做,為什么?我們的list的類型使用了上界通配符啊,而且匹配的是所有A的子類,而我們add的都是A的子類啊,為什么不可以呢?我們?cè)賮砜匆幌?? extends A>,我們的list可以持有任何A的子類對(duì)象,也就是A、B、C的實(shí)例都是可以的,那我們是不是可以把 extend B>認(rèn)為是 extends A>的子類呢? entends C>呢?我們暫且認(rèn)為是可以這樣吧,那看下面的這個(gè)方法:
void joke(List extends A> list) { A a = new B(); A b = new C(); C c = new C(); list.add(a); list.add(b); list.add(c); }
當(dāng)然上面的代碼是無法通過編譯的,我們分析一下為什么,記住 extends A>是設(shè)定上界,所以,joke方法的參數(shù)是開放的,我們可以傳進(jìn)去一個(gè) extend A>的list,也可以是一個(gè) extends B>的list,還可以是一個(gè) extends C>的list。因?yàn)橄旅娴拇a是可以通過編譯的:
private static void jokeIn(List list) { // } static { List extends A> list = new ArrayList<>(); List extends B> list1 = new ArrayList<>(); List extends C> list2 = new ArrayList<>(); jokeIn(list); jokeIn(list1); jokeIn(list2); }
好吧,問題來了,當(dāng)我們傳到j(luò)oke方法中的參數(shù)是List extends A>的時(shí)候,方法內(nèi)部的add都是可以接受的,但是當(dāng)我們傳進(jìn)去的參數(shù)是List extends B>的時(shí)候,list.add(a)明顯是無法成功的,因?yàn)槲覀兊膌ist將可以允許持有B的子類,但是A不在這個(gè)范圍里面,所以是不合法的,當(dāng)傳進(jìn)去的是List extends C>的時(shí)候呢?連list.add(B)也不允許了。所以這就是問題所在,所以不允許這樣的代碼通過編譯是明智的,因?yàn)槲覀儾荒芸偸潜WC調(diào)用joke方法的用戶會(huì)嚴(yán)格傳進(jìn)來一個(gè)List extends A>的參數(shù)。
那怎么使用上界呢?換句話說,如何來產(chǎn)生一個(gè)List extends A>的list呢?還記得一開始我們說的數(shù)組協(xié)變嗎?下面的代碼使用了java語言數(shù)組具有協(xié)變能力來產(chǎn)生一個(gè)具有上界的list:
List extends A> list = Arrays.asList(a, b);
Arrays.asList(T ... data)使用了ArrayList的一個(gè)構(gòu)造函數(shù):
ArrayList(E[] array) { a = Objects.requireNonNull(array); }
可以看到使用了數(shù)組的協(xié)變,使得我們可以在Arrays.asList里面?zhèn)鬟f進(jìn)去所以A的子類對(duì)象。
2、下界通配符
上界定義了可以達(dá)到了最高點(diǎn),超出就是違法的;而下界則是說明了底線,你只能比底線更高級(jí),低于底線就是違法的。在java里面,可以使用 super Type>來表達(dá)下界的意義,具體一點(diǎn), super A>表達(dá)的和 extends A>是兩個(gè)相反的方向,前者是說所有基于A的基類,后者是說所有基于A的子類,我們?cè)賮砜匆幌孪旅孢@個(gè)方法:
void joke(List super A> list) { A a = new B(); A b = new C(); C c = new C(); list.add(a); list.add(b); list.add(c); }
此時(shí)的joke方法的參數(shù)是List super A>,此時(shí)List super B>和List super C>都變成了List super A>的父類了,因?yàn)閷?shí)際上List super B>和List super C>表達(dá)的能力比List super A>更強(qiáng),也就是List super C>包含了List super B>和List super A>,而List super B>則包含了List super A>,好了,說明了這些之后,我們?cè)賮砜匆幌聦?duì)joke方法的調(diào)用會(huì)出現(xiàn)哪些情況:
static { List super A> list = new ArrayList<>(); List super B> list1 = new ArrayList<>(); List super C> list2 = new ArrayList<>(); jokeIn(list); jokeIn(list1); // error jokeIn(list2); //error }
好吧,問題出現(xiàn)了,我們可以將List super A> 的參數(shù)傳遞給joke,因?yàn)檫@正是我們需要的,而我們也知道List super A>的表達(dá)能力在List super B>和 super C>中是最低的,所以,當(dāng)我們將一個(gè)表達(dá)能力強(qiáng)于List super A>的參數(shù)傳遞給joke之后,編譯器報(bào)錯(cuò)了。當(dāng)然,這僅僅是為了說明所謂下界的界定。
有了下界,我們可以使用下面的代碼來為我們工作了:
List super A> lists = new ArrayList<>(); lists.add(a); lists.add(b); lists.add(c);
解釋一下,lists里面的元素類型是這樣一種類型,這種類型是A的基類,我們只是界定了下界,只要高于這個(gè)下界,就可以被lists接收,而b、c的基類都是A,可以被lists接收,所以上面的代碼是可以工作的。
3、無界通配符
有了上界和下界,還有無界,需要說明的一點(diǎn)是,不能同時(shí)使用上界和下界,因?yàn)橛袩o界啊(開玩笑的)??!
我們?cè)趈ava中使用>來表達(dá)無界,對(duì)于>,目前來講鎖表達(dá)的意思是:
我是想要java的范型來編寫這段代碼,我在這里并不是想使用原生類 型,但是在當(dāng)前這種情況下,泛型參數(shù)可以持有任何類型。 ----來自《java編程思想》15.10.3 無界通配符(686頁)
使用無界通配符的一種場(chǎng)景是:如果向一個(gè)使用>的方法傳遞了一個(gè)原生類型,那么對(duì)編譯器來說可能會(huì)推斷出實(shí)際的參數(shù)類型,使得這個(gè)方法可以回轉(zhuǎn)并且調(diào)用另外一個(gè)使用這個(gè)確切類型的方法。這叫做“類型捕獲”,看下面的代碼:
static class Holder{ private T data; public Holder() { } public Holder(T data) { this.data =data; } public T getData() { return data; } public void setData(T data) { this.data = data; } } static void actual(Holder holder) { System.out.println(holder.getData()); } static void func(Holder> holder) { actual(holder); } static { Holder> holder = new Holder<>("hujian"); func(holder); }
可以看到,actual的參數(shù)是具體的T,而func的參數(shù)是無界的>,這里發(fā)生了一件參數(shù)類型捕獲的事情,在調(diào)用func的時(shí)候,類型被捕獲,而可以在actual方法中使用我們從func中傳遞進(jìn)來的無界參數(shù)。
可以使用無界通配符來接收多個(gè)類型的對(duì)象,然后根據(jù)不同的類型來交付給不同的方法來處理,可以回憶一下操作系統(tǒng)的中斷處理程序的處理方法,通過安裝一些中斷類型和與之對(duì)應(yīng)的handler,然后通過控制程序來將中斷處理信號(hào)分發(fā)到不同的handler中處理,其實(shí)思想是一樣的,可以看一下下面的代碼來理解這個(gè)模型:
staticvoid actual(Holder holder) { T data = holder.getData(); if (data instanceof String) { actual((String) data); } else if (data instanceof Integer) { actual((Integer) data); } else if (data instanceof Double) { actual((Double) data); } } static void actual(String holder) { System.out.print("string:" + holder); } static void actual(Integer holder) { System.out.println("Integer:" + holder); } static void actual(Double holder) { System.out.println("double:" + holder); } static void func(Holder> holder) { actual(holder); }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70560.html
摘要:虛擬機(jī)中并沒有泛型類型對(duì)象,所有的對(duì)象都是普通類。其原因就是泛型的擦除。中數(shù)組是協(xié)變的,泛型是不可變的。在不指定泛型的情況下,泛型變量的類型為該方法中的幾種類型的同一個(gè)父類的最小級(jí),直到。 引入泛型的主要目標(biāo)有以下幾點(diǎn): 類型安全 泛型的主要目標(biāo)是提高 Java 程序的類型安全 編譯時(shí)期就可以檢查出因 Java 類型不正確導(dǎo)致的 ClassCastException 異常 符合越早出...
摘要:使用強(qiáng)轉(zhuǎn)的話,只能強(qiáng)轉(zhuǎn)成和它的基類,如果強(qiáng)轉(zhuǎn)成的子類的話,有可能會(huì)報(bào)運(yùn)行時(shí)異常。擁有類型,它是的子類型因此,我們可以將賦給類型為的變量在聲明處設(shè)置后,就可以和或它的子類進(jìn)行比較了。 歡迎關(guān)注我的博客:songjhhs blog原文連接:對(duì)比Java泛型中的extends/super和Kotlin的out/in 在 Java 泛型中,有一個(gè)叫做通配符上下界 bounded wildca...
摘要:泛型方法泛型類中可以定義靜態(tài)非靜態(tài)的泛型方法。上述泛型類會(huì)被替換成下面形式一般使用第一個(gè)限定類型替換變?yōu)樵碱愋停瑳]有限定類型,使用替換。 引言 在面向?qū)ο蟮氖澜缋?,我們?nèi)绻枰粋€(gè)容器來盛裝對(duì)象。舉個(gè)例子:一個(gè)籃子。我們可以用這個(gè)籃子裝蘋果,也可以用這個(gè)籃子裝香蕉。基于 OOP 的思想,我們不希望為蘋果和香蕉分別創(chuàng)建不同的籃子;同時(shí),我們希望放進(jìn)籃子里的是蘋果,拿出來的還是蘋果。于是...
摘要:代碼使用泛型類中不依賴于類型參數(shù)的方法。委托依賴于動(dòng)態(tài)綁定,因?yàn)樗蠼o定的方法調(diào)用可以在運(yùn)行時(shí)調(diào)用不同的代碼段。委托捕獲操作并將其發(fā)送給另一個(gè)對(duì)象。委托可以被看作是在對(duì)象層次上的復(fù)用機(jī)制,而繼承是類層次上的復(fù)用機(jī)制。 大綱 設(shè)計(jì)可復(fù)用的類 繼承和重寫 重載(Overloading) 參數(shù)多態(tài)和泛型編程 行為子類型與Liskov替換原則 組合與委托 設(shè)計(jì)可復(fù)用庫與框架 API和庫...
摘要:靜態(tài)變量是被泛型類的所有實(shí)例所共享的。所以引用能完成泛型類型的檢查。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。事實(shí)上,泛型類擴(kuò)展都不合法。 前言 和C++以模板來實(shí)現(xiàn)靜多態(tài)不同,Java基于運(yùn)行時(shí)支持選擇了泛型,兩者的實(shí)現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得...
閱讀 3475·2021-11-18 10:02
閱讀 3722·2021-09-13 10:25
閱讀 1931·2021-07-26 23:38
閱讀 2581·2019-08-30 15:44
閱讀 2288·2019-08-30 13:51
閱讀 1235·2019-08-26 11:35
閱讀 2279·2019-08-26 10:29
閱讀 3455·2019-08-23 14:56