java基础知识(七)-- 泛型(Generics )
介紹
用法:???
List list = new ArrayList();// 1 list .add(new Integer(12));// 2 Integer x = (Integer) list .iterator().next();// 3// 第3 行的類型轉(zhuǎn)換有些煩人,為了保證對Integer 類型變量賦值的類型安全,必須進行類型轉(zhuǎn)換。 //當然,因為程序員可能不清楚他們的類型,導(dǎo)致這個類型轉(zhuǎn)換有可能產(chǎn)生一個運行時錯誤。 //而如何把一個list(集合) 中的內(nèi)容限制為一個特定的數(shù)據(jù)類型呢? //這就是generics背后的核心思想。這是上面程序片斷的一個泛型版本:List<Integer> list = new ArrayList<Integer>(); // 1 list.add(new Integer(12)); // 2 Integer x = list.iterator().next(); // 3//注意第1行變量list的類型聲明。 //它指定這不是一個任意的List,而是一個Integer 的List。 //我們說List是一個帶一個類型參數(shù)的泛型接口,我們在創(chuàng)建這個List 對象的時候指定了一個Integer類型參數(shù)是。 //另一個需要注意的是第3行沒了類型轉(zhuǎn)換。??? 現(xiàn)在,我們用第1行的類型參數(shù)取代了第3 行的類型轉(zhuǎn)換。然而,這里還有個很大的不同。編譯器現(xiàn)在能夠在編譯時檢查程序的正確性。當我們說list 被聲明為ist<Integer>類型,這告訴我們無論何時何地使用list 變量,編譯器保證其中的元素的正確的類型。實際結(jié)果是,這可以增加可讀性和穩(wěn)定性,尤其在大型的程序中。
泛型的設(shè)計背景
Java中的泛型是什么 ?
????????所謂泛型,就是允許在定義類、接口時通過一個標識表示類中某個屬性的類型或者是某個方法的返回值及參數(shù)類型。這個類型參數(shù)將在使用時(例如,繼承或?qū)崿F(xiàn)這個接口,用這個類型聲明變量、創(chuàng)建對象時)確定(即傳入實際的類型參數(shù),也稱為類型實參)。
????????集合容器類在設(shè)計階段/聲明階段不能確定這個容器到底實際存的是什么類型的對象,所以在JDK1.5之前只能把元素類型設(shè)計為Object,在集合中存儲對象并在使用前進行類型轉(zhuǎn)換是很不方便。
????????JDK1.5之后使用泛型來解決。這個時候除了元素的類型不確定,其他的部分是確定的,例如關(guān)于這個元素如何保存,如何管理等是確定的,因此此時把元素的類型設(shè)計成一個參數(shù),這個類型參數(shù)叫做泛型。Collection<E>,List<E>,ArrayList<E>這個<E>就是類型參數(shù),即泛型。允許我們在創(chuàng)建集合時再指定集合元素的類型,正如:List<String>,這表明該List只能保存字符串類型的對象。
????????JDK1.5改寫了集合框架中的全部接口和類,為這些接口、類增加了泛型支持,從而可以在聲明集合變量、創(chuàng)建集合對象時傳入類型實參。
使用泛型的好處是什么?
????????它提供了編譯期的類型安全,確保你只能把正確類型的對象放入集合中,避免了在運行時出現(xiàn)ClassCastException。
????????在集合中沒有泛型時任何類型都能夠加入集合中,類型不安全,讀出來的時候還需要強轉(zhuǎn)。
????????在集合中有泛型時只有指定類型才能添加到集合中,類型是安全的,讀出來的時候不需要強轉(zhuǎn),很便捷。
自定義泛型結(jié)構(gòu)
泛型的聲明
Interface List<T> 和class GenTest<K,V> //其中,T,K,V,E不代表值,而是表示類型。這里使用任意字母都可以。 //常用T表示,是Type的縮寫。泛型的實例化:
????????一定要在類名后面指定類型參數(shù)的值(類型)。如:???
List<String> strList= new ArrayList<String>(); Iterator<Customer> iterator = customers.iterator();????????T只能是類,不能用基本數(shù)據(jù)類型填充。但可以使用包裝類填充,把一個集合中的內(nèi)容限制為一個特定的數(shù)據(jù)類型,這就是generics背后的核心思想
? ? ? ?使用泛型的主要優(yōu)點是能夠在編譯時而不是在運行時檢測錯誤。
自定義泛型類, 泛型接口
????????泛型類可能有多個參數(shù),此時應(yīng)將多個參數(shù)一起放在尖括號內(nèi)。比如:<E1,E2,E3>
????????實例化后,操作原來泛型位置的結(jié)構(gòu)必須與指定的泛型類型一致。
????????泛型不同的引用不能相互賦值。泛型如果不指定,將被擦除,泛型對應(yīng)的類型均按照Object處理,但不等價于Object。經(jīng)驗:泛型要使用一路都用。要不用,一路都不要用。
????????如果泛型結(jié)構(gòu)是一個接口或抽象類,則不可創(chuàng)建泛型類的對象。泛型的指定中不能使用基本數(shù)據(jù)類型,可以使用包裝類替換。
????????在類/接口上聲明的泛型,在本類或本接口中即代表某種類型,可以作為非靜態(tài)屬性的類型、非靜態(tài)方法的參數(shù)類型、非靜態(tài)方法的返回值類型。但在靜態(tài)方法中不能使用類的泛型。
????????異常類不能是泛型的
????????父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型類型:
??? ??? 子類不保留父類的泛型:按需實現(xiàn)
??????? ??? 沒有類型擦除
??????? ??? 具體類型
??? ??? 子類保留父類的泛型:泛型子類
??????? ??? 全部保留
??????? ??? 部分保留
子類除了指定或保留父類的泛型,還可以增加自己的泛型
自定義泛型方法
????????方法,也可以被泛型化,不管此時定義在其中的類是不是泛型類。在泛型方法中可以定義泛型參數(shù),此時,參數(shù)的類型就是傳入數(shù)據(jù)的類型。???
/*** 泛型方法的格式* [訪問權(quán)限] <泛型> 返回類型 方法名([泛型標識 參數(shù)名稱])拋出的異常* 泛型方法聲明泛型時也可以指定上限**/public class DAO {public<E> E get(intid, E e) {E result= null;return result;}}泛型和子類繼承
??? 讓我們測試一下我們對泛型的理解。下面的代碼片斷合法么?
List<String> ls = new ArrayList<String>(); //1 List<Object> lo = ls; //2????????第1 行當然合法,但是這個問題處在于第2 行。這產(chǎn)生一個問題:一個String 的List 是一個Object 的List 么?大多數(shù)人的直覺是回答: “當然!”。因為乍看起來String是一種Object,所以List<String>應(yīng)當可以用在需要List<Object>的地方,但是事實并非如此。真這樣做的話會導(dǎo)致編譯錯誤。
??? 好,在看下面的幾行???
lo.add(new Object()); // 3 String s = ls.get(0); // 4: 試圖把Object 賦值給String??? 這里,我們使用lo 指向ls。我們通過lo 來訪問ls,一個String 的list。我們可以插入任意對象進去。結(jié)果是ls 中保存的不再是String。當我們試圖從中取出元素的時候,會得到意外的結(jié)果。java 編譯器當然會阻止這種情況的發(fā)生。第2 行會導(dǎo)致一個編譯錯誤。總之,如果Foo 是Bar 的一個子類型(子類或者子接口),而G 是某種泛型聲明,那么G<Foo>是G<Bar>的子類型并不成立!!
????????如果你再深一步考慮,你會發(fā)現(xiàn)Java這樣做是有意義的,因為List<Object>可以存儲任何類型的對象包括String, Integer等等,而List<String>卻只能用來存儲Strings。
通配符(Wildcards)
????????考慮寫一個例程來打印一個集合(Collection)中的所有元素。下面是在老的語言中你可能寫的代碼:
void printCollection(Collection c) {Iterator i = c.iterator();for (int k = 0; k < c.size(); k++) {System.out.println(i.next());} }下面是一個使用泛型的幼稚的嘗試(使用了新的循環(huán)語法):void printCollection(Collection<Object> c) {for (Object e : c) {System.out.println(e);} }??????????一個集合,它的元素類型可以匹配任何類型。顯然,它被稱為通配符。我們可以寫:
void printCollection(Collection<?> c) {for (Object e : c) {System.out.println(e);} }????????現(xiàn)在,我們可以使用任何類型的collection 來調(diào)用它。注意,我們仍然可以讀取c 中的元素,其類型是Object。這永遠是安全的,因為不管collection 的真實類型是什么,它包含的都是Object。但是將任意元素加入到其中不是類型安全的:
Collection<?> c = new ArrayList<String>(); c.add(new Object()); // 編譯時錯誤?????????因為我們不知道c 的元素類型,不能添加對象。add 方法有類型參數(shù)E 作為集合的元素類型。我們傳給add 的任何參數(shù)都必須是一個未知類型的子類。因為我們不知道那是什么類型,所以我們無法傳任何東西進去。唯一的例外是null,它是所有類型的成員。另一方面,我們可以調(diào)用get()方法并使用其返回值。返回值是一個未知的類型,但是我們知道,它總是一個Object
有限制的通配符(Bounded Wildcards)
????????限定通配符和非限定通配符。List<? extends T>和List <? super T>這兩個List的聲明都是限定通配符的例子:
????????List<? extends T>可以接受任何繼承自T的類型的List.
????????List<? super T>可以接受任何T的父類構(gòu)成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>????????
泛型方法
????????考慮寫一個方法,它用一個Object 的數(shù)組和一個collection 作為參數(shù),完成把數(shù)組中所有object 放入collection 中的功能。下面是第一次嘗試:
static void fromArrayToCollection(Object[] a, Collection<?> c) {for (Object o : a) {c.add(o); // 編譯期錯誤} }???????把對象放進一個未知類型的集合中。辦法是使用generic methods。就像類型聲明,方法的聲明也可以被泛型化——就是說,帶有一個或者多個類型參數(shù)。
static <T> void fromArrayToCollection(T[] a, Collection<T> c){for (T o : a) {c.add(o); // correct} }我們可以使用任意集合來調(diào)用這個方法,只要其元素的類型是數(shù)組的元素類型的父類。
Object[] oa = new Object[100];Collection<Object> co = new ArrayList<Object>();fromArrayToCollection(oa, co);// T 指ObjectString[] sa = new String[100];Collection<String> cs = new ArrayList<String>();fromArrayToCollection(sa, cs);// T inferred to be StringfromArrayToCollection(sa, co);// T inferred to be ObjectInteger[] ia = new Integer[100];Float[] fa = new Float[100];Number[] na = new Number[100];Collection<Number> cn = new ArrayList<Number>();fromArrayToCollection(ia, cn);// T inferred to be NumberfromArrayToCollection(fa, cn);// T inferred to be NumberfromArrayToCollection(na, cn);// T inferred to be NumberfromArrayToCollection(na, co);// T inferred to be ObjectfromArrayToCollection(na, cs);// compile-time error}static <T> void fromArrayToCollection (T[] a, Collection<T> c) {for (T o : a) {c.add(o); // correct}}????????注意,我們并沒有傳送真實類型參數(shù)(actual type argument)給一個泛型方法。編譯器根據(jù)實參為我們推斷類型參數(shù)的值。它通常推斷出能使調(diào)用類型正確的最明確的類型參數(shù)。
總結(jié)
以上是生活随笔為你收集整理的java基础知识(七)-- 泛型(Generics )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信息学奥赛一本通 1033:计算线段长度
- 下一篇: web字体文件过大优化方案