摘要:泛型類在類的申明時指定參數(shù),即構(gòu)成了泛型類。換句話說,泛型類可以看成普通類的工廠。的作用就是指明泛型的具體類型,而類型的變量,可以用來創(chuàng)建泛型類的對象。只有聲明了的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。
什么是泛型?
泛型是JDK 1.5的一項新特性,它的本質(zhì)是參數(shù)化類型(Parameterized Type)的應(yīng)用,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù),在用到的時候在指定具體的類型。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口和泛型方法。
基本術(shù)語介紹
以ArrayList為什么使用泛型?和ArrayList 為例 整個ArrayList 稱為泛型類型 ArrayList 中的E稱為類型變量或者類型形參 整個ArrayList 稱為參數(shù)化的類型 ArrayList 中的Integer稱為類型參數(shù)的實例或者類型實參 ArrayList 中的 念為typeof Integer ArrayList稱為原始類型
泛型使類型(類和接口)在定義類、接口和方法時成為參數(shù),好處在于:
強化類型安全,由于泛型在編譯期進行類型檢查,從而保證類型安全,減少運行期的類型轉(zhuǎn)換異常。
提高代碼復(fù)用,泛型能減少重復(fù)邏輯,編寫更簡潔的代碼。
類型依賴關(guān)系更加明確,接口定義更加優(yōu)好,增強了代碼和文檔的易讀性。
一個簡單的例子
public class Test1 { public static void main(String[] args) { List list = new ArrayList(); list.add("kiwen1"); list.add("kiwen2"); list.add(123); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); System.out.println("name:" + name); } } } //輸出結(jié)果 name:kiwen1 name:kiwen2 Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at DateTest.Test1.main(Test.java:17)
從上面例子可以看出,定義了一個List類型的集合,向其中加入了兩個字符串類型的值和 一個Integer類型的值。此時list默認的類型為Object類型。但這里有兩個問題,在循環(huán)中,一是當(dāng)獲取一個值時必須進行強制類型轉(zhuǎn)換,二是沒有錯誤檢查。由于定義了name為String類型,運行時將Integer轉(zhuǎn)成String會產(chǎn)生錯誤。即編譯階段正常,而運行時會出現(xiàn)“java.lang.ClassCastException”異常。因此,導(dǎo)致此類錯誤編碼過程中不易被發(fā)現(xiàn)。
public class Test2 { public static void main(String[] args) { Listlist = new ArrayList (); list.add("kiwen1"); list.add("kiwen2"); //list.add(123); //提示編譯錯誤 for (int i = 0; i < list.size(); i++) { String name = list.get(i); System.out.println("name:" + name); } } } //輸出結(jié)果 name:kiwen1 name:kiwen2
該段代碼采用泛型寫法后,向list加入一個Integer類型的對象時會出現(xiàn)編譯錯誤,通過List
通過上面的例子可以證明,在編譯之后程序會采取去泛型化的措施。也就是說Java中的泛型,只在編譯階段有效。在編譯過程中,正確檢驗泛型結(jié)果后,會將泛型的相關(guān)信息擦出,并且在對象進入和離開方法的邊界處添加類型檢查和類型轉(zhuǎn)換的方法。也就是說,泛型信息不會進入到運行時階段。泛型類對此總結(jié)成一句話:泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同的基本類型。
在類的申明時指定參數(shù),即構(gòu)成了泛型類。泛型類的類型參數(shù)部分可以有一個或多個類型參數(shù),它們之間用逗號分隔。這些類稱為參數(shù)化類或參數(shù)化類型,因為它們接受一個或多個參數(shù)。
定義一個簡單的泛型類//在實例化泛型類時,必須指定T的具體類型 public class Test{ //在類中聲明的泛型整個類里面都可以用,除了靜態(tài)部分,因為泛型是實例化時聲明的。 //靜態(tài)區(qū)域的代碼在編譯時就已經(jīng)確定,只與類相關(guān) class A { T t; } //類里面的方法或類中再次聲明同名泛型是允許的,并且該泛型會覆蓋掉父類的同名泛型T class B { T t; } //靜態(tài)內(nèi)部類也可以使用泛型,實例化時賦予泛型實際類型 static class C { T t; } public static void main(String[] args) { //報錯,不能使用T泛型,因為泛型T屬于實例不屬于類 // T t = null; } //key這個成員變量的類型為T,T的類型由外部指定 private T key; public Test(T key) { //泛型構(gòu)造方法形參key的類型也為T,T的類型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值類型為T,T的類型由外部指定 return key; } }
在使用泛型的時候如果傳入泛型實參,則會根據(jù)傳入的泛型實參做相應(yīng)的限制,此時泛型才會起到本應(yīng)起到的限制作用,但是,泛型的類型參數(shù)只能是類類型,不能是簡單類型。如果不傳入泛型類型實參的話,在泛型類中使用泛型的方法或成員變量定義的類型可以為任何的類型。換句話說,泛型類可以看成普通類的工廠。
//不傳入泛型類型實參 List list = new List(); list.add(123); list.add("hello"); //傳入的泛型實參 List list如何繼承一個泛型類= new List (); list.add("hello");
如果不傳入具體的類型,則子類也需要指定類型參數(shù),
class Sonextends Test {}
如果傳入具體參數(shù),則子類不需要指定類型參數(shù)
class Son extends Test泛型接口{}
泛型接口與泛型類的定義基本一致
//定義一個泛型接口 public interface Generator如何實現(xiàn)一個泛型接口{ public T next(); }
一個簡單的例子
/** * 傳入泛型實參時: * 定義一個生產(chǎn)器實現(xiàn)這個接口,雖然我們只創(chuàng)建了一個泛型接口Generator泛型通配符* 但是我們可以為T傳入無數(shù)個實參,形成無數(shù)種類型的Generator接口。 * 在實現(xiàn)類實現(xiàn)泛型接口時,如已將泛型類型傳入實參類型,則所有使用泛型的地方都要替換成傳入的實參類型 * 即:Generator ,public T next();中的的T都要替換成傳入的String類型。 */ public class FruitGenerator implements Generator { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
我們知道,Box
為了弄清楚這個問題,我們使用Box這個泛型類繼續(xù)看下面的例子:
package DateTest; public class GenericTest { class Box{ private T data; public Box() { } public Box(T data) { this.data = data; } public T getData() { return data; } public void setData(T data) { this.data = data; } } public static void main(String[] args) { Box name = new Box (99); Box age = new Box (712); getData(name); //The method getData(Box ) in the type GenericTest is //not applicable for the arguments (Box ) getData(age); // 1 } public static void getData(Box data){ System.out.println("data :" + data.getData()); } }
通過提示信息我們可以看到Box
回到上面的例子,如何解決上面的問題?總不能為了定義一個新的方法來處理Generic類型的類,這顯然與java中的多態(tài)理念相違背。因此我們需要一個在邏輯上可以表示同時是Box
我們可以將上面的方法改一下:
public static void getData(Box> data) { System.out.println("data :" + data.getData()); }
類型通配符一般是使用?代替具體的類型實參,注意, 此處的?和Number、String、Integer一樣都是一種實際的類型,可以把?看成所有類型的父類。是一種真實的類型。
可以解決當(dāng)具體類型不確定的時候,這個通配符就是 ? ;當(dāng)操作類型時,不需要使用類型的具體功能時,只使用Object類中的功能。那么可以用 ? 通配符來表未知類型。
泛型無限定通配符無限定通配符使用>的格式,代表未知類型的泛型。 當(dāng)可以使用Object類中提供的功能或當(dāng)代碼獨立于類型參數(shù)來實現(xiàn)方法時,這樣的參數(shù)可以使用任何對象。
public void showKeyValue1(List> list) { for (Object item : list) { System.out.print(item + " "); } }泛型上限通配符
通配符上界使用 extends T>的格式,意思是需要一個T類型或者T類型的子類,一般T類型都是一個具體的類型,例如下面的代碼。
//只能傳入number的子類或者number public void showKeyValue2(List extends Number> list) { for (Number number : list) { System.out.print(number.intValue()+" "); } } //假如傳入String類型,list.add("hello");會提示 //The method add(Number) in the type Listis not applicable for the arguments (String)
無論傳入的是何種類型的集合,我們都可以使用其父類的方法統(tǒng)一處理。
泛型下限通配符通配符下界使用 super T>的格式,意思是需要一個T類型或者T類型的父類,一般T類型都是一個具體的類型,例如下面的代碼。
//只能傳入Integer的父類或者Integer public void showKeyValue3(List super Integer> obj){ System.out.println(obj); } //假如傳入String類型,list.add("hello");會提示 //The method add(Number) in the type List泛型方法is not applicable for the arguments (String)
在java中,泛型類的定義非常簡單,但是泛型方法就比較復(fù)雜了。
尤其是我們見到的大多數(shù)泛型類中的成員方法也都使用了泛型,有的甚至泛型類中也包含著泛型方法,這樣在初學(xué)者中非常容易將泛型方法理解錯了。
泛型類,是在實例化類的時候指明泛型的具體類型;泛型方法,是在調(diào)用方法的時候指明泛型的具體類型 。
定義泛型方法如下
調(diào)用泛型方法如下
定義泛型方法時,必須在返回值前邊加一個,來聲明這是一個泛型方法,持有一個泛型T,然后才可以用泛型T作為方法的返回值。
Class的作用就是指明泛型的具體類型,而Class 類型的變量c,可以用來創(chuàng)建泛型類的對象。
為什么要用變量c來創(chuàng)建對象呢?既然是泛型方法,就代表著我們不知道具體的類型是什么,也不知道構(gòu)造方法如何,因此沒有辦法去new一個對象,但可以利用變量c的newInstance方法去創(chuàng)建對象,也就是利用反射創(chuàng)建對象。
泛型方法要求的參數(shù)是Class類型,而Class.forName()方法的返回值也是Class ,因此可以用Class.forName()作為參數(shù)。其中,forName()方法中的參數(shù)是何種類型,返回的Class 就是何種類型。在本例中,forName()方法中傳入的是User類的完整路徑,因此返回的是Class 類型的對象,因此調(diào)用泛型方法時,變量c的類型就是Class ,因此泛型方法中的泛型T就被指明為User,因此變量obj的類型為User。
/** * 泛型方法的基本介紹 * 說明: * 1)public 與 返回值中間非常重要,可以理解為聲明此方法為泛型方法。 * 2)只有聲明了 的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。 * 3) 表明該方法將使用泛型類型T,此時才可以在方法中使用泛型類型T。 * 4)與泛型類的定義一樣,此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的參數(shù)常用于表示泛型。 */ public class Generic { public T name; public Generic(){} public Generic(T param){ name=param; } public T m(){ return name; } public void m1(E e){ } public T m2(T e){ } }
上面代碼中,m()方法不是泛型方法,m1()和m2()都是泛型方法。
泛型方法與可變參數(shù)public class Test{ @Test public void test () { printMsg("hello1",1,"hello2",2.0,false); print("hello1","hello2", "hello3"); } //普通可變參數(shù)只能適配一種類型 public void print(String ... args) { for(String t : args){ System.out.println(t); } } //泛型的可變參數(shù)可以匹配所有類型的參數(shù)。。有點無敵 public靜態(tài)方法與泛型void printMsg( T... args){ for(T t : args){ System.out.println(t); } } }
如果在類中定義使用泛型的靜態(tài)方法,需要添加額外的泛型聲明(將這個方法定義成泛型方法)。即使靜態(tài)方法要使用泛型類中已經(jīng)聲明過的泛型也不可以。
public class Test{ private static T num;//此時編譯器會提示錯誤信息 public static void test(T t){//此時編譯器會提示錯誤信息: ... //Cannot make a static reference to the non-static type T } }
因為靜態(tài)方法和靜態(tài)變量屬于類所有,而泛型類中的泛型參數(shù)的實例化是在創(chuàng)建泛型類型對象時指定的,所以如果不創(chuàng)建對象,根本無法確定參數(shù)類型。但是靜態(tài)泛型方法是可以使用的,我們前面說過,泛型方法里面的那個類型和泛型類那個類型完全是兩回事。
public class StaticGenerator泛型的限制{ public static void show(T t){ } }
Java泛型不能使用原始類型
使用泛型,原始類型不能作為類型參數(shù)傳遞。例如
Testtest = new Test ();
如果將int原始類型傳遞給Test類,那么編譯器會報錯。為了避免這種情況,需要傳遞Integer對象而不是int原始類型。
Java泛型不能使用實例
類型參數(shù)不能用于在方法中實例化其對象。例如
public staticvoid show(Test test) { //compiler error //Cannot instantiate the type T //T item = new T(); //test.add(item); }
如果需要實現(xiàn)這樣的功能,可以使用反射。
public staticvoid show(Test test, Class clazz) throws InstantiationException, IllegalAccessException{ T item = clazz.newInstance(); // OK test.add(item); System.out.println("Item showed."); }
Java泛型不能使用靜態(tài)域
使用泛型時,類型參數(shù)不允許為靜態(tài)(static)。由于靜態(tài)變量在對象之間共享,因此編譯器無法確定要使用的類型。如果允許靜態(tài)類型參數(shù)。
Java泛型不能轉(zhuǎn)換類型
除非由無界通配符進行參數(shù)化,否則不允許轉(zhuǎn)換為參數(shù)化類型。
TestintegerTest = new Test (); Test numberTest = new Test (); //Compiler Error: Cannot cast from Test to Test ,下面用法是錯誤的 integerTest = (Test )numberTest; //可以使用無界通配符進行轉(zhuǎn)換功能 private static void add(Test> test){ Test integerTest = (Test )test; }
Java泛型instanceof運算符
因為編譯器使用類型擦除,運行時不會跟蹤類型參數(shù),所以在Test
Java泛型不能使用異常
泛型類不允許直接或間接擴展Throwable類。
//The generic class Testmay not subclass java.lang.Throwable class Test extends Exception {} //The generic class Test1 may not subclass java.lang.Throwable class Test1 extends Throwable {}
在一個方法中,不允許捕獲一個類型參數(shù)的實例,但throws子句中允許使用類型參數(shù)。
public staticvoid execute(List jobs) { try { for (J job : jobs){} // compile-time error //Cannot use the type parameter T in a catch block } catch (T e) { // ... } } // class Test { private int t; public void add(int t) throws T { this.t = t; } public int get() { return t; } }
Java泛型不能使用數(shù)組
錯誤代碼
//Cannot create a generic array of TestTest [] arrayOfLists = new Test [2];
因為編譯器使用類型擦除,類型參數(shù)被替換為Object,用戶可以向數(shù)組添加任何類型的對象。但在運行時,代碼將無法拋出ArrayStoreException。
下面使用Sun的一篇文檔的一個例子來說明這個問題:
List[] lsa = new List [10]; // Not really allowed. Object o = lsa; Object[] oa = (Object[]) o; List li = new ArrayList (); li.add(new Integer(3)); oa[1] = li; // Unsound, but passes run time store check String s = lsa[1].get(0); // Run-time error: ClassCastException.
這種情況下,由于JVM泛型的擦除機制,在運行時JVM是不知道泛型信息的,所以可以給oa[1]賦上一個ArrayList而不會出現(xiàn)異常,但是在取出數(shù)據(jù)的時候卻要做一次類型轉(zhuǎn)換,所以就會出現(xiàn)ClassCastException,如果可以進行泛型數(shù)組的聲明,上面說的這種情況在編譯期將不會出現(xiàn)任何的警告和錯誤,只有在運行時才會出錯。而對泛型數(shù)組的聲明進行限制,對于這樣的情況,可以在編譯期提示代碼有類型安全問題,比沒有任何提示要強很多。
下面采用通配符的方式是被允許的:數(shù)組的類型不可以是類型變量,除非是采用通配符的方式,因為對于通配符的方式,最后取出數(shù)據(jù)是要做顯式的類型轉(zhuǎn)換的。
List>[] lsa = new List>[10]; // OK, array of unbounded wildcard type. Object o = lsa; Object[] oa = (Object[]) o; Listli = new ArrayList (); li.add(new Integer(3)); oa[1] = li; // Correct. Integer i = (Integer) lsa[1].get(0); // OK
Java泛型不能重載
一個類不允許有兩個重載方法,可以在類型擦除后使用相同的簽名。
類型擦除Java編譯器應(yīng)用類型擦除。 類型擦除是指編譯器使用實際的類或橋接方法替換泛型參數(shù)的過程。 在類型擦除中,編譯器確保不會創(chuàng)建額外的類,并且沒有運行時開銷。
Java編譯器編譯泛型的步驟:
檢查泛型的類型 ,獲得目標類型
擦除類型變量,并替換為限定類型(T為無限定的類型變量,用Object替換)
調(diào)用相關(guān)函數(shù),并將結(jié)果強制轉(zhuǎn)換為目標類型。
ArrayListarrayString=new ArrayList (); ArrayList arrayInteger=new ArrayList (); System.out.println(arrayString.getClass()==arrayInteger.getClass());
上面代碼輸入結(jié)果為 true,可見通過運行時獲取的類信息是完全一致的,泛型類型被擦除了!
如何擦除:
當(dāng)擦除泛型類型后,留下的就只有原始類型了,例如上面的代碼,原始類型就是ArrayList。擦除類型變量,并替換為限定類型(T為無限定的類型變量,用Object替換),如下所示
擦除之前:
//泛型類型 class Pair{ private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
擦除之后:
//原始類型 class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
因為在Pair
如果要死磕Java泛型內(nèi)部原理,請參考文章泛型的內(nèi)部原理:類型擦除以及類型擦除帶來的問題、Java泛型深入了解
總結(jié)在使用泛型類時,由于 Java 泛型的類型參數(shù)之實際類型在編譯時會被消除,所以無法在運行時得知其類型參數(shù)的類型。雖然傳入了不同的泛型實參,但并沒有真正生成不同的類型,傳入不同泛型實參的泛型類在內(nèi)存上實際只有一個,但在邏輯上,我們可以理解為多個不同的泛型類型。
參考文章
https://blog.csdn.net/s10461/...
https://www.cnblogs.com/lwbqq...
https://www.cnblogs.com/iyang...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/74778.html
摘要:本文是作者自己對中線程的狀態(tài)線程間協(xié)作相關(guān)使用的理解與總結(jié),不對之處,望指出,共勉。當(dāng)中的的數(shù)目而不是已占用的位置數(shù)大于集合番一文通版集合番一文通版垃圾回收機制講得很透徹,深入淺出。 一小時搞明白自定義注解 Annotation(注解)就是 Java 提供了一種元程序中的元素關(guān)聯(lián)任何信息和著任何元數(shù)據(jù)(metadata)的途徑和方法。Annotion(注解) 是一個接口,程序可以通過...
摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當(dāng)初愛情萌芽的模樣…… Java 進階面試問題列表 -...
摘要:引用泛型除了方法因不能使用外部實例參數(shù)外,其他繼承實現(xiàn)成員變量,成員方法,方法返回值等都可使用。因此,生成的字節(jié)碼僅包含普通的類,接口和方法。 為什么要使用泛型程序設(shè)計? 一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義類的對應(yīng)類型;如果要編寫可以應(yīng)用于多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。----摘自原書Ordinary classes and meth...
摘要:知識點總結(jié)泛型知識點總結(jié)泛型泛型泛型就是參數(shù)化類型適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼泛型中的類型在使用時指定泛型歸根到底就是模版優(yōu)點使用泛型時,在實際使用之前類型就已經(jīng)確定了,不需要強制類型轉(zhuǎn)換。 Java知識點總結(jié)(Java泛型) @(Java知識點總結(jié))[Java, Java泛型] [toc] 泛型 泛型就是參數(shù)化類型 適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼 泛型中的類型在使用時指定 泛...
閱讀 3469·2021-09-02 15:41
閱讀 2904·2021-09-02 09:48
閱讀 1457·2019-08-29 13:27
閱讀 1224·2019-08-26 13:37
閱讀 892·2019-08-26 11:56
閱讀 2547·2019-08-26 10:24
閱讀 1726·2019-08-23 18:07
閱讀 2673·2019-08-23 15:16