欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中數(shù)組協(xié)變和范型不變性踩坑記錄

 更新時(shí)間:2019年02月24日 10:34:20   作者:左之了  
數(shù)組的協(xié)變性來(lái)源于數(shù)組的一個(gè)優(yōu)勢(shì),這篇文章主要給大家介紹了關(guān)于Java中數(shù)組協(xié)變和范型不變性踩坑的一些內(nèi)容,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

變性是OOP語(yǔ)言不變的大坑,Java的數(shù)組協(xié)變就是其中的一口老坑。因?yàn)樽罱鹊搅耍阕鲆粋€(gè)記錄。順便也提一下范型的變性。

解釋數(shù)組協(xié)變之前,先明確三個(gè)相關(guān)的概念,協(xié)變、不變和逆變。

下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧

一、協(xié)變、不變、逆變

假設(shè),我為一家餐館寫(xiě)了這樣一段代碼

class Soup<T> {
 public void add(T t) {}
}

class Vegetable { }

class Carrot extends Vegetable { }

有一個(gè)范型類Soup<T>,表示用食材T做的湯,它的方法add(T t)表示向湯中添加食材T。類Vegetable表示蔬菜,類Carrot表示胡蘿卜。當(dāng)然,Carrot是Vegetable的子類。

那么問(wèn)題來(lái)了,Soup<Vegetable>和Soup<Carrot>之間是什么關(guān)系呢?

第一反應(yīng),Soup<Carrot>應(yīng)該是Soup<Vegetable>的子類,因?yàn)楹}卜湯顯然是一種蔬菜湯。如果真是這樣,那就看看下面的代碼。其中Tomato表示西紅柿,是Vegetable的另一個(gè)子類

Soup<Vegetable> soup = new Soup<Carrot>();
soup.add(new Tomato());

第一句沒(méi)問(wèn)題,Soup<Carrot>是Soup<Vegetable>的子類,所以可以將Soup<Carrot>的實(shí)例賦給變量soup。第二句也沒(méi)問(wèn)題,因?yàn)閟oup聲明為Soup<Vegetable>類型,它的add方法接收一個(gè)Vegetable類型的參數(shù),而Tomato是Vegetable,類型正確。

但是,兩句放在一起卻有了問(wèn)題。soup的實(shí)際類型是Soup<Carrot>,而我們給它的add方法傳遞了一個(gè)Tomato的實(shí)例!換言之,我們?cè)谟梦骷t柿做胡蘿卜湯,肯定做不出來(lái)。所以,把Soup<Carrot>視為Soup<Vegetable>的子類在邏輯上雖然是通順的,在使用過(guò)程中卻是有缺陷的。

那么,Soup<Carrot>和Soup<Vegetable>究竟應(yīng)該是什么關(guān)系呢?不同的語(yǔ)言有不同的理解和實(shí)現(xiàn)??偨Y(jié)起來(lái),有三種情況。

(1)如果Soup<Carrot>是Soup<Vegetable>的子類,則稱泛型Soup<T>是協(xié)變的

(2)如果Soup<Carrot>和Soup<Vegetable>是無(wú)關(guān)的兩個(gè)類,則稱泛型Soup<T>是不變的

(3)如果Soup<Carrot>是Soup<Vegetable>的父類,則稱泛型Soup<T>是逆變的。(不過(guò)逆變不常見(jiàn))

理解了協(xié)變、不變和逆變的概念,再看Java的實(shí)現(xiàn)。Java的一般泛型是不變的,也就是說(shuō)Soup<Vegetable>和Soup<Carrot>是毫無(wú)關(guān)系的兩個(gè)類,不能將一個(gè)類的實(shí)例賦值給另一個(gè)類的變量。所以,上面那段用西紅柿做胡蘿卜湯的代碼,其實(shí)根本無(wú)法通過(guò)編譯。

二、數(shù)組協(xié)變

Java中,數(shù)組是基本類型,不是泛型,不存在Array<T>這樣的東西。但它和泛型很像,都是用另一個(gè)類型構(gòu)建的類型。所以,數(shù)組也是要考慮變性的。

