java零基礎入門-高級特性篇(七) 泛型? 下
本章閱讀有難度字逗,請謹慎閱讀鸠按,如有不適狈网,可以跳過宙搬。
本章繼續(xù)講解泛型的上下限和其他的知識點,由于概念的復雜性拓哺,這里繼續(xù)使用Book這個類來描述勇垛,使概念理解起來具備連續(xù)性。
泛型的通配符可以分為3種類型士鸥,無邊界通配符闲孤,設定上限的通配符,設定下限的通配符础淤。
上一章講解的<?>是無邊界通配符崭放,設定上限的通配符<? extends E>哨苛,設定下限的通配符<? super E>。
設定上限的通配符
首先來看一張圖币砂。有三個類建峭,數(shù)學書繼承教科書,教科書繼承書籍【龃荩現(xiàn)在定義一個List<? extend Book> books集合亿蒸,這是什么意思呢?<? extend Book>這個泛型表示通配符掌桩?匹配的類型只能是Book類型的子類边锁,Book類型是?類型的上限波岛,上限就是說這里茅坛?匹配的最高類型只能是Book了。
看圖则拷,如果設置通配符上限<? extend Book>贡蓖,那么?可以是TextBook煌茬,也可以是MathBook斥铺,他們都是Book的子類。如果設置<? extend TextBook>坛善,這時候通配符晾蜘?的上限就是TextBook了,如果將Book類型作為通配類型眠屎,就會編譯報錯剔交,而TextBook的子類MathBook作為通配類型是可以的。如果設置<? extend MathBook>改衩,那么省容?只能是MathBook類型。
上例中主要看Student這個要讀書的可憐孩子燎字,readBook方法中設置了通配符的上限為Book,然后在主方法中設置的List泛型為MathBook阿宅,因為MathBook是Book的子類候衍,所以滿足通配符的條件,可以作為參數(shù)傳遞給readBook方法洒放。這里要注意的是蛉鹿,設置通配符上限的時候依然不可以使用add方法。為什么往湿?這里有點繞妖异,繞不過來就假設惋戏。
假設通配符?是MathBook他膳,那么參數(shù)就是List<MathBook>响逢,但是往List<MathBook>里面添加Book類型,這是不行的棕孙,因為Book是MathBook的父類舔亭,所以設置通配符上限也不可以使用add方法。
再來看循環(huán)蟀俊,在無邊界通配符的時候钦铺,要變量元素只能是Object類型,但是這里可以作為Book類型遍歷元素肢预,為什么矛洞?因為通配符?已經(jīng)設置了上限Book烫映,無論?是什么類型沼本,都是Book的子類,而子類是可以向上自動轉型的窑邦,如果參數(shù)是List<MathBook>擅威,依然可以使用Book類型來遍歷MathBook元素。
設定下限的通配符
再來看設定下限的通配符冈钦。定義List<? super Book> books郊丛;使用<? super Book>的形式設置通配符的下限,意思是通配符瞧筛?的類型只能是Book的父類厉熟,看圖
設置<? super Book>以后,如果通配符较幌?類型為TextBook揍瑟,會編譯錯誤,因為TextBook不是Book的父類乍炉,而是子類绢片,MathBook是孫子,錯的更離譜了岛琼。如果設置<? super TextBook>底循,那么?是Book類型就是允許的槐瑞,因為Book是TextBook的父類熙涤,其他的請自行揣摩。
這里只需要修改Student類型,其他代碼可以保持不變祠挫。需要注意的是這里設置了下限是MathBook那槽,而傳入的參數(shù)恰好是List<MathBook>,所以這里是可以的等舔。如果將下限設置為TextBook骚灸,代碼就會報錯了,因為這里的參數(shù)只能是TextBook或者父類Book软瞎,傳入List<MathBook>就會發(fā)生編譯錯誤逢唤。
這里居然可以使用add方法了,為什么涤浇?假設通配符鳖藕?是Book,那么List<Book>是可以添加Book的子類MathBook類型的只锭。因為這里通配符不論是什么類型著恩,必須是MathBook的父類,所以在父類的List集合添加子類MathBook是完全可以的蜻展。
至于循環(huán)中又變成了Object喉誊,是因為這里無法確定父類是什么類型,無法保證父類都有getName()這個方法纵顾。因為Object是Book的父類伍茄,如果參數(shù)是List<Object>,那么就無法使用Book里的方法了施逾,所以只能當成Object來操作敷矫。
泛型方法
泛型方法?前面不是講了么汉额?請注意曹仗,泛型方法需要在定義方法的時候,就對方法中的泛型類型進行定義蠕搜。
以上兩個方法不是泛型方法怎茫,原因就是真正的泛型方法需要在方法中定義。如何定義泛型方法妓灌?
修飾符? <泛型類型參數(shù)> 返回值? 方法名(){...}
請注意轨蛤,在方法的修飾符與返回值之間定義泛型類型參數(shù),這時候的方法才是一個泛型方法虫埂。泛型方法為什么要在定義方法的時候定義泛型俱萍?因為泛型是一個參數(shù),參數(shù)就有作用域告丢,定義在類上面的泛型作用域是整個類,定義在方法上的泛型,作用域是整個方法岖免。
先看左邊一張圖岳颇,如果在類上面指定了泛型,而又在類中定義了泛型方法颅湘,而且泛型方法中的泛型參數(shù)和類中的泛型參數(shù)一樣话侧,那么類上的泛型類型參數(shù)會被方法中的泛型參數(shù)覆蓋,程序也會出現(xiàn)警告闯参。
原因就在右圖瞻鹏,泛型類,是在實例化類的時候指明泛型的具體類型鹿寨,泛型方法新博,是在調(diào)用方法的時候指明泛型的具體類型。就算泛型方法定義的泛型類型參數(shù)與類定義的不同也是可以的脚草,因為方法自己定義了泛型參數(shù)赫悄,不需要類定義的泛型參數(shù)。在創(chuàng)建類對象的時候馏慨,具體定義的泛型類型可以和對象調(diào)用方法時埂淮,具體定義的泛型類型不同。比如Book在創(chuàng)建對象的時候使用的類型是Integer写隶,而調(diào)用sayTheBookName的時候傳遞的參數(shù)卻是String倔撞,這是完全可以的。如果定義了泛型方法慕趴,那么方法中的泛型可以看做是獨立于類定義的泛型而存在的痪蝇。所以如果定義泛型方法,建議方法中的泛型不要與類上定義的泛型類型相同秩贰。
然后霹俺,就算不使用泛型類,也是可以直接使用泛型方法的毒费。比如上例中丙唧,去掉Book<T>后面的泛型定義,將T改為String觅玻,程序也不會報錯想际,而且泛型方法可以正常被調(diào)用。
在使用泛型方法的時候有幾個地方需要注意:
1)自動類型推斷溪厘。比如book.sayTheBookName("教科書")胡本,這里程序會根據(jù)傳入的參數(shù)自動的將E推斷為String類型。
2)在定義方法的時候畸悬,不要因為類型可以自動推斷而定義相同的泛型類型參數(shù)侧甫。
這樣定義泛型方法是沒有問題的,可以正確編譯,也可以正確運行披粟。但是不建議這樣做咒锻,因為根據(jù)傳入的參數(shù),第一個E會被推斷為String類型守屉,而第二個E被推斷為Integer類型惑艇,這樣會造成理解上的歧義。
3)如果直接將泛型類型參數(shù)定義為類型是不會報錯的拇泛,但是如果在集合類型的泛型中滨巴,將泛型類型定義為一樣的參數(shù),就真的會報錯了俺叭。
上面“教科書”和1很容易推斷出是字符串和Integer類型恭取,但是如果調(diào)用方法時將有泛型的集合作為參數(shù),并且方法里面定義的集合泛型參數(shù)還是相同的绪颖,這時候程序就無法進行自動推斷了秽荤。這里最好將泛型方法再多定義一個泛型參數(shù),保證不會出現(xiàn)歧義柠横,這樣程序才能正確的進行類型推斷窃款。
public <M,O> int getAllNum(List<M> mathBook,List<O> englishBook){...}
這樣就可以避免歧義,正確推斷類型了牍氛。
泛型通配符和泛型方法
希望講到這里你還沒有暈晨继。
那么我們繼續(xù)看下一個問題。前面說的泛型通配符搬俊?可以代替任何一個類型紊扬,T這種形式的泛型類型參數(shù)不是也可以代替任何一個類型嗎?他們有什么區(qū)別呢唉擂?
其實泛型方法和方法中使用通配符在某些情況下是可以相互替代的餐屎。
1)這是他們第一個相同的地方,他們都可以接收一個未知的類型
2)你可能會說玩祟,通配符可以設置上下限啊腹缩,不好意思,這個功能泛型方法也有
將上面的方法修改成通配符上限和泛型方法上限也沒有任何問題空扎。需要注意的是藏鹊,使用泛型方法的上下限時,需要在方法定義的時候設置上下限转锈,而不是在參數(shù)里面設置上下限盘寡。
不同的地方在于,當設置泛型通配符上下限的時候撮慨,會存在一個只能讀不能寫的情況竿痰,就是無法往集合添加元素脆粥,因為不能確定類型。但是使用泛型方法的時候菇曲,就可以對集合進行添加操作冠绢,因為調(diào)用泛型方法的時候,類型就已經(jīng)確定了常潮。所以如果需要對集合元素進行讀取之外的操作,可以使用泛型方法楷力。
再一個就是當多個泛型類型參數(shù)之間有依賴關系的時候喊式,可以使用泛型方法。
這里有2個對象萧朝,依賴對象和被依賴對象岔留,T extends B,T是依賴對象检柬,B是被依賴對象献联。如果依賴對象不確定,可以使用泛型通配符何址,但是如果被依賴對象不確定里逆,則不可以使用泛型通配符。
依賴對象使用通配符沒有問題用爪,程序可以運行原押。因為通配符類型的上限就是B。
如果被依賴對象不確定偎血,則無法確定T類型的上限诸衔,導致程序編譯出錯。所以如果多個泛型類型之間有依賴關系颇玷,使用泛型方法會比較適合笨农。
泛型的擦除
泛型類型信息只在編譯的時候發(fā)揮作用,一旦被加載到虛擬機泛型信息會被全部丟棄帖渠。所以在編譯階段List<Book>和List<TextBook>可以看做兩個不同的類型谒亦,但是一旦加載到虛擬機,他們就是同樣的類型阿弃。泛型被丟了诊霹,那他是個什么類型?用專業(yè)的話說就是擦除到泛型的上限渣淳。比如沒有指定上限的時候脾还,擦除后的類型是Object,如果制定了類型的上限比如<? extends Book>入愧,那么擦除后的類型就是Book鄙漏。關于泛型的擦除會涉及到反射知識嗤谚,這里老規(guī)矩,先混臉熟怔蚌。
泛型知識一般多用于對代碼進行高層次抽象巩步,比如編寫一些工具方法,框架桦踊,比如在集合框架中就有大量的泛型使用椅野,所以有一定的難度,初學者掌握集合的泛型使用即可籍胯。