Java編程思想12——泛型

“泛型”這個術(shù)語的意思是:"適用于許多許多的類型”。如何做到這一點呢,正是通過解耦類或方法與所使用的類型之間的約束。

1.與C++的比較

Java中的泛型就需要與C++進(jìn)行一番比較,理由有二:首先,了解C++模板的某些方面,有助于你理解泛型的基礎(chǔ)。同時,非常重要的一點是,你可以了解Java泛型的局限是什么,以及為什么會有這些限制。最終的目的是幫助你理解,Java泛型的邊界在哪里。根據(jù)我的經(jīng)驗,理解了邊界所在,你才能成為程序高手。因為只有知道了某個技術(shù)不能做到什么,你才能更好地做到所能做的(部分原因是,不必浪費時間在死胡同里亂轉(zhuǎn))

第二個原因是,在Java社區(qū)中,人們普遍對C++模板有一種誤解,而這種誤解可能會誤導(dǎo)你,令你在理解泛型的意圖時產(chǎn)生偏差。

2.簡單泛型

有許多原因促成了泛型的出現(xiàn),而最引人注目的一個原因,就是為了創(chuàng)造容器類。

泛型的主要目的之一就是用來指定容器要持有什么類型的對象,而且由編譯器來保證類型的正確性。

2.1 一個元組類庫

元組(tuple), 它是將一組對象直接打包存儲于其中的一個單一對象。這個容器對象允許讀取其中元素, 但是不允許向其中存放新的對象。

2.2 一個棧類

2.3 RandomList

3.泛型接口

泛型也可以應(yīng)用于接口。例如生成器(generator),這是一種專門負(fù)責(zé)創(chuàng)建對象的類。實際上,這是工廠方法設(shè)計模式的一種應(yīng)用。不過,當(dāng)使用生成器創(chuàng)建新的對象時,它不需要任何參數(shù),而工廠方法一般需要參數(shù)。也就是說,生成器無需額外的信息就知道如何創(chuàng)建新對象。

Java泛型的一個局限性:基本類型無法作為類型參數(shù)。不過,Java SE5具備了自動打包和自動拆包的功能,可以很方便地在基本類型和其相應(yīng)的包裝器類型之間進(jìn)行轉(zhuǎn)換。

4.泛型方法

同樣可以在類中包含參數(shù)化方法, 而這個方法所在的類可以是泛型類,也可以不是泛型類。也就是說,是否擁有泛型方法,與其所在的類是否是泛型沒有關(guān)系。

一個基本的指導(dǎo)原則:無論何時,只要你能做到,你就應(yīng)該盡址使用泛型方法。也就是說,如果使用泛型方法可以取代將整個類泛型化,那么就應(yīng)該只使用泛型方法,因為它可以使事情更清楚明白。另外,對于一個static的方法而言,無法訪間泛型類的類型參數(shù),所以,如果static方法需要使用泛型能力,就必須使其成為泛型方法。

注意,當(dāng)使用泛型類時,必須在創(chuàng)建對象的時候指定類型參數(shù)的值,而使用泛型方法的時候,通常不必指明參數(shù)類型,因為編譯器會為我們找出具體的類型。這稱為類型參數(shù)推斷(type argument inference)。因此,我們可以像調(diào)用普通方法一樣調(diào)用f(),而且就好像是們被無限次地重載過。

如果調(diào)用f()時傳入基本類型,自動打包機(jī)制就會介入其中,將基本類型的值包裝為對應(yīng)的對象。事實上,泛型方法與自動打包避免了許多以前我們不得不自己編寫出來的代碼。

4.1 杠桿利用類型參數(shù)推斷

類型推斷只對賦值操作有效,其他時候并不起作用。

顯式的類型說明
要顯式地指明類型,必須在點操作符與方法名之間插入尖括號,然后把類型置于尖括號內(nèi)。如果是在定義該方法的類的內(nèi)部,必須在點操作符之前使用this關(guān)鍵字,如果是使用static的方法,必須在點操作符之前加上類名。
當(dāng)然,這種語法抵消了New類為我們帶來的好處(即省去了大證的類型說明),不過,只有在編寫非賦值語旬時,我們才需要這樣的額外說明。

4.2 可變參數(shù)與泛型方法

4.3 用于Generator的泛型方法

