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

歡迎訪問 生活随笔!

生活随笔

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

java

java协变 生产者理解_Java进阶知识点:协变与逆变

發布時間:2025/4/5 java 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java协变 生产者理解_Java进阶知识点:协变与逆变 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、背景

要搞懂Java中的協辦與逆變,不得不從繼承說起,如果沒有繼承,協變與逆變也天然不存在了。

我們知道,在Java的世界中,存在繼承機制。比如MochaCoffee類是Coffee類的派生類,那么我們可以在任何時候使用MochaCoffee類的引用去替換Coffee類的引用(重寫函數時,形參必須與重寫函數完全一致,這是一處列外),而不會引發編譯錯誤(至于會不會引發程序功能錯誤,取決于代碼是否符合里氏替換原則)。

簡而言之,如果B類是A類的派生類,那么B類的引用可以賦值給A類的引用。

賦值的方式最常見有兩種。

第一:使用等于運算符顯式賦值

Coffee coffee = new MochaCoffee();

上述代碼可以分兩階段理解,首先new MochaCoffee()返回MochaCoffee的引用,然后將此引用顯式賦值給Coffee類型的引用。

第二:函數傳參賦值

public class Main {

public static void main(String[] args) {

function(new MochaCoffee());

}

public static void function(Coffee coffee) {

}

}

基礎知識復習完后,我們正式開始進入協變與逆變的世界,首先我們來看如下常見代碼:

Coffee a[] = new MochaCoffee[10];

List extends Coffee> b = new ArrayList();

List super MochaCoffee> c = new ArrayList();

這三行代碼每一行單獨看,好像都可以勉強看得懂,但是這三行代碼似乎透露出一些讓人內心秩序隱隱不安的疑惑:

MochaCoffee[]是Coffee[]的子類?

ArrayList是List extends Coffee>的子類?

ArrayList是List super MochaCoffee>的子類?

我們只學習過Class之間有繼承關系,這些數組、容器類型之間難道也有繼承關系,這種繼承關系在JDK哪一處源碼中有定義?還有沒有其他類似的情況?

如果你也有類似的問題,說明你的知識體系中缺失了一個知識點,這就是我們今天講的Java中的協變與逆變。

二、逆變與協變

2.1 定義

假設F(X)代表Java中的一種代碼模式,其中X為此模式中可變的部分。如果B是A的派生類,而F(B)也享受F(A)派生類的待遇,那么F模式是協變的,如果F(A)反過來享受F(B)派生類的待遇,那么F模式是逆變的。如果F(A)和F(B)之間不享受任何繼承待遇,那么F模式是不變的。(這里的繼承待遇指的是前面復習到的“如果B類是A類的派生類,那么B類的引用可以賦值給A類的引用。”)

Java中絕大部分代碼模式都是不變的(大家可以安心了)。

2.2 Java中的協變與協變模式

Java中目前已知的支持協變與逆變的模式,我總結了三類,歡迎大家補充。

2.2.1 F(X) = 將X數組化,此時F模式是協變的

Coffee a[] = new Coffee[10];

MochaCoffee b[] = new MochaCoffee[10];

a = b; //b可以賦值給a

這可以回答之前的問題,雖然MochaCoffee[]不是Coffee[]的子類,但數組化這種代碼模式是協變的,所以MochaCoffee[]也可以直接賦值給Coffee[]。

值得注意的是,雖然數組是協變的,但是數組是會記住實際類型并在每一次往數組中添加元素時做類型檢查。比如如下代碼雖然可以利用數組的協變性通過編譯,但是運行時依然會拋出異常。

Coffee a[] = new MochaCoffee[10];

a[0] = new Coffee(); //拋出ArrayStoreException

這也是數組的協變設計被廣為詬病的原因,因為異常應該盡量在編譯時就發現,而不是推遲到運行時。不過數組支持協變后,java.util.Arrays#equals(java.lang.Object[], java.lang.Object[])這種類型的函數就不需要為每種可能的數組類型去分別實現一次了。數組的協變設計有歷史版本兼容性方面的考慮等,Java的每一個設計可能不是最優的,但確實是設計者在當時的情況下可以做出的最好選擇。

2.2.2 F(X) = 將X通過 extend X>語法作為泛型參數,此時F模式是協變的

List extends Coffee> a = new ArrayList();

List extends MochaCoffee> b = new ArrayList();

a = b; //b可以賦值給a

同樣的,雖然ArrayList不是List extends Coffee>的子類,但是List extends X>這種代碼模式是協變的,所以b可以直接賦值給a。

值得注意的是,雖然利用協變性,可以將ArrayList賦值給List extends Coffee>,但是賦值后,List extends Coffee>中不能取出MochaCoffee,同時也只能添加null。因為List跟數組不一樣,它在運行時插入元素時,類型信息已經被擦除為Object,無法做類型檢測,只能依靠聲明在編譯時做嚴格的類型檢查,List extends?Coffee>聲明意味著這個容器中的元素類型不確定,可能是Coffee的任何子類,所以往里面添加任何類型都是不安全的,但是可以取出Coffee類型。如下:

List extends Coffee> a = new ArrayList();

//a.add(new MochaCoffee()); //不能添加MochaCoffee

//a.add(new Coffee()); //也不能添加Coffee

a.add(null); //可以添加null

Coffee coffee = a.get(0); //可以取出Coffee

2.2.3?F(X) = 將X通過 super?X>語法作為泛型參數,此時F模式是逆變的

List super MochaCoffee> a = new ArrayList();

List super Coffee> b = new ArrayList();

a = b; //b可以賦值給a

ArrayList不是List super MochaCoffee>的子類,但是List super X>這種代碼模式是逆變的,所以b可以直接賦值給a。

值得注意的是,雖然利用逆變性,可以將ArrayList賦值給List super MochaCoffee>,但是賦值后,List super MochaCoffee>中不能添加Coffee,同時也只能取出Object(除非進行強制類型轉換)。List super MochaCoffee>聲明意味著這個容器中的元素類型不確定,可能是MochaCoffee的任何基類,所以往里面添加MochaCoffee及其子類是安全的,但是取出的類型就只能是最頂層基類Object了。如下:

List super MochaCoffee> a = new ArrayList();

// a.add(new Coffee()); //不能添加Coffee

a.add(new MochaCoffee()); //可以添加MochaCoffee

Object object = a.get(0); //只能取出Object

注:沒有extend和super關鍵字加持的泛型模式都是不變的,A與B之間有繼承關系,但是List和List之間不享受任何繼承待遇,這就解決了上面提到數組協變導致的問題,讓類型錯誤在編譯時就可以被發現。

2.3 PECS原則

2.2.2和2.2.3中的注意事項,也體現了著名的PECS原則:“Producer Extends,Consumer Super”。

因為使用 extends T>后,如果泛型參數作為返回值,用T接收一定是安全的,也就是說使用這個函數的人可以知道你生產了什么東西;

而使用 super T>后,如果泛型參數作為入參,傳遞T及其子類一定是安全的,也就是說使用這個函數的人可以知道你需要什么東西來進行消費。

比如Java8新增的函數接口java.util.function.Consumer#andThen方法就體現了Consumer Super這一原則。

三、總結

1、數組是協變的。

2、extend關鍵字加持的泛型是協變的。

3、super關鍵字加持的泛型是逆變的。

4、注意數組和泛型容器中添加和獲取元素的類型限制。

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的java协变 生产者理解_Java进阶知识点:协变与逆变的全部內容,希望文章能夠幫你解決所遇到的問題。

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