最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

前言

變性是OOP語言不變的大坑,Java的數(shù)組協(xié)變就是其中的一口老坑。因為最近踩到了,便做一個記錄。順便也提一下范型的變性。

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

下面話不多說了,來一起看看詳細的介紹吧

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

假設,我為一家餐館寫了這樣一段代碼

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

class Vegetable { }

class Carrot extends Vegetable { }

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

那么問題來了,Soup<Vegetable>和Soup<Carrot>之間是什么關系呢?

第一反應,Soup<Carrot>應該是Soup<Vegetable>的子類,因為胡蘿卜湯顯然是一種蔬菜湯。如果真是這樣,那就看看下面的代碼。其中Tomato表示西紅柿,是Vegetable的另一個子類

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

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

但是,兩句放在一起卻有了問題。soup的實際類型是Soup<Carrot>,而我們給它的add方法傳遞了一個Tomato的實例!換言之,我們在用西紅柿做胡蘿卜湯,肯定做不出來。所以,把Soup<Carrot>視為Soup<Vegetable>的子類在邏輯上雖然是通順的,在使用過程中卻是有缺陷的。

那么,Soup<Carrot>和Soup<Vegetable>究竟應該是什么關系呢?不同的語言有不同的理解和實現(xiàn)??偨Y起來,有三種情況。

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

(2)如果Soup<Carrot>和Soup<Vegetable>是無關的兩個類,則稱泛型Soup<T>是不變的

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

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

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

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

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

Vegetable[] vegetables = new Carrot[10];
vegetables[0] = new Tomato(); // 運行期錯誤

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

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

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

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

ArrayList是一個泛型類,它是不變的。所以,ArrayList<Carrot>和ArrayList<Vegetable>之間并無繼承關系,這段代碼在編譯期就會報錯。

兩段代碼雖然都會報錯,但通常情況下,編譯期錯誤總比運行期錯誤好處理一些。

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

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

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

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

要實現(xiàn)這一點,應該采用一種類似于協(xié)變性的寫法

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

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

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

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

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

但是方法內的第二句是正確的。因為參數(shù)是null,它可以是任何合法的類型。編譯器認為它是安全的。

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

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

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

這種情況就不存在上面的限制了,下面的代碼毫無問題

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

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關文章

  • 如何使用Spring?Boot設置上傳文件大小限制

    如何使用Spring?Boot設置上傳文件大小限制

    上傳文件是互聯(lián)網(wǎng)中常應用的場景之一,最典型的情況就是上傳頭像等,下面這篇文章主要給大家介紹了關于如何使用Spring?Boot設置上傳文件大小限制的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-01-01
  • java正則匹配HTML中a標簽里的中文字符示例

    java正則匹配HTML中a標簽里的中文字符示例

    這篇文章主要介紹了java正則匹配HTML中a標簽里的中文字符,涉及java中文正則及HTML元素操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2017-01-01
  • Springboot 全局日期格式化處理的實現(xiàn)

    Springboot 全局日期格式化處理的實現(xiàn)

    這篇文章主要介紹了Springboot 全局日期格式化處理的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-05-05
  • Java深入學習圖形用戶界面GUI之布局管理器

    Java深入學習圖形用戶界面GUI之布局管理器

    本文章向大家介紹Java GUI布局管理器,主要包括布局管理器使用實例、應用技巧、基本知識點總結和需要注意事項,具有一定的參考價值,需要的朋友可以參考一下
    2022-05-05
  • Java實現(xiàn)Flappy Bird游戲源碼

    Java實現(xiàn)Flappy Bird游戲源碼

    這篇文章主要為大家詳細介紹了Java實現(xiàn)Flappy Bird游戲源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • Java報錯Java.text.ParseException的解決方法匯總

    Java報錯Java.text.ParseException的解決方法匯總

    在Java開發(fā)的復雜世界中,錯誤處理是開發(fā)者必須面對的關鍵挑戰(zhàn)之一,其中,Java.text.ParseException就像一個隱藏在代碼叢林中的陷阱,常常讓開發(fā)者們陷入困惑,本文給大家介紹了Java報錯Java.text.ParseException的解決方法,需要的朋友可以參考下
    2024-10-10
  • Java?Web實現(xiàn)簡易圖書管理系統(tǒng)

    Java?Web實現(xiàn)簡易圖書管理系統(tǒng)

    這篇文章主要為大家詳細介紹了Java?Web實現(xiàn)簡易圖書管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡單使用方法

    Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡單使用方法

    本文主要介紹了Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡單使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-07-07
  • Java中Lombok的@Builder注解注意事項

    Java中Lombok的@Builder注解注意事項

    這篇文章主要介紹了Java中Lombok的@Builder注解注意事項,使用Lombok也會造成很多問題,尤其@Builder 有個很大的坑,已經(jīng)見過好幾次由于使用@Builder注解導致默認值失效的問題,如果測試時沒有在意這個問題,就很容易引發(fā)線上問題,需要的朋友可以參考下
    2023-12-12
  • SpringBoot如何讀取Resource下的文件

    SpringBoot如何讀取Resource下的文件

    這篇文章主要介紹了SpringBoot如何讀取Resource下的文件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04

最新評論

贞丰县| 东港市| 太康县| 乡宁县| 蒲城县| 宁南县| 武汉市| 新乡县| 观塘区| 色达县| 柘荣县| 手机| 淄博市| 大余县| 万州区| 凉城县| 县级市| 喀什市| 广宗县| 东港市| 凤阳县| 梓潼县| 临高县| 河源市| 夏邑县| 东丰县| 稷山县| 水城县| 册亨县| 怀宁县| 玉田县| 遵义县| 蒙阴县| 连南| 定日县| 汝城县| 阿合奇县| 扶风县| 江陵县| 甘孜| 恩施市|