利用生成器, 我們可以很方便地填充一個Collection。

4.4 一個通用的Generator

這個類提供了個基本實現(xiàn),用以生成某個類的對象。這個類必需具備兩個特點:

  • (1)它必須聲明為public。(因為BasicGenerator與要處理的類在不同的包中,所以該類必須聲明為 public, 并且不只具有包內(nèi)訪問權(quán)限。)
  • (2)它必須具備默認(rèn)的構(gòu)造器(無參數(shù)的構(gòu)造器)。要創(chuàng)建這樣的BasicGenerator對象,只需調(diào)用create()方法,并傳人想要生成的類型。

4.5 簡化元組的使用

有了類型參數(shù)推斷, 再加上static方法, 我們可以重新編寫之前看到的元組工具, 使其成為更通用的工具類庫。

4.6 一個Set實用工具

用Set來表達(dá)數(shù)學(xué)中的關(guān)系式。通過使用泛型方法,可以很方便地做到這一點,而且可以應(yīng)用于多種類型。

5.匿名內(nèi)部類

泛型還可以應(yīng)用于內(nèi)部類以及匿名內(nèi)部類。

6.構(gòu)建復(fù)雜模型

泛型的一個重要好處是能夠簡單而安全地創(chuàng)建復(fù)雜的模型。

7.擦除的神秘之處

當(dāng)你開始更深入地鉆研泛型時, 會發(fā)現(xiàn)有大景的東西初看起來是沒有意義的。 例如, 盡管 可以聲明ArrayList.class, 但是不能聲明ArrayList<lnteger>.class。

在泛型代碼內(nèi)部, 無法獲得任何有關(guān)泛型未數(shù)類型的信息。

Java泛型是使用擦除來實現(xiàn)的, 這意味若當(dāng)你在使用泛型時, 任何具體的類型信息都被擦除了, 你唯一知道的就是你在使用一個對象。 因此List<String>和List<lnteger>在運行時事實上 是相同的類型。 這兩種形式都被擦除成它們的 “原生” 類型, 即List。理解擦除以及應(yīng)該如何處理它, 是你在學(xué)習(xí)Java泛型時面臨的最大障礙。

7.1 C++的方式

泛型類型參數(shù)將擦除到它的第一個邊界。

不能因此而認(rèn)為<T extends HasF>形式的任何東西而都是有缺陷的。例如,如果某個類有一個 返回T的方法,那么泛型就有所幫助,因為它們之后將返回確切的類型。

7.2 遷移兼容性

為了減少潛在的關(guān)于擦除的混淆,你必須清楚地認(rèn)識到這不是一個語言特性。它是Java的 泛型實現(xiàn)中的一種折中,因為泛型不是Java語言出現(xiàn)時就有的組成部分,所以這種折中是必需的。

泛型在Java中仍舊是有用的,只是不如它們本來設(shè)想的那么有用,而原因就是擦除。

在基于擦除的實現(xiàn)中,泛型類型被當(dāng)作第二類類型處理,即不能在某些重要的上下文環(huán)境中使用的類型。泛型類型只有在靜態(tài)類型檢查期間才出現(xiàn),在此之后,程序中的所有泛型類型都將被擦除,替換為它們的非泛型上界。例如,諸如List<T>這樣的類型注解將被擦除為List,而普通的類型變量在未指定邊界的情況下將被擦除為Object。

擦除的核心動機(jī)是它使得泛化的客戶端可以用非泛化的類庫來使用,反之亦然,這經(jīng)常被稱為 遷移兼容性 。通過允許非泛型代碼與泛型代碼共存,擦除使得這種向著泛型的遷移成為可能。

7.3 擦除的問題

擦除主要的正當(dāng)理由是從非泛化代碼到泛化代碼的轉(zhuǎn)變過程,以及在不破壞現(xiàn)有類庫的情況下,將泛型融人Java語言。

擦除的代價是顯著的。泛型不能用于顯式地引用運行時類型的操作之中,例如轉(zhuǎn)型instanceof操作和new表達(dá)式。因為所有關(guān)于參數(shù)的類型信息都丟失了,無論何時,當(dāng)你在編寫 泛型代碼時,必須時刻提醒自己,你只是看起來好像擁有有關(guān)參數(shù)的類型信息而已。

