1.泛型程式設計
泛型程式設計意味著編寫的代碼可以被很多不同類型的對象所重用。例如:不希望為了聚集String和Integer對象分別設計不同的類。(個人覺得此處說的聚集譯為:建立一個對象,屬性可以為String和Integer類型。但是卻有著相同的行為或屬性)
代碼如下:
public class StringTest { private String age; public String getAge() { return age; } public void setAge(String age) { this.age = age; }}
public class IntegerTest { private Integer age; public Integer getAge() { return Age; } public void setAge(Integer age) { this.age= age; }}
可以看到上面兩個類都有size屬性和方法。只是他們的屬性類型有所不同。那麼我們則可以使用到泛型:例
public class GenericTest<T> { private T size; public T getSize() { return size; } public void setSize(T size) { this.size = size; }}
然後只需要如下調用
public static void main(String[] args){ GenericTest<String> genericString= new GenericTest<>(); GenericTest<Integer> genericInteger = new GenericTest<>(); }
這樣我們就很好的解決了代碼重用的問題,不是嗎?看不懂?不急,我只是說了泛型的好處,讓我們慢慢往下瞭解。
2.定義簡單的泛型類
沒接觸過泛型的同學看到上面例子中的T可能會覺得一臉懵逼,其實T只是一個類的類型變數(雖然你可以定義A、B、C...,但是我們還是要規範點的是吧),然後T需要使用角括弧括起來,當你定義多個類型變數時。可以看下面的Generic類,
public class Generic<T,U> { private T size; private U length; ...}
當你傳入Generic<Integer,String> 時,你可以把這個類想象成如下使用
public class Generic { private Integer size; private String length; ...}
所以,泛型類其實可以看做普通的工廠類。
3.泛型方法
上面介紹了一個簡單的泛型類,讓我們來看看泛型方法是怎麼寫吧。
public class ArrayAlg { public static <T> T getMiddle(T... a){ return a[a.length/2]; }}
可以看到,ArrayAlg是一個普通類,並不是一個泛型類。說明:泛型方法可以定義在普通類和泛型類中。
當我們調用getMiddle方法時,在方法名前的角括弧放入具體類型:
String s = ArrayAlg.<String>getMiddle("John","Q","Min");
看起來是不是很彆扭?其實可以省略<String>型別參數,編譯器會使用String[ ]數組與泛型T[ ]進行匹配推斷出一定是String,這個應該就是他們私下bilibili的規則了。變成:
String s = ArrayAlg.getMiddle("John","Q","Min");
有的小夥伴可能會想到,醬紫的話,我能不能瞎比的傳參數進去?例:
Double middle2 = ArrayAlg.getMiddle(3.14,0);
其實當你這樣寫的時候編譯器就已經提示錯誤了,因為第二個參數編譯器會預設認為是Integer類型。解決方案有兩種:
1.把Double改成Number類型,因為Number類型為Double和Integer的父類。
2.自己改代碼去吧,誰讓你瞎搞。
4.類型變數的限定
有時候,類或方法需要對類型變數加以約束,假如,某個泛型方法需要使用到Comparable介面的compareTo方法,那我們則需要限制傳進來的參數必須實現Comparable介面,例:
public static <T extends Comparable> T minmax(T[] a){ compareTo...}
傳進來的參數a則必須實現Comparable介面。假如傳進來參數需要實現Comparable和Serializable兩個介面,則
public static <T extends Comparable&Serializable> Pair<T> minmax(T[] a){ compareTo... }
叮咚!那麼傳進來的參數需要繼承某個類怎麼寫?其實是一樣的。傳進來的參數需要繼承某個類、實現某個介面都是使用extends關鍵字來控制。原因:選擇關鍵字extends的原因是更接近子類的概念,並且Java的設計者也不打算在語言中再添加一個新的關鍵字
5.泛型代碼和虛擬機器
5.1類型擦除
Java 的泛型在編譯器有效,在運行期被刪除,也就是說所有泛型參數類型在編譯後都會被清除掉
例如:
public class GenericClass<T> { private T first; public T getFirst() { return first; } ...}
擦除類型後
public class GenericClass { private Object first; public Object getFirst() { return first; } ...}
可以看到,類型擦除後的GengericClass和沒引用泛型沒有什麼兩樣。而且,當你使用GenericClass<String>後,也是變成原始的GenericClass類型。
但是當我們使用限定類型變數後,類型擦除後將會使用限定類型。例:
public class GenericClass<T extends Comparable> { private T first; ...}
可以看到限定了傳進來的參數需要實現Comparable介面,那麼類型擦除後如下:
public class GenericClass{ private Comparable first; ...}
問題:當你限定了參數需要實現Comparable和Serializable介面,就是GenericClass<T extends Comparable&Serializable>時,類型擦除後會變成怎樣呢?
public class GenericClass{ private Comparable first; ...}
虛擬機器會把首位實現的介面(此處為Comparable),轉為擦除後物件類型。當然,如果你這樣寫GenericClass<T extends Serializable&Comparable> ,那麼擦除後的類型為Serializable。
5.2翻譯泛型運算式
當程式調用泛型方法時,如果擦除傳回型別, 編譯器將插入強制類型轉換。例如List集合源碼:
public interface List<E> extends Collection<E> { ... E get(int index); ...}
當不使用類型變數時,取出來的值是Object,而定義了String類型變數時,編譯器將插入強制類型轉換:
List list1 = new ArrayList();
list1.add("1");list1.get(0); //ObjectList<String> list2 = new ArrayList<>();
list2.add("1");list2.get(0); //String
但是在虛擬機器中時,list2.get(0)在調用時翻譯成了兩條虛擬機器命令:
1.對原始方法List集合的get方法調用
2.將返回的Object進行強制類型轉換成String
5.3翻譯泛型方法
當GenericSubClass類繼承了Generic類時,泛型方法會有什麼變化?
public class Generic<T> { private T size; public T getSize() { return size; }
...}
public class GenericSubclass extends Generic<Integer> { @Override public Integer getSize() { return super.getSize(); }}
此時的@Override真的是重寫父類方法嗎?到了運行期間,Generic被類型擦除後getSize方法傳回型別變成Object:
public Object getSize() { return size; }
而GenericSubclass的getSize方法的傳回型別卻是Integer:
public Integer getSize() { return super.getSize(); }
這並不是重寫父類的方法,因為方法並不一樣,所以導致GenericSubclass在虛擬機器中卻有了兩個getSize方法:
public Integer getSize() { ... }public Object getSize() { ... }
導致的原因就是類型擦除與多態發生了衝突。要解決此問題,虛擬機器自動在GenericSubclass中產生了一個橋方法:
public Integer getSize() { return (Integer)super.getSize(); }
最後,虛擬機器就會調用自己產生的橋方法來解決此衝突。
若有不足之處,請各位大牛在下面留言指出,Thanks♪(・ω・)ノ