教你如何使用泛型(二)

泛型的基本使用可以參考我的這篇文章(教你如何使用泛型),然而,當你真正使用泛型時,還需特別小心一些陷阱。本篇文章主要爲你介紹Java的泛型的類型檫除,以及類型檫除會帶來哪些問題,如何正確的處理這些問題。
首先,我們先看一個例子。

    public static void main(String[] args) {
        ArrayList<String> strList = new ArrayList<String>();
        ArrayList<Integer> intList = new ArrayList<Integer>();
        System.out.println(strList.getClass());
        System.out.println(intList.getClass());
    }

我們大部分人都會認爲ArrayList<String>ArrayList<Integer>是不同的類型。然而,當我們運行上述的代碼時,返回的結果都是class java.util.ArrayList。爲什麼會出現這樣的結果呢??
這是因爲類型檫除的效果。

類型檫除(type erasure) : generic type information is present only at compile time, after which it is erased by the compiler.

簡單的說,就是當你在使用泛型時,任何具體的信息都被檫除了。在泛型代碼的內部,無法獲得任何有關泛型參數類型的信息。因此,在上述代碼中,ArrayList<String>ArrayList<integer>是相同的類型。
Java設計很大程度上是受C++的啓發。而Java 泛型是Java SE5時才提出來的。如果你以前學過C++,那麼你會發現,有些以前C++可以做到的,而Java卻不能做到。爲了加深對泛型的理解,我們再看一個例子。
當我們使用C++模板時,我們可以這樣寫。

#include <iostream>

using namespace std;

template<class T> class Manipulator{
    T obj;
public:
    Manipulator(T x){
        obj = x;
    }
    void manipulate(){
        //注意這個方法。
        obj.f();
    }
};

class HasF{
public:
    void f(){
        cout<<"call to f()";
    }
};


int main()
{
    HasF hasF;
    Manipulator<HasF> man(hasF);
    man.manipulate();
//    cout << "Hello world!" << endl;
    return 0;
}

看到上面的代碼,我們可能會奇怪,obj.f().obj怎麼會知道f()是爲類型參數T而存在的呢?因爲,當實例化模板類時,C++編譯器將進行檢查,在Manipulator<HasF>實例化時,它會知道HasF擁有一個f()
用C++寫這種代碼是很容易的,因爲當模板被實例化時,模板代碼知道模板參數的類型。
那麼,Java泛型該如何實現呢??
如果直接像C++上面直接調用HasF中的方法f(),編譯器會報錯。因爲類型檫除,所以無法獲得泛型參數類型的信息。爲了調用f()方法,我們必須協助泛型類,給定泛型類的邊界,告知編譯器只能接受遵循這個邊界的類型。
代碼如下。
HasF.java

package genericdemo.typeerasure;

public class HasF {
    void f(){
        System.out.println("call to f()");
    }
}

Manipulator.java

package genericdemo.typeerasure;

import java.util.ArrayList;

public class Manipulator<T extends HasF> {
    private T obj;
    public Manipulator(T x) {
        obj = x;
    }
    public void method(){
        obj.f();
    }

    public static void main(String[] args) {
        ArrayList<String> strList = new ArrayList<String>();
        ArrayList<Integer> intList = new ArrayList<Integer>();
        System.out.println(strList.getClass());
        System.out.println(intList.getClass());
        System.out.println(strList.getClass() == intList.getClass());
        HasF hasF = new HasF();
        Manipulator<HasF> man = new Manipulator<HasF>(hasF);
        man.method();
    }
}

可能你會問,既然類型擦除會帶來種種問題,那麼,爲什麼還要使用擦除呢??首先,你要明白,Java一開始是不支持泛型的。泛型是Java SE5所作出的重大改變。如果從Java 1.0時,就支持泛型,那麼設計者肯定不會使用擦除。因此,之所以使用擦除,是在不破壞現有類庫的情況下,將泛型融入Java語言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章