7.4 邊界處的動作

注意, 對于在泛型中創(chuàng)建數(shù)組, 使用Array.newInstance()是推薦的方式。

即使擦除在方法或類內(nèi)部移除了 有關(guān)實際類型的信息, 編譯器仍舊可以確保在方法或類中使用的類型的內(nèi)部一致性。
因為擦除在方法體中移除了類型信息, 所以在運行時的問題就是邊界:即對象進(jìn)人和離開方法的地點。 這些正是編譯器在編譯期執(zhí)行類型檢查并插人轉(zhuǎn)型代碼的地點。

8.擦除的補(bǔ)償

正如我們看到的,擦除丟失了在泛型代碼中執(zhí)行某些操作的能力。任何在運行時需要知道確切類型信息的操作都將無法工作。

偶爾可以繞過這些問題來編程,但是有時必須通過引人類型標(biāo)簽來對擦除進(jìn)行補(bǔ)償。這意味著你蒂要顯式地傳遞你的類型的Class對象,以便你可以在類型表達(dá)式中使用它。

8.1 創(chuàng)建類型實例

在Erased.java中對創(chuàng)建一個newT()的嘗試將無法實現(xiàn),部分原因是因為擦除,而另一部分原因是因為編譯器不能驗證T具有默認(rèn)(無參)構(gòu)造器。但是在C++中, 這種操作很自然、很直觀,井且很安全(它是在編譯期受到檢查的)。

Java中的解決方案是傳遞一個工廠對象,井使用它來創(chuàng)建新的實例。 最便利的工廣對象就是 Class對象, 因此如果使用類型標(biāo)簽, 那么你就可以使用newInstance()來創(chuàng)建這個類型的新對象。

8.2 泛型數(shù)組

一般的解決方案是在任何想要創(chuàng)建泛型 數(shù)組的地方都使用ArrayList。

數(shù)組將跟蹤它們的實際類型, 而這個類型是在數(shù)組被創(chuàng)建時確定的, 因此,即使gia已經(jīng)被轉(zhuǎn)型為Generic<lnteger>[], 但是這個信息只存在千編譯期(并且如果沒有@Suppress Warnings注解, 你將得到有關(guān)這個轉(zhuǎn)型的警告)。 在運行時, 它仍舊是Objec數(shù)組, 而這將引發(fā)問題。
成功創(chuàng)建泛型數(shù)組的唯一方式就是創(chuàng)建一個被擦除類型的新數(shù)組, 然后對其轉(zhuǎn)型。

最好是在集合內(nèi)部使用Object[],然后當(dāng)你使用數(shù)組元素時,添加一個對T的轉(zhuǎn)型。
沒有任何方式可以推翻底層的數(shù)組類型,它只能是Object[]。在內(nèi)部將array當(dāng)作Object[] 而不是T[]處理的優(yōu)勢是:我們不太可能忘記這個數(shù)組的運行時類型,從而意外地引人缺陷。

類型標(biāo)記Class<T>被傳遞到構(gòu)造器中,以便從擦除中恢復(fù),使得我們可以創(chuàng)建需要的實際類型的數(shù)組,盡管從轉(zhuǎn)型中產(chǎn)生的警告必須用@SuppressWarnings壓制住。一旦我們獲得了實際類型,就可以返回它,并獲得想要的結(jié)果,就像在main()中看到的那樣。該數(shù)組的運行時類型是確切類型T[]。

Neal Gafter (Java SES的領(lǐng)導(dǎo)開發(fā)者之一)在他的博客中指出,在重寫Java類庫時,他十 分懶散,而我們不應(yīng)該像他那樣。Neal還指出,在不破壞現(xiàn)有接口的情況下,他將無法修改某些Java類庫代碼。因此,即使在Java類庫源代碼中出現(xiàn)了某些慣用法,也不能表示這就是正確的解決之道。當(dāng)查看類庫代碼時,你不能認(rèn)為它就是應(yīng)該在自己的代碼中遵循的示例。

9.邊界

邊界使得你可以在用于泛型的參數(shù)類型上設(shè)置限制條件。盡管這使得你可以強(qiáng)制規(guī)定泛型可以應(yīng)用的類型,但是其潛在的一個更重要的效果是你可以按照自己的邊界類型來調(diào)用方法。

