Java核心技術第八章——泛型程式設計(1)

來源:互聯網
上載者:User

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♪(・ω・)ノ

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.