一直聽說Java中的範型實現得很糟糕,但自己雖然一直用,卻也沒覺得有何問題,感覺比起遍地氾濫的Object來說,還是要優雅多了。然而,這不過是錯覺罷了。
Anders hejlsberg 解釋過這一現象:
Java的泛型最初是基於Martin Odersky和其它人一起做的稱作Pizza的一個項目的。Pizza後改名為GJ,然後成為JSR,最後以被Java語言收容而告終。這種泛型以能夠 在原有的VM(Virtual Machine,虛擬機)上運行為關鍵設計目標。也就是說,你不必修改你的VM,但它會帶來很多限制。這些限制並不會很快出現,但很快你就會說:「嗯,這 有點陌生。」對於我來說,事情的起因是前陣子需要寫一個循環隊列充當Buffer用,空間是有限的,但可以無限次的循環再利用。要求資源佔用要小,性能要好。該隊列一開始就要將空間中的所有元素都初始化,所以類似刪除、空隊列判斷、隊列大小計算等常見操作都可以忽略,算是個比較好寫的數據結構。JDK中自然是沒有現成的類可用,AbstractList都顯得有些多餘,於是,我單純的創建了一個類CircularArrayList<E>,並指定實現接口List<E>。因為只需有限空間,為減小不必要的資源佔用,內部使用了普通的定長數組。注意,我按照以往的習慣,使用了範型,拉開了惡夢的序幕。
首先讓我崩潰的是這句語句:
E[] elementData = new E[capacity];
這是關於Java範型中最經典、也是最無奈的「錯誤」了。當時鬱悶的我上網一搜,發現了無數的「同道」。這種寫法,對於任何一個熟悉傳統範型的人來說,都不會覺得有何不妥吧。更雷人的是,網上查到這句語句的正確Java版寫法竟然是:
E[] elementData = (E[]) new Object[capacity];
我的天!那我要那個E是來幹嘛的?那直接用Object不就得了?當擺設啊?我不服氣,決心尋求「官方寫法」,打開我以往的最愛ArrayList<E>看其構造函數,赫然是:
this.elementData = (E[])new Object[initialCapacity];
我無語了。原來Java中的範型真的只是擺設。我的初始化的設想自然也完蛋了,因為你根本別想指望建立一個E的實例。以下語句在Java中也是無法編譯的:
for (int i = 0; i < size; i++) {
elementData[convert(i)] == new E();
}
想知道「正確」寫法?說實話,實在太噁心了,我原本不想說的:
public void init(Class<E> c) {
for (int i = 0; i < size; i++) {
elementData[convert(i)] == c.newInstance();
}
}
這還是你走運的情況下。假如你的E不幸沒有缺省構造函數,你只能使用反射的方法,那個代碼更噁心,有興趣的自己看吧:
http://serdom.eu/ser/2007/03/25/java-generics-instantiating-objects-of-type-parameter-without-using-class-literalAnders hejlsberg 說道:
當你編譯一個Java泛型類時,編譯器會將所有的類型參數替換為Object。當然,如果你嘗試建立一個List,你就需要對所有的int進行裝箱。因此,這會有很大的開銷。另外,為了讓VM高興,編譯器必須為所有的類型插入類型轉換。如果一個List是Object的,而你想將這些Object視為Customer,就必須將 Object轉換為Customer,以讓類型檢查器滿意。而它在實現這些的時候,真的只是為你插入所有這些類型轉換。因此,你只是嘗到了語法上的甜頭,卻沒有獲得任何執行效率。所以我覺得這是(泛型的)Java實現的頭號問題。
這就是Java為了兼容性做出的折衷:以一種近乎無恥的方式,實現了一種現代編程語言中絕無僅有的「範型」。據說Java 7中會有真正的範型,在那之前,我想我還是不要再碰這個「極品」比較好。請.Net的程序員盡情的嘲笑我吧。也許我的下一個程序會是用python寫的吧。