Java泛型中的子类型化
泛型類型為Java程序引入了新的類型安全范圍。 在同一類型上,泛型類型可以表現(xiàn)得很好,尤其是在使用通配符時(shí) 。 在本文中,我想解釋子類型如何與Java泛型一起工作。
關(guān)于泛型類型子類型化的一般思考
不同泛型類型相同的類或接口的不定義亞型層級線性盡可能通用參數(shù)類型的子類型的層次結(jié)構(gòu)。 例如,這意味著List <Number>不是List <Integer>的超類型。 下面的突出示例很好地說明了為什么禁止這種子類型化:
在進(jìn)一步詳細(xì)討論之前,讓我們首先考慮一下有關(guān)類型的一般信息:類型為程序引入了冗余。 當(dāng)您將變量定義為Number類型時(shí),請確保該變量僅引用知道如何處理Number定義的任何方法(例如Number.doubleValue)的對象。 這樣,您可以確保可以安全地在變量當(dāng)前表示的任何對象上調(diào)用doubleValue,并且不再需要跟蹤變量引用的對象的實(shí)際類型。 (只要引用不為null。null引用實(shí)際上是Java嚴(yán)格類型安全性的少數(shù)例外之一。當(dāng)然,null的“對象”不知道如何處理任何方法調(diào)用。)但是,如果您試圖將String類型的對象分配給此Number類型的變量,Java編譯器將認(rèn)識到該對象實(shí)際上不理解Number所需的方法,并且會引發(fā)錯誤,因?yàn)樗荒鼙WC將來可能會調(diào)用例如doubleValue將被理解。 但是,如果我們?nèi)鄙貸ava中的類型,則程序不會僅憑此更改其功能。 只要我們從不進(jìn)行錯誤的方法調(diào)用,那么沒有類型的Java程序就等效。 從這個(gè)角度來看,類型僅僅是為了防止我們的開發(fā)人員在愚蠢的事情上奪走一點(diǎn)自由。 此外,類型是隱式記錄程序的一種好方法。 (諸如Smalltalk之類的其他編程語言不知道類型,并且除了在大多數(shù)時(shí)候困擾之外,這也有其好處。)
有了這個(gè),讓我們回到泛型。 通過定義通用類型,您可以允許通用類或接口的用戶為其代碼添加某種類型安全性,因?yàn)樗麄兛梢韵拗谱约簝H以某種方式使用您的類或接口。 例如,當(dāng)您通過定義List <Number>將List定義為僅包含Numbers時(shí),建議您每次嘗試將String類型的對象添加到此列表中時(shí),Java編譯器都將引發(fā)錯誤。 在使用Java泛型之前,您只需要相信列表僅包含數(shù)字即可。 當(dāng)您將集合的引用交給第三方代碼中定義的方法或從該代碼接收到集合時(shí),這可能會特別痛苦。 使用泛型,即使在編譯時(shí),您也可以確保List中的所有元素都是某個(gè)超類型。
同時(shí),通過使用泛型,您會在泛型類或接口內(nèi)失去一些類型安全性。 例如,當(dāng)您實(shí)現(xiàn)通用列表時(shí)
class MyList<T> extends ArrayList<T> { }您不知道MyList中T的類型,并且必須期望該類型可以像Object一樣簡單。 這就是為什么您可以限制通用類型要求某些最小類型的原因:
class MyList<T extends Number> extends ArrayList<T> {double sum() { double sum = .0d;for(Number val : this) {sum += val.doubleValue();}return sum;} }這使您可以假定MyList中的任何對象都是Number的子類型。 這樣,您就可以在泛型類中獲得某種類型的安全性。
通配符
Java中的通配符等效于說出任何類型 。 因此,在實(shí)例化類型(即定義泛型類的某些實(shí)例應(yīng)代表哪種具體類型)時(shí),不允許使用通配符。 例如,在將對象實(shí)例化為新的ArrayList <Number>時(shí)發(fā)生類型實(shí)例化,其中您隱式調(diào)用包含在其類定義中的ArrayList的類型構(gòu)造函數(shù)
class ArrayList<T> implements List<T> { ... }ArrayList <T>是帶有單個(gè)參數(shù)的簡單類型構(gòu)造函數(shù)。 因此,在ArrayList的類型構(gòu)造函數(shù)定義(ArrayList <T>)中或在此構(gòu)造函數(shù)的調(diào)用(新ArrayList <Number>)中,都不允許使用通配符。 但是,如果僅引用類型而不實(shí)例化新對象,則可以使用通配符,例如在局部變量中。 因此,允許以下定義:
ArrayList<?> list;通過定義此變量,可以為任何通用類型的ArrayList創(chuàng)建占位符。 但是,由于對通用類型的這種限制很小,因此無法通過此變量對其的引用將對象添加到列表中。 這是因?yàn)槟鷮ψ兞苛斜硭淼姆盒妥龀隽诉@樣的一般假設(shè),即添加一個(gè)類型為String的對象并不安全,因?yàn)槌隽斜淼牧斜砜赡苄枰撤N其他任何子類型的對象。 通常,此必需的類型是未知的,并且不存在任何類型的子類型的對象,可以安全地添加該對象。 (例外是取消了類型檢查的空引用。但是,您永遠(yuǎn)不應(yīng)在集合中添加空值。)同時(shí),從列表中刪除的所有對象都將是對象類型,因?yàn)檫@是關(guān)于a的唯一安全假設(shè)此變量表示的所有可能列表的常見超類型 。 因此,您可以使用extends和super關(guān)鍵字形成更復(fù)雜的通配符:
ArrayList<?> list = new ArrayList<List<?>>();在此示例中,由于不將通配符應(yīng)用于類型實(shí)參,而不應(yīng)用于構(gòu)造的類型本身,因此滿足了不得使用通配符類型構(gòu)造ArrayList的要求。
至于泛型類的子類型化,我們可以總結(jié)一下,如果原始類型是子類型,并且泛型類型都是彼此的子類型,則某些泛型類型是另一種類型的子類型。 因此,我們可以定義
List<? extends Number> list = new ArrayList<Integer>();因?yàn)樵碱愋虯rrayList是List的子類型,并且因?yàn)榉盒虸nteger是?的子類型? 擴(kuò)展Number。
最后,請注意,通配符List <?>是List <?的快捷方式。 擴(kuò)展Object>,因?yàn)檫@是一種常用的類型定義。 但是,如果泛型類型構(gòu)造函數(shù)確實(shí)實(shí)施了另一個(gè)較低的類型邊界,例如
class GenericClass<T extends Number> { }變量GenericClass <?>而是GenericClass <?的快捷方式。 擴(kuò)展Number>。
取放原則
這種觀察將我們引到了“ 獲取-放出”原理 。 另一個(gè)著名的例子可以最好地解釋這一原理:
class CopyClass {<T> void copy(List<T> from, List<T> to) {for(T item : from) to.add(item);} }此方法定義不是很靈活。 如果您有一些列表List <Integer>,則無法將其內(nèi)容復(fù)制到某些List <Number>甚至List <Object>。 因此,“獲取和放置”原則規(guī)定,當(dāng)您僅從通用實(shí)例(通過return參數(shù))讀取對象時(shí),應(yīng)始終使用下限通配符(?extends),而在以下情況下應(yīng)始終使用上限通配符(?super)。您只提供通用實(shí)例方法的參數(shù)。 因此,更好的MyAddRemoveList實(shí)現(xiàn)如下所示:
class CopyClass {<T> void copy(List<? extends T> from, List<? super T> to) {for(T item : from) to.add(item);} }由于您僅從一個(gè)列表中讀取內(nèi)容,然后再寫入另一個(gè)列表中,因此很遺憾,這是很容易被忽略的,您甚至可以在Java核心API中找到不采用“獲取與放置”原理的類。 (請注意,上述方法還描述了泛型類型構(gòu)造函數(shù)。)
請注意,類型List <? 擴(kuò)展T>和List <? 超級T>都沒有List <T>的要求那么具體。 還要注意,這種子類型對于非通用類型已經(jīng)是隱式的。 如果定義的方法要求使用Number類型的方法參數(shù),則可以自動接收任何子類型的實(shí)例,例如Integer。 但是,即使期望超型Number,也始終可以安全地讀取您收到的此Integer對象。 而且由于無法寫回該引用,即您不能用Double的實(shí)例覆蓋Integer對象,因此Java語言不需要通過聲明方法簽名(如void someMethod(<?擴(kuò)展Number> number)。 同樣,當(dāng)您答應(yīng)從方法中返回整數(shù)時(shí),調(diào)用者只需要一個(gè)Number類型的對象,您仍然可以從方法中返回( 寫 )任何子類型。 同樣,由于無法從假設(shè)的返回變量中讀取值,因此在方法簽名中聲明返回類型時(shí),不必通過通配符放棄這些假設(shè)的讀取權(quán)限。
翻譯自: https://www.javacodegeeks.com/2013/12/subtyping-in-java-generics.html
總結(jié)
以上是生活随笔為你收集整理的Java泛型中的子类型化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑病毒的本质(计算机病毒的本质特征是什
- 下一篇: 高分韩国悬疑电影(推荐八部超级好看的韩国