為了執(zhí)行這種限制, Java泛型重用了extends關(guān)鍵字。 對你來說有一點很重要,即要理解extends關(guān)鍵字在泛型邊界上下文環(huán)境中和在普通情況下所具有的意義是完全不同的。

10.通配符

數(shù)組對象可以保留有關(guān)它們包含的對象類型的規(guī)則。 就好像數(shù)組對它們持有的對象是有意識的,因此在編譯期檢查和運行時檢查之間,你不能濫用它們。

泛型的主要目標(biāo)之一是將這種錯誤檢測移入到編譯期。

不是向上轉(zhuǎn)型一Apple的List不是Fruit的List。 Apple的List將持有Apple和Apple的子類型,而Fruit的Lis氓持有任何類型的Fruit, 誠然,這包括Apple在內(nèi),但是它不是 一個Apple的List, 它仍舊是Fruit的List。 Apple的List在類型上不等價于Fruit的List, 即使Apple是一種Fruit類型。

真正的問題是我們在談?wù)撊萜鞯念愋停皇侨萜鞒钟械念愋汀Ec數(shù)組不同,泛型沒有內(nèi)建的協(xié)變類型。這是因為數(shù)組在語言中是完全定義的,因此可以內(nèi)建了編譯期和運行時的檢查,但是在使用泛型時,編譯器和運行時系統(tǒng)都不知道你想用類型做些什么,以及應(yīng)該采用什么樣的規(guī)則。

有時你想要在兩個類型之間建立某種類型的向上轉(zhuǎn)型關(guān)系,這正是通配符所允許的。

10.1 編譯器有多聰明

編譯器只關(guān)注傳遞進(jìn)來和要返回的對象類型, 它并不會分析代碼,以查看是否執(zhí)行了任何實際的寫入和讀取操作。

10.2 逆變

還可以走另外一條路,即使用超類型通配符。 這里,可以聲明通配符是由某個特定類的任何基類來界定的,方法是指定<? super MyClass>, 甚至或者使用類型參數(shù): <? super T> (盡管你不能對泛型參數(shù)給出一個超類型邊界,即不能聲明<T super MyCiass>) 。 這使得你可以安全地傳遞一個類型對象到泛型類型中。

超類型邊界放松了在可以向方法傳遞的參數(shù)上所作的限制。

10.3 無界通配符

無界通配符<?>看起來意味著“任何事物”,因此使用無界通配符好像等價于使用原生類型。
實際上,它是在聲明:“我是想用Java的泛型來編寫這段代碼,我在這里并不是要用原生類型,但是在當(dāng)前這種情況下,泛型參數(shù)可以持有任何類型。”

編譯器何時才會關(guān)注原生類型和涉及無界通配符的類型之間的差異呢?
原生Holder將持有任何類型的組合,而Holder<?>將持有具有某種具體類型的同構(gòu)集合。

使用確切類型來替代通配符類型的好處是,可以用泛型參數(shù)來做更多的事,但是使用通配符使得你必須接受范圍更寬的參數(shù)化類型作為參數(shù)。 因此,必須逐個情況地權(quán)衡利弊, 找到更適合你的需求的方法。

10.4 捕獲轉(zhuǎn)換

有一種情況特別需要使用<?>而不是原生類型。 如果向一個使用<?>的方法傳遞原生類型,那么對編譯器來說,可能會推斷出實際的類型參數(shù),使得這個方法可以回轉(zhuǎn)并調(diào)用另一個使用這個確切類型的方法。

捕獲轉(zhuǎn)換,因為未指定的通配符類型被捕獲,并被轉(zhuǎn)換為確切類型。

11.問題

11.1 任何基本類型都不能作為類型參數(shù)

解決之道是使用基本類型的包裝器類以及Java SE5的自動包裝機(jī)制。
如果性能成為了問題, 就需要使用專門適配基本類型的容器版本。

自動包裝機(jī)制不能應(yīng)用于數(shù)組。

11.2 實現(xiàn)參數(shù)化接口

一個類不能實現(xiàn)同一個泛型接口的兩種變體, 由于擦除的原因, 這兩個變體會成為相同的接口。

11.3 轉(zhuǎn)型和警告

