天天看點

java學習筆記(十)——泛型

10.1 簡單泛型

使用泛型最重要的原因就是為了創造容器類。

有些情況下,我們希望容器能夠同時持有多種類型的對象。通常我們隻會使用容器來存儲一種類型的對象。泛型的主要目的之一就是用來指定容器要持有什麼類型的對象,而且由編譯器來保證類型的正确性。

與其使用Object,更想暫時不指定類型,而且稍後再決定使用什麼類型,要達到這個目的,需要使用類型參數,用尖括号包覆,放在類名後面。然後在使用這個類的時候,再用實際的類型替換此類型參數。

10.1.1 一個元組類庫

我們經常需要僅一次方法就能傳回多個對象。但return語句隻允許傳回單個對象。是以,需要建立一個對象來持有要傳回的多個對象。每次需要的時候,專門建立一個類來完成這樣的工作。有了泛型就能一次性解決該問題,也能在編譯期就能確定類型安全。

這個概念稱為元組(tuple),它是将一組對象直接打包存儲于其中的一個單一對象。這個容器對象運作讀取其中元素,但是不允許向其中存放新的對象(也稱為資料傳送對象,或信使)。

通常,元組可以具有任意對象,同時元組中的對象可以是任意不同的類型。不過,我們希望能夠為每一個對象指明其類型,并且從容器中讀取出來,能夠得到正确的類型。要處理不同長度的問題,我們需要建立多個不同的元組。

10.2 泛型接口

泛型也可以應用于接口。例如生成器(generator),這是一種專門負責建立對象的類。實際上,這是工廠方法設計模式的一種應用。不過,當使用生成器建立新的對象時,它不需要任何參數,而工廠方法一般需要參數。也就是生成器無需額外資訊就知道如何建立新對象。

10.3 泛型方法

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

泛型方法使得該方法能夠獨立于類而産生變化。無論何時,應盡量使用泛型方法。如果使用泛型方法可以取代将整個類泛型化,那麼就應該隻使用泛型方法,因為它可以使事情更清楚明白。對于一個static的方法而言,無法通路泛型類的類型參數,是以,如果static方法需要使用泛型能力,就必須使其成為泛型方法。

10.4 邊界

邊界使得你可以在用于泛型的參數類型上設定限制條件。盡管這使得你可以強制規定泛型可以應用的類型,但是其潛在的一個更重要的效果是你可以按自己的邊界類型來調用方式。

因為擦除移除了類型資訊,是以,可以用無界泛型參數調用的方法隻是那些可以用Object調用的方法。但是,如果能夠将這個參數限制為某個類型子集,那麼就可以用這些類型子集來調用方法。為了執行這種限制,Java泛型重用了extends關鍵字。需要了解extends關鍵字在泛型邊界上下文環境中和普通環境中所具有的意義是完全不同的。

10.5 問題

10.5.1 基本類型不能作為類型參數

Java泛型的限制之一是,不能将基本類型用作類型參數。是以,不能建立ArrayList<int>之類的東西。

解決方法是使用基本類型的包裝類以及自動包裝機制。如果建立一個ArrayList<Integer>,并将基本類型int應用于這個容器,那麼你将發現自動包裝機制将自動實作int到Integer的雙向轉換。

10.5.2 實作參數化接口

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

10.5.3 轉型和警告

使用帶有泛型類型參數的轉型或instanceof不會有任何效果。

10.6 動态類型安全

因為可以向之前的代碼傳遞泛型容器,是以舊式代碼仍舊有可能破壞你的容器,java.util.Collections中有一組便利工具,可以解決在這種情況下的類型檢查問題,它們是:靜态方法checkedCollection(),checkedList(),checkedMap(),checkedSet(),checkedSortedMap()和checkedSortedSet()。這些方法每一個都會将你希望動态檢查的容器當作第一個參數接受,并将你希望強制要求的類型作為第二個參數接受。

受檢查的容器在試圖插入類型不正确的對象時抛出ClassCastException,這與泛型之前的(原生)容器形成了對比,對于後者來說,當你将對象從容器中取出時,才會通知你出現了問題。在後一種情況,就不知道問題在哪裡,如果使用受檢查的容器,就可以發現誰在試圖插入不良對象。

