天天看點

Java 泛型優點之編譯時類型檢查

Java 泛型優點之編譯時類型檢查

使用泛型代碼要比非泛型代碼更有優勢,下面是 Java 官方教程對泛型其中一個優點的介紹:

“Stronger type checks at compile time.
A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.”           

現在我有兩點 疑問 :

1、 在使用泛型時能在編譯時被檢測出的問題,在未使用泛型時是怎樣的情況?即怎樣才會出現這類上文中最後一句提到的不是更容易解決的運作時錯誤?(以代碼舉例)

2、 Java 如何提供這種編譯時的更強的類型檢查(第一句)。

解決

在 Java 還未明确的實作泛型機制之前,是具有泛型能力的,隻不過沒有進行文法層次上的包裝。比如以容器舉例。

容器的中的元素基本類型都是 Object,而由于 Java 的設計理念,Java 中所有類預設都是繼承于 Object 的,是以容器中的每一個元素都可 hold 任意對象的執行個體。

代碼如下:

ArrayList list = new ArrayList();
list.add(new String("over"));
list.add(new String("loard"));
...           

示意圖如下:

Java 泛型優點之編譯時類型檢查

當我們提取容器中的某一個 Object 元素時,我們隻能通路到 Object 對象作用域内的執行個體和方法。為了通路更加具體的對象(比如上圖中的 String)的方法或者執行個體域,我們需要告訴編譯器将 Object 引用轉換為 String 類型(Object 引用隻能通路 String 對象的一個子集,即定義在 Object 對象中的部分。即便我們的确有一個 String 對象)。當這種轉換符合繼承層級時(String 是 Object 的子類),轉化即可以通過編譯(隻是通過編譯)。

String str = (String)list.get(i);           

自然地,現在我們可以通過 str 通路 String 對象的方法和執行個體域。但是這裡其實是存在潛在的問題的。Object 引用能夠 hold 任意對象,那麼在這個例子的容器中,意味着我們可以将其他 Object 的子類類型的對象傳遞給容器的元素:

list.add(new Integer(1));   //通過編譯           

然後當我們再次執行類型轉換時,編譯時沒問題(因為實際是 String(Object) ),但程式将會在運作時抛出一個異常。

String str = (String)list.get(i);   //抛出 ClassCastException 異常           

盡管異常機制會提醒我們程式發生了我們未預期的情況,并将這些錯誤回報給我們,然而如果問題能在編譯時被解決,我們更希望在編寫代碼時就将錯誤避免掉。

當 Java 引入泛型機制後,這一目标可以被實作。Java 的泛型機制主要特點便是在原來的類型轉化機制上增加類型參數和類型擦除機制。

是以當我們再次使用容器時,我們将給它傳遞一個類型參數:

ArrayList<String> list = new ArrayList<>();           

這樣當我們将不是 String 類型的對象傳遞給容器的元素時,編譯器将會提示我們類型錯誤。如此一來,之前的類型轉換錯誤就被阻擋在了編譯時期。

但是,Java 為了向前相容使用普通的類型轉換的代碼而采用的擦除機制并不是很強大(相比 C++)。

比如對于泛型函數來說,使用擦除機制的泛型似乎并沒有帶來什麼改觀(類型安全方面)。

類型擦除的例子:

public <T extend SomeObject> f(T t) {       //預設 T 繼承于 Object
    T a = t;
    Sysyem.out.println(a);
}           

當我們對這個方法調用後,編譯器将進行對類型的擦除,經編譯器處理後的代碼如下:

public SomeObject f(SomeObject t) {
    SomeObject a = t;
    Sysyem.out.println(a);
}           

由于編譯器在編譯時将我們傳遞的類型資訊擦除掉(無法獲得類型資訊),是以一旦我們進行不合法的類型轉換,編譯器也不會察覺:

public <T extend Object> f(T t) {
    ...
    String str = (String)t;
    ...
}
//類型擦除後
public Object f(Object t) {
    ...
     String str = (String)t;    //編譯完全沒問題
    ...
}           

當我們調用該方法:

f(new Integer(1));      //在運作時将抛出一個 ClassCastException           

對比 C++ 的模闆,C++ 将在編譯時通過傳遞的類型參數檢測到存在非法的類型轉換(C++ 元程式設計具有将運作時檢測遷移到編譯期的能力)。

是以,問題應該算是被解決了(雖然有些簡陋和倉促)。

感謝閱讀

轉載請注明出處

參考資料:

泛型類型擦除

https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html

Java 泛型類型安全

https://stackoverflow.com/questions/44841156/java-generics-type-safety

Java 中的類型轉換

https://stackoverflow.com/questions/5289393/casting-variables-in-java

C++ 和 Java 中的泛型機制的不同

https://stackoverflow.com/questions/36347/what-are-the-differences-between-generic-types-in-c-and-java

運作時 VS 編譯時

https://stackoverflow.com/questions/846103/runtime-vs-compile-time

《Java 程式設計思想》第四版 通過異常處理錯誤 (為什麼編譯時解決問題要比運作時解決問題要好的原因之一)