與泛型的不變性不同,Java的數(shù)組是協(xié)變的。也就是說(shuō),Carrot[]是Vegetable[]的子類。而上一節(jié)中的例子已經(jīng)表明,協(xié)變有時(shí)會(huì)引發(fā)問(wèn)題。比如下面這段代碼

Vegetable[] vegetables = new Carrot[10];
vegetables[0] = new Tomato(); // 運(yùn)行期錯(cuò)誤

因?yàn)閿?shù)組是協(xié)變的,編譯器允許把Carrot[10]賦值給Vegetable[]類型的變量,所以這段代碼可以順利通過(guò)編譯。只有在運(yùn)行期,JVM真的試圖往一堆胡蘿卜中插入一個(gè)西紅柿的時(shí)候,才發(fā)現(xiàn)大事不好。所以,上面的代碼在運(yùn)行期會(huì)拋出一個(gè)java.lang.ArrayStoreException類型的異常。

數(shù)組協(xié)變性,是Java的著名歷史包袱之一。使用數(shù)組時(shí),千萬(wàn)要小心!

如果把例子中的數(shù)組替換為L(zhǎng)ist,情況就不同了。就像這樣

ArrayList<Vegetable> vegetables = new ArrayList<Carrot>(); // 編譯期錯(cuò)誤
vegetables.add(new Tomato());

ArrayList是一個(gè)泛型類,它是不變的。所以,ArrayList<Carrot>和ArrayList<Vegetable>之間并無(wú)繼承關(guān)系,這段代碼在編譯期就會(huì)報(bào)錯(cuò)。

兩段代碼雖然都會(huì)報(bào)錯(cuò),但通常情況下,編譯期錯(cuò)誤總比運(yùn)行期錯(cuò)誤好處理一些。

三、當(dāng)泛型也想要協(xié)變、逆變

泛型是不變的,但某些場(chǎng)景里我們還是希望它能協(xié)變起來(lái)。比如,有一個(gè)天天喝蔬菜湯減肥的小姐姐

class Girl {
 public void drink(Soup<Vegetable> soup) {}
}

我們希望drink方法可以接受各種不同的蔬菜湯,包括Soup<Carrot>和Soup<Tomato>。但受到不變性的限制,它們無(wú)法作為drink的參數(shù)。

要實(shí)現(xiàn)這一點(diǎn),應(yīng)該采用一種類似于協(xié)變性的寫(xiě)法

public void drink(Soup<? extends Vegetable> soup) {}

意思是,參數(shù)soup的類型是泛型類Soup<T>,而T是Vegetable的子類(也包括Vegetable自己)。這時(shí),小姐姐終于可以愉快地喝上胡蘿卜湯和西紅柿湯了。

但是,這種方法有一個(gè)限制。編譯器只知道泛型參數(shù)是Vegetable的子類,卻不知道它具體是什么。所以,所有非null的泛型類型參數(shù)均被視為不安全的。說(shuō)起來(lái)很拗口,其實(shí)很簡(jiǎn)單。直接上代碼

public void drink(Soup<? extends Vegetable> soup) {
 soup.add(new Tomato()); // 錯(cuò)誤
 soup.add(null); // 正確
}

方法內(nèi)的第一句會(huì)在編譯期報(bào)錯(cuò)。因?yàn)榫幾g器只知道add方法的參數(shù)是Vegetable的子類,卻不知道它具體是Carrot、Tomato、或者其他的什么類型。這時(shí),傳遞一個(gè)具體類型的實(shí)例一律被視為不安全的。即使soup真的是Soup<Tomato>類型也不行,因?yàn)閟oup的具體類型信息是在運(yùn)行期才能知道的,編譯期并不知道。

但是方法內(nèi)的第二句是正確的。因?yàn)閰?shù)是null,它可以是任何合法的類型。編譯器認(rèn)為它是安全的。

同樣,也有一種類似于逆變的方法

public void drink(Soup<? super Vegetable> soup) {}

這時(shí),Soup<T>中的T必須是Vegetable的父類。

這種情況就不存在上面的限制了,下面的代碼毫無(wú)問(wèn)題

public void drink(Soup<? super Vegetable> soup) {
 soup.add(new Tomato());
}

