本篇文章給大家?guī)淼膬?nèi)容是介紹深入理解什么是Java泛型,?泛型怎么使用,?有一定的參考價值,有需要的朋友可以參考一下,,希望對你們有所助,。 一、什么是泛型 “泛型” 意味著編寫的代碼可以被不同類型的對象所重用,。泛型的提出是為了編寫重用性更好的代碼,。泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù),。 比如常見的集合類 LinkedList:
可以看到,,LinkedList<E> 類名及其實現(xiàn)的接口名后有個特殊的部分<E>,而且它的成員的類型 Link<E> 也包含一個<E>,這個符號的就是類型參數(shù),,它使得在運行中,,創(chuàng)建一個 LinkedList 時可以傳入不同的類型。 二,、為什么引入泛型 在引入泛型之前,,要想實現(xiàn)一個通用的、可以處理不同類型的方法,,你需要使用 Object 作為屬性和方法參數(shù),,比如這樣:
它使用一個 Object 數(shù)組來保存數(shù)據(jù),這樣在使用時可以添加不同類型的對象:
Object 是所有類的父類,,所有的類都可以作為成員被添加到上述類中,;當(dāng)需要使用的時候,必須進(jìn)行強制轉(zhuǎn)換,,而且這個強轉(zhuǎn)很有可能出現(xiàn)轉(zhuǎn)換異常:
第二行代碼將一個 Integer 強轉(zhuǎn)成 String,,運行時會報錯 : 可以看到,使用 Object 來實現(xiàn)通用,、不同類型的處理,,有這么兩個缺點:
根據(jù)《Java 編程思想》中的描述,,泛型出現(xiàn)的動機在于: 有許多原因促成了泛型的出現(xiàn),而最引人注意的一個原因,,就是為了創(chuàng)建容器類,。 在 JDK 1.5 出現(xiàn)泛型以后,許多集合類都使用泛型來保存不同類型的元素,,比如 Collection:
實際上引入泛型的主要目標(biāo)有以下幾點: 類型安全
消除強制類型轉(zhuǎn)換
潛在的性能收益
三,、泛型的使用方式 泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù),。 類型參數(shù)的意義是告訴編譯器這個集合中要存放實例的類型,,從而在添加其他類型時做出提示,在編譯時就為類型安全做了保證,。 參數(shù)類型可以用在類,、接口和方法的創(chuàng)建中,,分別稱為泛型類、泛型接口,、泛型方法,。
泛型類 泛型類和普通類的區(qū)別就是類名后有類型參數(shù)列表 <E>,既然叫“列表”了,,當(dāng)然這里的類型參數(shù)可以有多個,,比如 public class HashMap<K, V>,參數(shù)名稱由開發(fā)者決定,。 類名中聲明參數(shù)類型后,,內(nèi)部成員、方法就可以使用這個參數(shù)類型,,比如上面的 GenericClass<F> 就是一個泛型類,,它在類名后聲明了類型 F,它的成員,、方法就可以使用 F 表示成員類型,、方法參數(shù)/返回值都是 F 類型。 泛型類最常見的用途就是作為容納不同類型數(shù)據(jù)的容器類,,比如 Java 集合容器類,。 泛型接口 和泛型類一樣,泛型接口在接口名后添加類型參數(shù),,比如以下 GenericInterface<T>,,接口聲明類型后,接口方法就可以直接使用這個類型,。 實現(xiàn)類在實現(xiàn)泛型接口時需要指明具體的參數(shù)類型,,不然默認(rèn)類型是 Object,這就失去了泛型接口的意義,。 未指明類型的實現(xiàn)類,,默認(rèn)是 Object 類型:
指明了類型的實現(xiàn):
泛型接口比較實用的使用場景就是用作策略模式的公共策略, Comparator就是一個泛型接口:
泛型接口定義基本的規(guī)則,,然后作為引用傳遞給客戶端,這樣在運行時就能傳入不同的策略實現(xiàn)類,。 泛型方法 泛型方法是指使用泛型的方法,,如果它所在的類是一個泛型類,那就很簡單了,,直接使用類聲明的參數(shù),。 如果一個方法所在的類不是泛型類,或者他想要處理不同于泛型類聲明類型的數(shù)據(jù),,那它就需要自己聲明類型,。
四,、泛型的通配符 通配符:傳入的類型有一個指定的范圍,,從而可以進(jìn)行一些特定的操作 泛型中有三種通配符形式: 1.<?>無限制通配符 2.<? extends E> extends 關(guān)鍵字聲明了類型的上界,表示參數(shù)化的類型可能是所指定的類型,,或者是此類型的子類,。 3.<? super E> super 關(guān)鍵字聲明了類型的下界,表示參數(shù)化類型可能是指定類型,,或者是此類型的父類,。 無限制通配符 < ?> 要使用泛型,但是不確定或者不關(guān)心實際要操作的類型,,可以使用無限制通配符(尖括號里一個問號,,即 <?> ),表示可以持有任何類型,。 ? 和 Object 不一樣,,List<?> 表示未知類型的列表,而 List<Object> 表示任意類型的列表,。 如傳入個 List<String> ,,這時 List 的元素類型就是 String,想要往 List 里添加一個 Object,,這當(dāng)然是不可以的,。 上界通配符 < ? extends E> 在類型參數(shù)中使用 extends 表示這個泛型中的參數(shù)必須是 E 或者 E 的子類,這樣有兩個好處:
下界通配符 < ? super E> 在類型參數(shù)中使用 super 表示這個泛型中的參數(shù)必須是 E 或者 E 的父類。
上面的 dst 類型 “大于等于” src 的類型,,這里的“大于等于”是指 dst 表示的范圍比 src 要大,,因此裝得下 dst 的容器也就能裝 src。 通配符比較 無限制通配符 < ?> 和 Object 有些相似,,用于表示無限制或者不確定范圍的場景,。 < ? super E> 用于靈活寫入或比較,使得對象可以寫入父類型的容器,,使得父類型的比較方法可以應(yīng)用于子類對象,。 < ? extends E> 用于靈活讀取,使得方法可以讀取 E 或 E 的任意子類型的容器對象,。 因此使用通配符的基本原則:
小總結(jié)一下:
舉個例子:
1.要進(jìn)行比較,,所以 E 需要是可比較的類,因此需要 extends Comparable<…>(注意這里不要和繼承的 extends 搞混了,,不一樣) 2.Comparable< ? super E> 要對 E 進(jìn)行比較,,即 E 的消費者,所以需要用 super 3.而參數(shù) List< ? extends E> 表示要操作的數(shù)據(jù)是 E 的子類的列表,,指定上限,,這樣容器才夠大 五、泛型的類型擦除 Java 中的泛型和 C++ 中的模板有一個很大的不同:
在 Java 中,泛型是 Java 編譯器的概念,,用泛型編寫的 Java 程序和普通的 Java 程序基本相同,,只是多了一些參數(shù)化的類型同時少了一些類型轉(zhuǎn)換。 實際上泛型程序也是首先被轉(zhuǎn)化成一般的,、不帶泛型的 Java 程序后再進(jìn)行處理的,,編譯器自動完成了從 Generic Java 到普通 Java 的翻譯,Java 虛擬機運行時對泛型基本一無所知,。 當(dāng)編譯器對帶有泛型的java代碼進(jìn)行編譯時,,它會去執(zhí)行類型檢查和類型推斷,然后生成普通的不帶泛型的字節(jié)碼,,這種普通的字節(jié)碼可以被一般的 Java 虛擬機接收并執(zhí)行,這在就叫做 類型擦除(type erasure),。 實際上無論你是否使用泛型,,集合框架中存放對象的數(shù)據(jù)類型都是 Object,,這一點不僅僅從源碼中可以看到,通過反射也可以看到,。
上面代碼輸出結(jié)果并不是預(yù)期的false,而是true,。其原因就是泛型的擦除。 六,、擦除的實現(xiàn)原理 一直有個疑問,,Java 編譯器在編譯期間擦除了泛型的信息,那運行中怎么保證添加,、取出的類型就是擦除前聲明的呢,? Java 編輯器會將泛型代碼中的類型完全擦除,使其變成原始類型,。當(dāng)然,,這時的代碼類型和我們想要的還有距離,接著 Java 編譯器會在這些代碼中加入類型轉(zhuǎn)換,,將原始類型轉(zhuǎn)換成想要的類型,。這些操作都是編譯器后臺進(jìn)行,可以保證類型安全,??傊盒途褪且粋€語法糖,它運行時沒有存儲任何類型信息,。 擦除導(dǎo)致的泛型不可變性 泛型中沒有邏輯上的父子關(guān)系,,如 List 并不是 List 的父類。兩者擦除之后都是List,,所以形如下面的代碼,,編譯器會報錯:
泛型的這種情況稱為 不可變性,,與之對應(yīng)的概念是 協(xié)變,、逆變:
Java 中數(shù)組是協(xié)變的,泛型是不可變的,。 擦除的拯救者:邊界 我們知道,,泛型運行時被擦除成原始類型,,這使得很多操作無法進(jìn)行. 如果沒有指明邊界,類型參數(shù)將被擦除為 Object,。 如果我們想要讓參數(shù)保留一個邊界,,可以給參數(shù)設(shè)置一個邊界,泛型參數(shù)將會被擦除到它的第一個邊界(邊界可以有多個),,這樣即使運行時擦除后也會有范圍,。 比如:
上述代碼中, People 的類型參數(shù) T 有兩個邊界,編譯器事實上會把類型參數(shù)替換為它的第一個邊界的類型。 七,、泛型的規(guī)則
泛型的使用場景 當(dāng)類中要操作的引用數(shù)據(jù)類型不確定的時候,,過去使用 Object 來完成擴展,JDK 1.5后推薦使用泛型來完成擴展,,同時保證安全性,。 八、總結(jié) 1.上面說到使用 Object 來達(dá)到復(fù)用,,會失去泛型在安全性和直觀表達(dá)性上的優(yōu)勢,,那為什么 ArrayList 等源碼中的還能看到使用 Object 作為類型? 泛型出現(xiàn)時,,Java 平臺即將進(jìn)入它的第二個十年,,在此之前已經(jīng)存在了大量沒有使用泛型的 Java 代碼。人們認(rèn)為讓這些代碼全部保持合法,,并且能夠與使用泛型的新代碼互用,,非常重要。 這樣都是為了兼容,,新代碼里要使用泛型而不是原始類型,。 2.泛型是通過擦除來實現(xiàn)的。因此泛型只在編譯時強化它的類型信息,,而在運行時丟棄(或者擦除)它的元素類型信息,。擦除使得使用泛型的代碼可以和沒有使用泛型的代碼隨意互用。 3.如果類型參數(shù)在方法聲明中只出現(xiàn)一次,,可以用通配符代替它,。
只出現(xiàn)了一次 類型參數(shù),沒有必要聲明,完全可以用通配符代替:
對比一下,,第二種更加簡單清晰吧,。 4.數(shù)組中不能使用泛型 Array 事實上并不支持泛型,這也是為什么 Joshua Bloch 在 《Effective Java》一書中建議使用 List 來代替 Array,,因為 List 可以提供編譯期的類型安全保證,而 Array 卻不能,。 5.Java 中 List<Object> 和原始類型 List 之間的區(qū)別?
九、補充 靜態(tài)資源不認(rèn)識泛型 接上一個話題,,如果把<T>去掉,,那么:
報錯,T未定義,。但是如果我們再把static去掉:
這并不會有任何問題,。兩相對比下,可以看出static方法并不認(rèn)識泛型,,所以我們要加上一個<T>,,告訴static方法,后面的T是一個泛型,。既然static方法不認(rèn)識泛型,,那我們看一下static變量是否認(rèn)識泛型:
這證明了,static變量也不認(rèn)識泛型,,其實不僅僅是staic方法,、static變量、static塊,,也不認(rèn)識泛型,,可以自己試一下??偨Y(jié)起來就是一句話:靜態(tài)資源不認(rèn)識泛型,。 |
|
來自: jackeyqing > 《面試》