泛型

2.簡單泛型

-********
Java泛型的核心概念:告訴編譯器想使用什么類型, 然后編譯器幫你處理一切細節

2.1 一個元組類庫

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

2.2 一個堆棧類

末端哨兵 end sentinel來判斷堆棧何時為空

2.3 RandomList

構建一個可以應用于各種類型的對象的List工具

3.泛型接口

泛型也可以用于接口, 例如生成器, 這是一種專門負責創建對象的類. 是工廠方法設計模式的一種應用.
這里寫了一個Generator類, 用泛型來生成不同類型的對象.

4. 泛型方法

4.1杠桿利用類型參數判斷

類型參數推斷避免了重復的泛型參數列表.

Map<Person,List<? extends Pet>> petpeople = New.map()
這樣就在后面避免了重復寫參數類型, 但是這樣只對賦值操作有效

4.2 可變參數與泛型方法

泛型方法和可變參數列表可以很好地共存.

4.3 用于Generator的泛型方法

這里利用了之前的Generator生成器來填充一個List.通過泛型化操作可以填充不同的類型.

4.4 一個通用的Generator

這里寫了一個可以為任何一個具有默認構造器的類構造Generator的方法.
生成的BasicGenerator中的next()方法可以產生一個該類的對象.

4.5 簡化元組的使用

4.6 一個Set實用工具

介紹了并集,交集等的set方法.

5. 匿名內部類

泛型還可以用于內部類以及匿名內部類.

6. 構建復雜模型

泛型的好處就是能夠簡單而安全的創建復雜的模型.

7. 擦除的神秘之處

ArrayList<Integer>().getClass()==ArrayList<String>().getClass()
這里兩者是相同的類型, 因為在代碼內部,我們無法獲得任何有關泛型參數類型的信息.
Java泛型是用擦除來實現的, 這意味著你在使用泛型的時候, 任何具體的類型信息都被擦除了.你唯一知道的就是你在使用一個對象.

7.1 C++的方式

C++中, 當模板被實例化時, 模板代碼就知道其參數類型.
由于有擦除,Java無法知曉參數的類型.為了調用某些方法, 我們必須給定泛型類的邊界,以告知編譯器只能接受遵循這個邊界的類型, 通過extends關鍵字. List<T extends Haf>
泛型類型參數將擦除到他的第一個邊界.

7.2 遷移兼容性

如果泛型在Java1.0中就是一部分了,那么這個特性將不會使用擦除來實現, 它將使用具體化, 使類型參數保持為第一類實體, 因此就能夠在類型參數上執行基于類型的語言操作和反射操作.
在基于擦除的實現中, 泛型類型被當做第二類類型處理.即不能在某些重要的上下文環境中使用的類型.
泛型類型只有在靜態類型檢查期間才出現.在此之后, 程序中的所有泛型類型都將被擦除替換為他們的非泛型上界. 比如List<Integer> 被擦除為List.
擦除的核心動機是它使得泛化的客戶端可以用非泛化的類庫來使用.這被稱為遷移兼容性.
所以Java類型不僅必須支持向后兼容性, 而且還要支持遷移兼容性, 使得類庫按照他們自己的步調變為泛型的. 所以擦除是唯一可行的解決方案, 允許非泛型代碼與泛型代碼共存.

7.3 擦除的問題

擦除的代價是顯著的. 泛型不能顯式的引用運行時類型的操作之中,比如轉型, instanceOf操作和new表達式. 因為所有有關參數的類型信息都丟失了.

7.4 邊界處的動作

在泛型中創建數組:Array.newInstance()
泛型最令人困惑的方面是因為他可以表示任何沒有意義的事物.
即使編譯器因為擦除而無法知道任何類型的信息, 但是他仍然可以在編譯期內確保你放置到result中的對象具有T類型, 使其適合ArrayList.
也就是即使擦除在方法或類內部移除了有關實際類型的信息, 編譯期仍舊可以確保在方法或者類中使用的類型的內部一致性.
因為擦除在方法體中移除了類型信息, 所以在運行時的問題就是邊界"即對象進入和離開的地點,這些正是編譯器在編譯期執行類型檢查并插入轉型代碼的地點.