使用帶有泛型類型參數(shù)的轉(zhuǎn)型或instanceof不會有任何效果。

11.4 重載

與此不同的是,當(dāng)被擦除的參數(shù)不能產(chǎn)生唯一的參數(shù)列表時,必須提供明顯有區(qū)別的方法名。

11.5 基類劫持了接口

12.自限定的類型

class SetfBounded<T extends SetfBounded<T>>{ 

12.1 古怪的循環(huán)泛型

class GenericType<T> {}

public class CuriouslyRecurringGeneric
  extends GenericType<CuriouslyRecurringGeneric> {}

Java中的泛型關(guān)乎參數(shù)和返回類型,因此它能夠產(chǎn)生使用導(dǎo)出類作為其參數(shù)和返回類型的基類。 它還能將導(dǎo)出類型用作其域類型, 甚至那些將被擦除為Object的類型。

CRG(古怪的循環(huán)泛型)的本質(zhì):基類用導(dǎo)出類替代其參數(shù)。這意味著泛型基類變成了一種其所有導(dǎo)出類的公共功能的模版,但是這些功能對于其所有參數(shù)和返回值,將使用 導(dǎo)出類型。也就是說,在所產(chǎn)生的類中將使用確切類型而不是基類型。

12.2 自限定

自限定將采取額外的步驟, 強(qiáng)制泛型當(dāng)作其自己的邊界參數(shù)來使用。

自限定的參數(shù)有何意義呢?它可以保證類型參數(shù)必須與正在被定義的類相同。

自限定限制只能強(qiáng)制作用于繼承關(guān)系。 如果使用自限定,就應(yīng)該了解這個類所用的類型參數(shù)將與使用這個參數(shù)的類具有相同的基類型。 這會強(qiáng)制要求使用這個類的每個人都要遵循這種形式。

12.3 參數(shù)協(xié)變

自限定類型的價值在于它們可以產(chǎn)生協(xié)變參數(shù)類型——方法參數(shù)類型會隨子類而變化。

自限定泛型事實上將產(chǎn)生確切的導(dǎo)出類型作為其返回值。

在非泛型代碼中參數(shù)類型不能隨子類型發(fā)生變化:set(derived)和set(base)都是合法的,因此DerivedSetter.set()沒有覆蓋OrdinarySetter.set()而是重載了這個方法。

13.動態(tài)類型安全

受檢查的容器在你試圖插人類型不正確的對象時拋出ClassCastExceptioil,這與泛型之前的(原生)容器形成了對比,對于后者來說,當(dāng)你將對象從容器中取出時,才會通知你出現(xiàn)了問題。

14.異常

由于擦除的原因, 將泛型應(yīng)用于異常是非常受限的。 catch語句不能捕獲泛型類型的異常, 因為在編譯期和運行時都必須知道異常的確切類型。 泛型類也不能直接或間接繼承自Throwable (這將進(jìn)一步咀止你去定義不能捕獲的泛型異常)。

但是, 類型參數(shù)可能會在一個方法的throws子句中用到。 這使得你可以編寫隨檢查型異常的類型而發(fā)生變化的泛型代碼。

15.混型(Mixins)

其最基本的概念是混合多個類的能力, 以產(chǎn)生一個可以表示混型中所有類型的類。

混型的價值之一是它們可以將特性和行為一致地應(yīng)用于多個類之上。混型有一點面向方面編程(AOP)的味道。

15.1 C++中的混型

在C++中,使用多重繼承的最大理由,就是為了使用混型。但是,對于混型來說,更有趣更優(yōu)雅的方式是使用參數(shù)化類型,因為混型就是繼承自其類型參數(shù)的類。在C++中,可以很容易的創(chuàng)建混型,因為C++能夠記住其模版參數(shù)的類型。

Java泛型不允許這樣。擦除會忘記基類類型,因此泛型類不能直接繼承自一個泛型參數(shù)。

15.2 與接口混合

Mixin類基本上是在使用代理,因此每個混人類型都要求在Mixin中有一個相應(yīng)的域,而你必須在Mixin中編寫所有必需的方法,將方法調(diào)用轉(zhuǎn)發(fā)給恰當(dāng)?shù)膶ο蟆_@個示例使用了非常簡單的類,但是當(dāng)使用更復(fù)雜的混型時,代碼數(shù)量會急速增加。

