5分鐘Java短文:泛型之逆變和協變
本文轉載自微信公眾號「咸魚正翻身」,作者MDove 。轉載本文請聯系咸魚正翻身公眾號。
前言
日常瑣碎的時間下,不適合看一些長篇高質量的文章,但是瑣碎時間也是時間,看一些短小精悍的文章來查缺補漏也是極好的。碎花化的時間,就交給“碎片化的文章”來填充吧。
今天“碎片化文章”主題:泛型-逆變和協變。逆變和協變擺在這我猜很多朋友會蒙蔽,畢竟我們日常好像、大概沒怎么接觸過這個概念。
事實并非如此,我們日常開發中經常見,只是不知道這么個名詞而已。
正文
OK,今天5分鐘短文就讓咱們聊一聊逆變和協變這倆個概念。
1、基礎概念
其實它們倆的概念很好理解。接下來讓我們仔細讀一遍下邊的這一段話:
逆變與協變用來描述類型轉換后的繼承關系。如果A、B表示類型,f(...)表示類型轉換,≤表示繼承關系(比如,A≤B表示A是B的子類)
如果f(...)是逆變的,那么當A≤B時則f(B)≤f(A)成立
如果f(...)是協變的,那么當A≤B時則f(A)≤f(B)成立
額外補充一條:如果f(...)是不變的,那么當A≤B時則f(B)與f(A)沒有任何關系
2、代碼場景
如果大家充分理解了上邊的話,其實就能想到咱們日常代碼中的例子:數組就是一種協變;泛型是不變的。上代碼:
- public class A extends B {}
- public class B {}
- public void test() {
- B[] arrs = new A[66];
- List<B> list = new ArrayList<A>();
- }
這段代碼是編不過的:
因為數組是協變的,所以 A[]是 B[]的子類;而泛型不是,所以 List并不是 List的子類。
3、通配符的意義
因為這個原因的存在,所以才有了通配符。
3.1、協變-上限通配符
代碼改成這個樣子就可以正常編譯了:
通配符的存在,讓泛型產生了協變,讓 List可以變成 List的子類。不過我猜經驗豐富的同學已經知道,這樣搞“沒什么卵用”,因為:
我們發現,這樣搞完。對于 list變量來說,我們只能 get()不能 add()!一時接受不了?其實這里也很好理解,協變之后對于list來說,我可以指向很多 List的子類。
假設此時我們可以隨意 add(),那么對于運行期來說簡直是災難:因為我可以隨意的 add(newA());add(newC())。如果這種情況存在那么我 get()的時候,是不是只能把它當做 B來使用,因為這里有可能有 A也有可能有 C...
這樣搞完全沒有意義...因此也就有了下邊的內容:逆變-下限通配符
3.2、逆變-下限通配符
直接上代碼:
- public class A extends B {}
- public class B {}
- public class C extends B {}
- public class D extends A {}
- public void test2(List<? super A> list){
- list.add(new A());
- list.add(new B());
- list.add(new C());
- list.add(new D());
- }
此時我們會發現:我們可以 add(), A及其子類。而這種實現就脫胎于咱們逆變這個概念。
3.3、小思考
如果我們仔細想一想會發現,這些都是在開發階段或者編譯階段的限制。做了這么多限制,到底為了什么?或者收益是什么樣的呢?
關于這部分內容的討論,咱們后續再聊~
尾聲
到此想聊的內容就結束了,關于泛型的話題還有很多很多,而熟練的使用和理解泛型對咱們編寫工具、框架有著關鍵的幫助。