8. 擦除的補償

擦除使得任何在運行時需要知道確切類型信息的操作都無法進行.
但是有時候必須通過引入類型標簽來對擦除進行補償, 這意味著你需要顯示的傳遞你的類型的Class對象, 以便可以在表達式中使用它.

8.1 創建類型實例

創建一個new T()的嘗試將無法實現, 部分原因是因為擦除, 而部分原因是因為編譯器不能驗證T具有默認的構造器.
Java的解決方法是傳遞一個工廠對象, 并使用它來創建新的實例, 通過newInstance()來創建這個類的新對象.

T x = kind.newInstance();
另一個方法是模板方法設計模式

8.2 泛型數組

不能創建泛型數組, 一般的解決方案是在任何想要創建泛型數組的地方都使用ArrayList
成功創建泛型數組唯一方式就是創建一個被擦除類型的新數組,然后對其轉型.

9. 邊界

邊界使得你可以在用于泛型的參數類型上設置限制條件,可以按照自己的邊界類型來調用方法.

10. 通配符

數組的特殊行為: 可以向導出類型的數組賦予基類型的數組引用.

Fruit[] fruit = new Apple[10];
fruit[0] = new Apple();
fruit[1] = new Jonathan();
fruit[0] = new Fruit();//在運行期會報錯
這里將Apple數組賦值給一個Fruit數組引用, 但是數組的實際類型是Apple, 只能在其中放置Apple或者他的子類型. 如果放置其他的類型, 編譯期不會報錯. 但是在運行的時候數組機制知道他處理的是Apple的數組,因此會拋出異常.
這種賦值的異常,在運行期可以發現. 而泛型的主要目標之一是將這種錯誤檢測移到編譯期.

List<Fruit> list = new ArrayList<Apple>();
這是無法編譯的.這里不是向上轉型.

10.1 編譯器有多聰明

add()方法接受一個具有泛型參數類型的參數, 而contains()和indexOf()將接受Object類型的參數.
這意味著將由泛型類的設計者來決定哪些調用是安全的.
為了在類型中使用了通配符的情況下禁止這類調用, 我們需要在參數列表中使用類型參數.
也就是在add,set這樣的方法參數中加上類型參數, 就會變編譯期報錯提示.
如果創建了一個Holder ,不能將其向上轉型為Holder, 但是可以向上轉型為Holder<? extends Fruit>. 然后調用get, 會返回一個Fruit對象, 這是因為設置的邊界. 同時可以轉型為apple :d = (Apple)fruit.get();轉型為其他類型可能會得到轉換異常信息.
但是Set方法不能工作于Apple或者Fruit, 因為Set()的參數是? extends Fruit,這意味著他可以是任何事物, 而編譯器無法驗證任何事物的安全性.
也就是說, 可以get不能set

10.2 逆變

另一種方法是用"超類型通配符", 這里可以聲明通配符是由某個特定類的任何基類來界定的.
方法是指定<? super MyClass>,或者使用類型參數<? super T>

static <T> void writeExact(List<T> list, T item){
list.add(item);
}
static <T> void writeWithWildcard(List<? super T> list, T item){
list.add(item);
}
static List<Fruit> fruit = new ArrayList<Fruit>();
writeExact(fruit ,new Apple());//報錯 類型不匹配
writeWithWildcard(fruit, new Apple());//可以編譯
在writeWithWildcard中, 其參數是List<? super T>,因此這個List將持有從T導出的某種具體類型.這樣就可以將一個T類型的或者從T導出的類型的對象作為參數傳遞進去.

10.3 無界通配符

無界通配符<?> 看起來好像意味著"任何事物", 因此使用無界通配符好像等價于使用原生類型.
他的意思是: 我是想用Java的泛型來編寫這段代碼, 我在這里并不是要使用原生類型, 但是在當前這種情況下, 泛型參數可以持有任何類型.
Map<?,?> map = Map map;
List<?> 表示"具有某種特定類型的非原生List,只是我們不知道那種類型是什么"