15.3 使用裝飾器模式

當(dāng)你觀察混型的使用方式時,就會發(fā)現(xiàn)混型概念好像與裝飾器設(shè)計模式關(guān)系很近。

裝飾器是通過使用組合和形式化結(jié)構(gòu)(可裝飾物I裝飾器層次結(jié)構(gòu))來實現(xiàn)的,而混型是基于繼承的。因此可以將基于參數(shù)化類型的混型當(dāng)作是一種泛型裝飾器機(jī)制,這種機(jī)制不需要裝飾器設(shè)計模式的繼承結(jié)構(gòu)。

15.4 與動態(tài)代理混合

可以使用動態(tài)代理來創(chuàng)建一種比裝飾器更貼近混型模型的機(jī)制。通過使用動態(tài)代理,所產(chǎn)生的類的動態(tài)類型將會是已經(jīng)混入的組合類型。

16.潛在類型機(jī)制(latent typing)

要編寫能夠盡可能廣泛地應(yīng)用的代碼。為了實現(xiàn)這一 點,我們需要各種途徑來放松對我們的代碼將要作用的類型所作的限制,同時不丟失靜態(tài)類型檢查的好處。

Java泛型看起來是向這一方向邁進(jìn)了一步。

當(dāng)要在泛型類型上執(zhí)行操作(即調(diào)用Object方法之前的操作)時, 就會產(chǎn)生問題,因為擦除要求指定可能會用到的泛型類型的邊界,以安全地調(diào)用代碼中的泛型 對象上的具體方法。這是對“泛化”概念的一種明顯的限制,因為必須限制你的泛型類型,使它們繼承自特定的類,或者實現(xiàn)特定的接口。在某些情況下,你最終可能會使用普通類或普通接口,因為限定邊界的泛型可能會和指定類或接口沒有任何區(qū)別。

一種解決方案稱為潛在類型機(jī)制或結(jié)構(gòu)化類型機(jī)制(structural typing),而更古怪的術(shù)語稱為鴨子類型機(jī)制(duck typing),即“如果它走起來像鴨子,并且叫起來也像鴨子,那么你就可以將它當(dāng)作 鴨子對待。“

具有潛在類型機(jī)制的語言只要求實現(xiàn)某個方法子集,而不是某個特定類或接口,從而放松了這種限制(并且可以產(chǎn)生更加泛化的代碼)。

潛在類型機(jī)制是一種代碼組織和復(fù)用機(jī)制。有了它編寫出的代碼相對千沒有它編寫出的代碼,能夠更容易地復(fù)用。代碼組織和復(fù)用是所有計算機(jī)編程的基本手段:編寫一次,多次使用,并在一個位置保存代碼。

兩種支持潛在類型機(jī)制的語言實例是Python和C++。

初看起來, Java的泛型機(jī)制比支持潛在類型機(jī)制的語言更 “缺乏泛化性”。

17.對缺乏潛在類型機(jī)制的補(bǔ)償

盡管Java不支持潛在類型機(jī)制,但是這并不意味若有界泛型代碼不能在不同的類型層次結(jié)構(gòu)之間應(yīng)用。也就是說,我們?nèi)耘f可以創(chuàng)建真正的泛型代碼,但是這需要付出一些額外的努力。

17.1 反射

17.2 將一個方法應(yīng)用于序列

反射提供了一些有趣的可能性,但是它將所有的類型檢查都轉(zhuǎn)移到了運行時,因此在許多情況下并不是我們所希望的。如果能夠?qū)崿F(xiàn)編譯期類型檢查,這通常會更符合要求。

17.3 當(dāng)你并未碰巧擁有正確的接口時

17.4 用適配器仿真潛在類型機(jī)制

潛在類型機(jī)制將在這里實現(xiàn)什么?它意味著你可以編寫代碼聲明: 我不關(guān)心我在這里使用的類型, 只要它具有這些方法即可。

從我們擁有的接口中編寫代碼來產(chǎn)生我們需要的接口,這是適配器設(shè)計模式的一個典型示例。 我們可以使用適配器來適配已有的接口,以產(chǎn)生想要的接口。

