首頁 Java java教程 Java數組協變與範型不變性的知識介紹(附程式碼)

Java數組協變與範型不變性的知識介紹(附程式碼)

Feb 23, 2019 pm 04:38 PM
java數組

這篇文章帶給大家的內容是關於Java數組協變與範型不變性的知識介紹(附代碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

變性是OOP語言不變的大坑,Java的陣列協變就是其中的一個老坑。因為最近踩到了,便做一個記錄。順便也提一下範式的變性。

解釋數組協變之前,先明確三個相​​關的概念,協變、不變和逆變。

一、協變、不變、逆變

#假設,我為一家餐廳寫了這樣一段程式碼

#
class Soup<T> {
    public void add(T t) {}
}
class Vegetable { }
class Carrot extends Vegetable { }
登入後複製

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

那麼問題來了,Soup和Soup之間是什麼關係呢?

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

Soup<Vegetable> soup = new Soup<Carrot>();
soup.add(new Tomato());
登入後複製

第一句沒問題,Soup是Soup的子類,Soup是Soup的子類,所以可以將Soup的實例賦給變數soup。第二句也沒問題,因為soup宣告為Soup型,它的add方法接收一個Vegetable類型的參數,而Tomato是Vegetable,型別正確。

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

那麼,Soup和Soup究竟應該是什麼關係呢?不同的語言有不同的理解和實現。總結起來,有三種情況。

(1)如果Soup是Soup的子類,則稱泛型Soup是協變的
(2)如果Soup和Soup ;是無關的兩個類,則稱泛型Soup是不變的
(3)如果Soup是Soup的父類,則稱泛型Soup是逆變的。 (不過逆變不常見)

理解了協變、不變和逆變的概念,再看Java的實作。 Java的一般泛型是不變的,也就是說Soup和Soup是毫無關係的兩個類,不能將一個類別的實例賦值給另一個類別的變數。所以,上面那段用番茄做胡蘿蔔湯的程式碼,其實根本無法通過編譯。

二、陣列協變

Java中,陣列是基本型,不是泛型,不存在Array這樣的東西。但它和泛型很像,都是用另一個型別建構的型別。所以,數組也是要考慮變性的。

與泛型的不變性不同,Java的陣列是協變的。也就是說,Carrot[]是Vegetable[]的子類別。而上一節的例子已經表明,協變有時會引發問題。例如下面這段程式碼

Vegetable[] vegetables = new Carrot[10];
vegetables[0] = new Tomato(); // 运行期错误
登入後複製

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

陣列協變性,是Java的著名歷史包袱之一。使用數組時,千萬要小心!

如果把範例中的陣列替換為List,情況就不同了。就像這樣

ArrayList<Vegetable> vegetables = new ArrayList<Carrot>(); // 编译期错误
vegetables.add(new Tomato());
登入後複製

ArrayList是一個泛型類,它是不變的。所以,ArrayList和ArrayList之間並無繼承關係,這段程式碼在編譯期就會報錯。

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

三、當泛型也想要協變、逆變

#泛型是不變的,但某些場景裡我們還是希望它能協變。例如,有一個天天喝蔬菜湯減肥的小姐姐

class Girl {
    public void drink(Soup<Vegetable> soup) {}
}
登入後複製

#

我们希望drink方法可以接受各种不同的蔬菜汤,包括Soup和Soup。但受到不变性的限制,它们无法作为drink的参数。

要实现这一点,应该采用一种类似于协变性的写法

public void drink(Soup<? extends Vegetable> soup) {}
登入後複製

意思是,参数soup的类型是泛型类Soup,而T是Vegetable的子类(也包括Vegetable自己)。这时,小姐姐终于可以愉快地喝上胡萝卜汤和西红柿汤了。

但是,这种方法有一个限制。编译器只知道泛型参数是Vegetable的子类,却不知道它具体是什么。所以,所有非null的泛型类型参数均被视为不安全的。说起来很拗口,其实很简单。直接上代码

public void drink(Soup<? extends Vegetable> soup) {
    soup.add(new Tomato()); // 错误
    soup.add(null); // 正确}
登入後複製

方法内的第一句会在编译期报错。因为编译器只知道add方法的参数是Vegetable的子类,却不知道它具体是Carrot、Tomato、或者其他的什么类型。这时,传递一个具体类型的实例一律被视为不安全的。即使soup真的是Soup类型也不行,因为soup的具体类型信息是在运行期才能知道的,编译期并不知道。

但是方法内的第二句是正确的。因为参数是null,它可以是任何合法的类型。编译器认为它是安全的。

同样,也有一种类似于逆变的方法

public void drink(Soup<? super Vegetable> soup) {}
登入後複製

这时,Soup中的T必须是Vegetable的父类。

这种情况就不存在上面的限制了,下面的代码毫无问题

public void drink(Soup<? super Vegetable> soup) {
    soup.add(new Tomato());
}
登入後複製

Tomato是Vegetable的子类,自然也是Vegetable父类的子类。所以,编译期就可以确定类型是安全的。

以上是Java數組協變與範型不變性的知識介紹(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
揭秘五種高效率的Java陣列去重方法 揭秘五種高效率的Java陣列去重方法 Dec 23, 2023 pm 02:46 PM

五種高效的Java數組去重方法大揭秘在Java開發過程中,經常會遇到需要對數組進行去重的情況。去重就是將陣列中的重複元素去掉,只保留一個。本文將介紹五種高效率的Java陣列去重方法,並提供具體的程式碼範例。方法一:使用HashSet去重HashSet是一種無序不重複集合,在新增元素時會自動去重。因此,我們可以利用HashSet的特性來進行陣列去重。 public

Java數組添加元素的常用方法 Java數組添加元素的常用方法 Feb 21, 2024 am 11:21 AM

Java陣列新增元素的常用方法,需要具體程式碼範例在Java中,陣列是一種常見的資料結構,可以儲存多個相同類型的元素。在實際開發中,我們經常需要在數組中添加新的元素。本文將介紹Java中陣列新增元素的常用方法,並提供具體的程式碼範例。使用循環建立新數組一個簡單的方法是建立一個新的數組,將舊數組的元素複製到新數組中,並添加新的元素。程式碼範例如下://原始數組i

java數組常用方法有哪些 java數組常用方法有哪些 Jan 02, 2024 pm 04:49 PM

常用方法有length屬性、複製陣列、陣列遍歷、陣列排序、陣列轉換為字串等。詳細介紹:1、length屬性:用來取得陣列的長度,它是一個屬性而不是方法。範例:int[] arr = {1, 2, 3}; int length = arr.length;;2、複製陣列:使用System.arraycopy()方法或Arrays類別的copyOf()方法來複製陣列的內容到新數組等等

五種經典的Java數組去重演算法詳解 五種經典的Java數組去重演算法詳解 Dec 23, 2023 am 10:01 AM

五種經典的Java陣列去重演算法詳解在Java程式設計中,經常會遇到需要對陣列進行去重操作的情況,即移除陣列中的重複元素,保留唯一的元素。以下將介紹五種經典的Java數組去重演算法,並提供對應的程式碼範例。使用HashSetHashSet是Java中的一個集合類,它會自動移除重複元素,利用這個特性可以快速實現陣列去重。程式碼範例:importjava.util.Arr

Java中的ArrayIndexOutOfBoundsException異常的解決方法 Java中的ArrayIndexOutOfBoundsException異常的解決方法 Jun 25, 2023 am 11:02 AM

Java是一種廣泛使用的程式語言,它為程式設計師提供了許多實用且強大的工具和功能。在編寫Java程式時,可能會遭遇到各種各樣的異常。其中,ArrayIndexOutOfBoundsException異常是一種常見的例外。當我們在嘗試存取數組中不存在的某個元素時,就會觸發這個異常。在本文中,我們將詳細討論Java中的ArrayIndexOutOfBoundsExc

如何在Java中使用陣列和集合進行資料儲存和操作 如何在Java中使用陣列和集合進行資料儲存和操作 Oct 18, 2023 am 08:15 AM

如何在Java中使用陣列和集合進行資料儲存和操作在Java程式設計中,陣列和集合是常用的資料儲存和操作方式。數組是一種用於儲存相同類型的資料的容器,而集合則是由多個元素組成的物件。使用陣列進行資料儲存和操作的基本方法如下:宣告數組變數要使用數組,首先需要宣告一個數組變數。可以使用下列語法宣告一個陣列變數:dataType[]arrayName;其中,dataT

深入解析Java數組去重的五種實用方法 深入解析Java數組去重的五種實用方法 Dec 23, 2023 am 09:21 AM

深入解析Java數組去重的五種實用方法在Java中,處理數組是非常常見的操作。而數組去重是實際開發中常遇到的問題。本文將深入解析Java數組去重的五種實用方法,並提供具體的程式碼範例。一、使用HashSet去重HashSet是Java中的一種集合,它具有自動去重的功能。我們可以利用HashSet的特性,將陣列中的元素加入HashSet中,實現去重的效果。

在Java中向數組添加元素時需要注意的技巧和事項 在Java中向數組添加元素時需要注意的技巧和事項 Jan 03, 2024 pm 02:01 PM

Java中數組添加元素的技巧和注意事項在Java中,數組是一種非常常見且重要的資料結構。它可以儲存一組相同類型的元素,並且可以透過索引存取和修改這些元素。在實際應用中,我們經常需要在陣列中動態地新增元素。本文將介紹一些Java中數組添加元素的技巧和注意事項,並提供相應的程式碼範例。使用動態數組(ArrayList)來新增元素動態數組ArrayList是

See all articles