10.4 捕獲轉換

f1()中的參數類型是確切的, 沒有通配符或邊界, f2()中參數是一個無界通配符, 同時調用f1().
參數類型在f2()的調用過程中被捕獲, 因此可以在對f1()的調用中被使用.

11. 問題

11.1 任何基本類型都不能作為類型參數

不能創建Array<int>, 解決方法是用過Java基本類型的自動包裝機制, 創建Array<Integer>

11.2 實現參數化接口

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

11.3 轉型和警告

使用帶有泛型類型參數的轉型或instanceOf不會有任何效果,.

11.4 重載

其實和接口相同

11.5 基類劫持了接口

12. 自限定的類型

12.1 古怪的循環泛型CRG

class GenericType<T>{}
public class CuriouslyRecurringGeneric extends
GenericType<CuriouslyRecurringGeneric> {}
它能夠產生使用導出類作為其參數和返回類型的基類, 它還能將導出類型用作其域類型. CRG的本質: 基類用導出類型替代其參數.

12.2 自限定

自限定所做的, 就是要求在繼承關系中, 像下面這樣使用這個類

class A extends SelfBounded<A>{}
這會強制要求將正在定義的類當做參數傳遞給基類
意義: 他可以保證類型參數必須與正在被定義的類相同.
自限定只能強制作用于繼承關系. 如果使用自限定, 就應該了解這個類所用的類型參數將于使用這個參數的類具有相同的基類型.

12.3 參數協變

自限定類型的價值在于他們可以產生協變參數類型--反法參數類型會隨子類而變化
子類重寫父類的方法并且修改返回值的類型為子類.
如果不使用自限定類型, 普通的繼承機制就會介入, 而你將能夠重載.也就是既可以返回子類的對象, 也可以調用父類的方法, 接受其他類型返回其他對象.

13. 動態類型安全

因為可以向JavaSE5之前的代碼傳遞泛型容器, 所以舊式代碼有可能會破壞你的容器.
java.util.Collections中的一組工具會解決這種情況下的類型檢查問題.
chekedList(param1, param2)param1是被檢查的list, param2是希望強制要求的類型.
受檢查的容器在你試圖插入類型不正確的對象時拋出ClassCastException.
而之前的舊List在放入的時候是不會報錯的, 只有在取出的時候才會報錯.

14. 異常

因為擦除的原因, 在編譯期和運行期都不知道異常的確切類型, 所以catch不能捕獲.

15. 混型

基本概念: 混合多個類的能力, 以產生一個可以表示混型中所有類型的類, 它使組裝多個類變得簡單易行.
混型的價值之一是他們可以將特性和行為一致的應用于多個類之上.

15.1 C++中的混型

15.2 與接口混合

常見的方案是用接口來產生混型效果.

15.3 使用裝飾器模式

15.4 與動態代理混合

可以使用動態代理來創建一種比裝飾器更貼近混型模型的機制.
通過使用動態代理, 所產生的類的動態類型將會是已經混入的組合類型

16. 潛在類型機制

潛在類型機制使得你可以橫跨類繼承結構, 調用不屬于某個公共接口的方法.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文大量參考Thinking in java(解析,填充)。 定義:多態算是一種泛化機制,解決了一部分可以應用于多...
    谷歌清潔工閱讀 478評論 0 2
  • 第8章 泛型 通常情況的類和函數,我們只需要使用具體的類型即可:要么是基本類型,要么是自定義的類。但是在集合類的場...
    光劍書架上的書閱讀 2,159評論 6 10
  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 7,939評論 12 51
  • 開發人員在使用泛型的時候,很容易根據自己的直覺而犯一些錯誤。比如一個方法如果接收List作為形式參數,那么如果嘗試...
    時待吾閱讀 1,073評論 0 3
  • 寫過很多故事,寫過很多安慰別人和自己的句子。我就像流星,匆匆劃破你黑暗的夜空,在帶給你歡樂的同時,燃燒了自己。 我...
    流離孤云閱讀 204評論 0 0