10.7 異常

由于擦除的原因,将泛型應用于異常是非常受限的。catch語句不能捕獲泛型類型的異常,因為在編譯器和運作時都必須知道異常的确切類型。泛型類也不能直接或間接繼承自Throwable(這将進一步阻止你去定義不能捕獲的泛型異常)。

10.8 混型

混型最基本的概念就是混合多個類的能力,以産生一個可以表示混型中所有類型的類。這往往是你最後的手段,它使組裝多個類變得簡單易行。

混型的價值之一是它們可以将特性和行為一緻地應用于多個類之上。如果想在混型中修改某些東西,作為一種意外的好處,這些修改将會應用于混型所應用的所有類型上。混型有點像AOP,而AOP經常被建議用來解決混型問題。

10.8.1 C++中的混型

在C++中,使用多重繼承的最大理由,就是為了使用混型。對應混型來說,更優雅的方式是使用參數化類型,因為混型就是繼承自其類型參數的類。在C++中,可以很容易地建立混型,因為C++能夠記住其模闆參數的類型。

10.8.2 與接口混合

一種更常見的解決方案就是使用接口來産生混型效果。

10.8.3 使用裝飾器模式

混型與裝飾器設計模式關系很近。裝飾器經常用于滿足各種可能的組合,而直接子類化會産生過多的類,是以是不實際的。

裝飾器模式使用分層對象來動态透明地向單個對象中添加責任。裝飾器指定包裝在最初的對象周圍的所有對象都具有相同的基本接口。某些事物是可裝飾的,可以通過将其他類包裝在這個可裝飾對象的四周,來将功能分層。這使得對裝飾器的使用時透明的,無論對象是否被裝飾,你都擁有一個可以向對象發送的公共消息集。裝飾類也可以添加新方法,但這是受限的。

裝飾器是通過使用組合和形式化結構(可裝飾物/裝飾器層次結構)來實作的,而混型是基于繼承的。是以可以将參數化類型的混型當作是一種泛型裝飾器機制,這種機制不需要裝飾器設計模式的繼承結構。

10.8.4 與動态代理混合

可以使用動态代理來建立一種比裝飾器更貼近混型模型的機制。通過使用動态代理,所産生的類的動态類型将會是已經混入的組合型。

10.9 潛在類型機制

當你在編寫或使用隻是持有對象的泛型時,這些代碼将可以工作與任何類型(除了基本類型,盡管自動包裝機制可以克服這一點)。或者說“持有器”泛型能夠聲明:“我不關心你是什麼類型”。代碼不關心它将要作用的類型,及可以真正應用于任何地方,并是以而“泛化”。

當泛型類型上執行操作(調用Object方法之前的操作)時,就會産生問題,因為擦除要求指定可能會用到的泛型類型的邊界,以安全地調用代碼中的泛型對象上的具體方法。這是對泛化的一種明顯的限制,因為必須限制泛型類型,使它們繼承自特定的類,或者實作特定的接口。在某些情況下,最終可能會使用普通類或普通接口,因為限定邊界的泛型可能會和指定類或接口沒用任何差別。

泛型代碼典型地将在泛型類型上調用少量方法,而具有潛在類型機制的語言隻要求實作某個方法子集,而不是某個特定的類或接口,進而放松了這種限制(并且可以産生更加泛化的代碼)。潛在類型機制使得你可以橫跨類繼承結構,調用不屬于某個公共接口的方法。由于不要求具體類型,是以代碼就可以更加泛化。

潛在類型機制是一種代碼組織和複用機制。有了它編寫出的代碼相對于沒有它編寫出的代碼,能夠更容易複用。代碼組織和複用是所有程式設計的基本手段:編寫一次,多次使用,并在一個位置儲存代碼。

兩種支援潛在類型機制的語言執行個體是Python和C++。Python是動态類型語言(事實上所有類型檢查都發生在運作時),而C++是靜态語言(類型檢查發生在編譯期),是以潛在類型機制不要求靜态或動态類型檢查。