泛型是JDK 1.5中引入的特性,允許在定義類、接口、方法的時候使用類型參數,聲明的類型參數在使用時用具體的類型替換。從好的方面來看,泛型的引入可以在編譯時刻就發現很多明顯的錯誤。從不好的方面,為了保證與舊有版本的兼容性,Java泛型的實現上存在一些不優雅的地方。
最常見的使用場景是泛型類或者接口:
interfaceTestInterface<T>{}classTestClass<T>{ T data;}
看以看到泛型的好處能節省我們的代碼量,當data的類型變化的時候,我們不需要去寫不同的接口或者類。當然有時候你需要指定多個類型,那麼可以:
classTestClass<K, V, OTHER>{ K key; V value; OTHER other;}
有時候我們希望只支持Number類型,那麼可以:
classTestClass<T extendsNumber>{ T data;}
當然,泛型也可以用在方法上,舉個例子:
public<T> T doSth(T a){return a;}
你可能會比較好奇如果同時在方法和類上面使用泛型的話會出現什麼情況:
publicclassTest<T>{ T data;@SuppressWarnings("hiding")public<T> T doSth(T a){return a;}publicstaticvoid main(String[] args){Test<String> t =newTest<String>();System.out.println(t.doSth(123)); t.data ="123";}}
結論是方法上的用方法的,其他的用類上的,如果方法上沒有,方法用類上的。
現在想一下泛型具體是如何實現的,用javap Test看doSth的方法聲明如下:
public java.lang.Object doSth(java.lang.Object);
如果是受限的泛型,比如:
publicclassTest<T extendsNumber>{public T doSth(T a){return a;}}
那麼得到的結果則是:
public java.lang.Number doSth(java.lang.Number);
如果限制類型有兩個(比如<T extends Comparable & Serializable>)則生成的字節碼中選用第一個(Comparable)。Java中的泛型是偽泛型,在運行期間,所有的泛型信息都會被擦除。也就是說在生成的Java字節碼中沒有包含泛型中的類型信息。那麼在重載的時候會有什麼影響,舉個例子:
publicclassTest{publicvoid doSth(List<Integer> list){}publicvoid doSth(List<String> list){}}
現在應該會猜到:因為類型擦除,這個類是不能被編譯通過的。那麼下面這段代碼呢?
publicclassTest{publicInteger doSth(List<Integer> list){returnnull;}publicString doSth(List<String> list){returnnull;}}
編譯通過了。這貌似與我們之前對重載的認識不相同:函數之間的區分是依據參數和方法名,返回值並不參與。上面這段代碼中常在不是根據返回值來判斷的,但是增加不同類型的返回值是的這兩個方法能夠共存在同一個Class文件中: > 重載要求方法有不同的方法簽名,而返回值並不在方法簽名中。但是在Class文件格式中,只要描述符不是完全一致的方法就可以共存,也就是說:返回值也能影響方法能不能共存在同一個Class中。 可以通過javap -s Test看到方法簽名。
首先來看通過ParameterizedType獲取類型的方法,如下:
publicclassTest{publicList<String> list;publicstaticvoid main(String[] args)throwsException{ParameterizedType pt =(ParameterizedType)Test.class.getField("list").getGenericType();System.out.println(pt.getActualTypeArguments()[0]);}}
其中getGenericType方法返回一個Type對象,如果是一個參數化類型,那麼返回的Type會反映源碼中使用的實際參數類型,實際的參數類型通過getActualTypeArguments獲��。