Tomato是Vegetable的子類,自然也是Vegetable父類的子類。所以,編譯期就可以確定類型是安全的。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • SpringBoot攔截器實(shí)現(xiàn)項(xiàng)目防止接口重復(fù)提交

    SpringBoot攔截器實(shí)現(xiàn)項(xiàng)目防止接口重復(fù)提交

    基于SpringBoot框架來(lái)開(kāi)發(fā)業(yè)務(wù)后臺(tái)項(xiàng)目時(shí),接口重復(fù)提交是一個(gè)常見(jiàn)的問(wèn)題,本文主要介紹了SpringBoot攔截器實(shí)現(xiàn)項(xiàng)目防止接口重復(fù)提交,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • mybatis條件構(gòu)造器(EntityWrapper)的使用方式

    mybatis條件構(gòu)造器(EntityWrapper)的使用方式

    這篇文章主要介紹了mybatis條件構(gòu)造器(EntityWrapper)的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java?IP歸屬地功能實(shí)現(xiàn)詳解

    java?IP歸屬地功能實(shí)現(xiàn)詳解

    前一陣子抖音和微博開(kāi)始陸續(xù)上了IP歸屬地的功能,引起了眾多熱議,有大批在國(guó)外的老鐵們開(kāi)始"原形畢露",被定位到國(guó)內(nèi)來(lái),那么IP歸屬到底是怎么實(shí)現(xiàn)的呢?那么網(wǎng)紅們的歸屬地到底對(duì)不對(duì)呢
    2022-07-07
  • Java字符轉(zhuǎn)碼之UTF-8互轉(zhuǎn)GBK具體實(shí)現(xiàn)

    Java字符轉(zhuǎn)碼之UTF-8互轉(zhuǎn)GBK具體實(shí)現(xiàn)

    在Java程序中字符串默認(rèn)的編碼方式是UTF-16編碼,因此需要將GBK編碼轉(zhuǎn)換為UTF-8編碼,主要是為了避免出現(xiàn)亂碼的情況,這篇文章主要給大家介紹了關(guān)于Java字符轉(zhuǎn)碼之UTF-8互轉(zhuǎn)GBK具體實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2023-11-11
  • java高并發(fā)的ReentrantLock重入鎖

    java高并發(fā)的ReentrantLock重入鎖

    這篇文章主要介紹了如何教你完全理解ReentrantLock重入鎖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面我們來(lái)一起學(xué)習(xí)一下吧
    2021-10-10
  • 如何使用Java?8中DateTimeFormatter類型轉(zhuǎn)換日期格式詳解

    如何使用Java?8中DateTimeFormatter類型轉(zhuǎn)換日期格式詳解

    這篇文章主要介紹了如何使用Java?8中DateTimeFormatter類型轉(zhuǎn)換日期格式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Java內(nèi)存模型JMM與volatile

    Java內(nèi)存模型JMM與volatile

    這篇文章主要介紹了Java內(nèi)存模型JMM與volatile,Java內(nèi)存模型是一種抽象的概念,并不真實(shí)存在,它描述的是一組規(guī)則或規(guī)范,定義了程序中各個(gè)變量的訪問(wèn)方式
    2022-07-07
  • IDEA巧用Postfix Completion讓碼速起飛(小技巧)

    IDEA巧用Postfix Completion讓碼速起飛(小技巧)

    這篇文章主要介紹了IDEA巧用Postfix Completion讓碼速起飛,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Springboot框架實(shí)現(xiàn)自動(dòng)裝配詳解

    Springboot框架實(shí)現(xiàn)自動(dòng)裝配詳解

    在使用springboot時(shí),很多配置我們都沒(méi)有做,都是springboot在幫我們完成,這很大一部分歸功于springboot自動(dòng)裝配。本文將詳細(xì)為大家講解SpringBoot的自動(dòng)裝配原理,需要的可以參考一下
    2022-08-08
  • Java如何獲取真實(shí)請(qǐng)求IP

    Java如何獲取真實(shí)請(qǐng)求IP

    這篇文章主要介紹了Java如何獲取真實(shí)請(qǐng)求IP問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08

最新評(píng)論