泛型之泛型方法
package com.hoonee.javase.generic;
/**
* 泛型方法
* 首先,泛型的聲明,必須在方法的修飾符(public,static,final,abstract等)之後,返回值聲明之前。
* 然後,和泛型類一樣,可以聲明多個泛型,用逗號隔開。
* @author Hoonee
* @mail [email protected]
*/
public class GenericMethodDemo {
public static <T> Class getClass(T t) {
return t.getClass();
}
public static void main(String[] args) {
System.out.println(GenericMethodDemo.getClass(1));
System.out.println(GenericMethodDemo.getClass("1"));
}
}
一個關於泛型類的問題
/**
* 一個關於泛型類的問題
*
* 這段代碼的輸出爲:Type of T is java.lang.Integer
* 問題是,既然JVM在運行時已經將泛型信息擦除,那麼就應該只知道ob是Object類型的,那麼在創建iObject對象的時候,
* 傳遞了一個Integer類型的參數88,爲什麼在showType()方法中 ob.getClass().getName())方法能夠得到
* Integer的類型信息呢?在showType()方法中通過ob只能調用Object類所具有的方法。(這與能從ob獲取其Integer
* 類型信息是否矛盾呢?)另外,GenClass.class文件經過反編譯後,如下:
*
* class GenClass {
* Object ob;
* GenClass(Object o) {
* ob = o;
* }
* Object getOb() {
* return ob;
* }
* void showType() {
* System.out.println((new StringBuilder("Type of T is ")).append(ob.getClass().getName()).toString());
* }
* }
*
* 疑惑解答:
*
* 你這個問題其實不只是泛型了。
* 第一組關鍵詞:引用,實例化。
* 先看下面這個簡單的例子:
* Object obj=new Integer();
* System.out.println(obj.getClass());
* obj是引用,引用的類型是Object,實例的類型是Integer
* 我們知道,引用是棧保存的一個類似地址的信息,本身沒有任何含義,真正能保存信息的是new Integer()所劃分的內存空間。
* 即是說,Integer這個信息是在實例裏的
*
* 第二組關鍵詞:繼承,父類,子類
* Integer繼承自Object
* obj.getClass()調用的是Integer的getClass方法,當然Integer是沒有該方法,於是一直回溯到父類Object,找到Object的getClass方法,
* 實現是本地native方法,我們不知道內部怎麼構造,但是可以肯定,它是在根據引用地址找到實例位置,確定實例類型。
* 綜合第一組可知,在這裏,實例類型是Integer
*
* 第三才是泛型。其實在第一部裏面已經說明過了。GenClass並沒有維護T的信息。維護T信息的是T ob;
* GenClass<Integer> iObject執行這一部的時候,ob就認爲自己是個Integer類型了。當它具有了實例之後,你才能調用getClass方法
* 所以綜合上兩點才表現出了你看到的效果。對於GenClass來說,一旦運行,它就不在乎T是什麼類型了,但是ob需要知道自己的類型吧?這個Integer被維護在了實例裏。
*
* @author Hoonee
* @mail [email protected]
*/
public class GenDemo {
public static void main(String args[]) {
GenClass<Integer> iObject = new GenClass<Integer>(88);
iObject.showType();
}
}
class GenClass<T> {
T ob;
GenClass(T o) {
this.ob = o;
}
T getOb() {
return this.ob;
}
void showType() {
System.out.println("Type of T is " + ob.getClass().getName());
}
}
只有引用類型才能作爲泛型方法的實際參數,不能是基本類型
除了在應用泛型時可以使用extends限定符,在定義泛型時也可以使用extends限定符
可以用來指定多個邊界,如<V extends Serializable & cloneable> void method(){},必須實現Serializable 和cloneable兩個接口
普通方法、構造方法和靜態方法中都可以使用泛型。
也可以用類型變量表示異常,稱爲參數化的異常,可以用於方法的throws列表中,但是不能用於catch子句中。
例:用下面的代碼說明對異常如何採用泛型:
private static <T extends Exception> sayHello() throws T
{
try{
}catch(Exception e){
throw (T)e;
}
}
在泛型中可以同時有多個類型參數,在定義它們的尖括號中用逗號分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
例:自定義泛型方法
public class GenericsUserDefined{
public static void main(String args[])throws Exception{
Integer x = add(3,5);
Number x1 = add(3.5,5);
//可看成float和int都屬於Number類型,如果Float x1 = add(3.5,5);出錯
Object x2 = add(3,"abc");
//可看成Integer,String都屬Object
}
//自定義泛型方法
private static <T>T add(T x,T y)
//定義一個類型<T>,必須在返回值之前或挨着返回值定義
//聲名了一個類型T,調用和返回的參數 都爲 T
{
return null;
}
}
泛型方法類型參數的類型推斷
編譯器判斷範型方法的實際類型參數的過程稱爲類型推斷,類型推斷是相對於知覺推斷的,其實現方法是一種非常複雜的過程。
根據調用泛型方法時實際傳遞的參數類型或返回值的類型來推斷,具體規則如下:
當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那麼根據調用方法時該處的實際應用類型來確定,這很容易憑着感覺推斷出來,即直接根據調用方法時傳遞的參數類型或返回值來決定泛型參數的類型,例如:
swap(new String[3],3,4) --> static <E> void swap(E[] a, int i, int j)
當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型都對應同一種類型來確定,這很容易憑着感覺推斷出來,例如:
add(3,5) --> static <T> T add(T a, T b)
當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型,且沒有使用返回值,這時候取多個參數中的最大交集類型,例如,下面語句實際對應的類型就是Number了,編譯沒問題,只是運行時出問題:
fill(new Integer[3],3.5f) --> static <T> void fill(T[] a, T v)
當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型, 並且使用返回值,這時候優先考慮返回值的類型,例如,下面語句實際對應的類型就是Integer了,編譯將報告錯誤,將變量x的類型改爲float,對比eclipse報告的錯誤提示,接着再將變量x類型改爲Number,則沒有了錯誤:
int x =(3,3.5f) --> static <T> T add(T a, T b)
參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型爲Object,編譯沒有問題,而第二種情況則根據參數化的Vector類實例將類型變量直接確定爲String類型,編譯將出現問題:
copy(new Integer[5],new String[5]) --> static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]) --> static <T> void copy(Collection<T> a , T[] b);