用像這樣的適配器看起來是對缺乏潛在類型機(jī)制的一種補(bǔ)償,因此允許編寫出真正的泛化代碼。 但是,這是一個額外的步驟,并且是類庫的創(chuàng)建者和消費者都必須理解的事物, 而缺乏經(jīng)驗的程序員可能還沒有能夠掌握這個概念。 潛在類型機(jī)制通過移除這個額外的步驟,使得泛化代碼更容易應(yīng)用,這就是它的價值所在。

18.將函數(shù)對象用作策略

策略設(shè)計模式,這種設(shè)計模式可以產(chǎn)生更優(yōu)雅的代碼,因為它將 “變化的事物” 完全隔離到了一個函數(shù)對象中。

函數(shù)對象的價值就在于,與普通方法不同,它們可以傳遞出去,并且還可以擁有在多個調(diào)用之間持久化的狀態(tài)。 當(dāng)然,可以用類中的任何方法來實現(xiàn)與此相似的操作,但是(與使用任何設(shè)計模式一樣)函數(shù)對象主要是由其目的來區(qū)別的。 這里的目的就是要創(chuàng)建某種事物,使它的行為就像是一個可以傳遞出去的單個方法一樣,這樣,它就和策略設(shè)計模式緊耦合了,有時甚至無法區(qū)分。

在C++中,潛在類型機(jī)制將在你調(diào)用函數(shù)時負(fù)責(zé)協(xié)調(diào)各個操作,但是在Java中,我們需要編寫函數(shù)對象來將泛型方法適配為我們特定的需求。

19.總結(jié):轉(zhuǎn)型真的如此之糟嗎?

使用泛型類型機(jī)制的最吸引人的地方,就是在使用容器類的地方。

在Java SE5之前,當(dāng)你將一個對象放置到容器中時,這個對象就會被向上轉(zhuǎn)型為Object, 因此你會丟失類型倌息。當(dāng)你想要將這個對象從容器中取回,用它去執(zhí)行某些操作時,必須將其向下轉(zhuǎn)型回正確的類型。如果沒有Java SE5的泛型版本的容器,你放到容器里的和從容器中取回的,都是Object。因此,我們很可能會將一個Dog放置到Cat的List中。

但是,泛型出現(xiàn)之前的Java并不會讓你誤用放入到容器中的對象。如果將一個Dog扔到Cat 的容器中,井且試圖將這個容器中的所有東西都當(dāng)作Cat處理,那么當(dāng)你從這個Cat容器中取回那個Dog引用,并試圖將其轉(zhuǎn)型為Cat時,就會得到一個RuntlmcExceptlon。你仍舊可以發(fā)現(xiàn)問 題,但是是在運行時而非編譯期發(fā)現(xiàn)它的。

類型安全的容器是能夠創(chuàng)建更通用代碼這一能力所帶來的副作用。

泛型正如其名稱所暗示的:它是一種方法,通過它可以編寫出更 “泛化” 的代碼,這些代碼對于它們能 夠作用的類型具有更少的限制,因此單個的代碼段可以應(yīng)用到更多的類型上。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內(nèi)容

  • ??在Effective中講到泛型之處提到了一個概念,類型擦除器,這是什么呢?接下來我們跟隨這篇文章探索類型擦除的...
    凌云_00閱讀 2,150評論 0 8
  • 第15章 泛型 一般的類和方法,只能使用具體的類型;要么是基本類型,要么是自定義的類。如果要編寫可以應(yīng)用于多種類型...
    智勇雙全的小六閱讀 264評論 0 0
  • 歲月無聲 記不得自己是誰 日子不是白就是黑 堅強(qiáng)的太久好疲憊 看見青青的草原藍(lán)鳥在飛 兇猛的風(fēng)吹進(jìn)海的胸 好想回到...
    林中風(fēng)兒閱讀 338評論 4 15
  • 寫了一段時間的晨讀感悟以來,有小伙伴私聊我說:你這都是紙上談兵啊,都沒看到你實際得到好處。我回答他:子非我,安知我...
    小貓吥吥妞閱讀 241評論 4 10
  • mc:組合里面忙內(nèi)一般都會忍受很多,所以我們現(xiàn)在這個時間來做平語時間游戲吧~為了忙內(nèi)~ 雜~隊里的忙內(nèi)是哪個? 賴...
    耶錦行閱讀 587評論 0 0