Java不能真正泛型的原因是什么?
簡(jiǎn)單來(lái)回顧一下類(lèi)型擦除,看下面這段代碼。
public class Cmower {
public static void method(ArrayList<String> list) {
System.out.println("Arraylist<String> list");
}
public static void method(ArrayList<Date> list) {
System.out.println("Arraylist<Date> list");
}
}
在淺層的意識(shí)上,我們會(huì)認(rèn)為 ArrayList<String> list 和 ArrayList<Date> list 是兩種不同的類(lèi)型,因?yàn)?String 和 Date 是不同的類(lèi)。
但由于類(lèi)型擦除的原因,以上代碼是不會(huì)編譯通過(guò)的——編譯器會(huì)提示一個(gè)錯(cuò)誤:
‘method(ArrayList)' clashes with ‘method(ArrayList)'; both methods have same erasure
也就是說(shuō),兩個(gè) method() 方法經(jīng)過(guò)類(lèi)型擦除后的方法簽名是完全相同的,Java 是不允許這樣做的。
也就是說(shuō),按照我們的假設(shè):如果 Java 能夠?qū)崿F(xiàn)真正意義上的泛型,兩個(gè) method() 方法是可以同時(shí)存在的,就好像方法重載一樣。
public class Cmower {
public static void method(String list) {
}
public static void method(Date list) {
}
}
為什么 Java 不能實(shí)現(xiàn)真正意義上的泛型呢?背后的原因是什么?
第一,兼容性
Java 在 2004 年已經(jīng)積累了較為豐富的生態(tài),如果把現(xiàn)有的類(lèi)修改為泛型類(lèi),需要讓所有的用戶(hù)重新修改源代碼并且編譯,這就會(huì)導(dǎo)致 Java 1.4 之前打下的江山可能會(huì)完全覆滅。
想象一下,你的代碼原來(lái)運(yùn)行的好好的,就因?yàn)?JDK 的升級(jí),導(dǎo)致所有的源代碼都無(wú)法編譯通過(guò)并且無(wú)法運(yùn)行,是不是會(huì)非常痛苦?
類(lèi)型擦除就完美實(shí)現(xiàn)了兼容性,Java 1.5 之后的類(lèi)可以使用泛型,而 Java 1.4 之前沒(méi)有使用泛型的類(lèi)也可以保留,并且不用做任何修改就能在新版本的 Java 虛擬機(jī)上運(yùn)行。
老用戶(hù)不受影響,新用戶(hù)可以自由地選擇使用泛型,可謂一舉兩得。
第二,不是“實(shí)現(xiàn)不了”
這部分內(nèi)容參考自 R大@RednaxelaFX
Pizza,1996 年的實(shí)驗(yàn)語(yǔ)言,在 Java 的基礎(chǔ)上擴(kuò)展了泛型。
Pizza 教程地址:http://pizzacompiler.sourceforge.net/doc/tutorial.html
這里插一下 Java 的版本歷史,大家好有一個(gè)時(shí)間線(xiàn)上的觀念。
- 1995年5月23日,Java語(yǔ)言誕生
- 1996年1月,JDK1.0 誕生
- 1997年2月18日,JDK1.1發(fā)布
- 1998年2月,JDK1.1被下載超過(guò)2,000,000次
- 2000年5月8日,JDK1.3發(fā)布
- 2000年5月29日,JDK1.4發(fā)布
- 2004年9月30日18:00 PM,J2SE1.5 發(fā)布
也就是說(shuō),Pizza 在 JDK 1.0 的版本上就實(shí)現(xiàn)了“真正意義上的”泛型,我引過(guò)來(lái)兩段例子,大家一看就明白了。
首先是 StoreSomething,一個(gè)泛型類(lèi),標(biāo)識(shí)符是大寫(xiě)字母 A 而不是我們熟悉的大寫(xiě)字母 T。
class StoreSomething<A> {
A something;
StoreSomething(A something) {
this.something = something;
}
void set(A something) {
this.something = something;
}
A get() {
return something;
}
}
這個(gè) A 呢,可以是任何合法的 Java 類(lèi)型:
StoreSomething<String> a = new StoreSomething("I'm a string!");
StoreSomething<int> b = new StoreSomething(17+4);
b.set(9);
int i = b.get();
String s = a.get();
對(duì)吧?這就是我們想要的“真正意義上的泛型”,A 不僅僅可以是引用類(lèi)型 String,還可以是基本數(shù)據(jù)類(lèi)型。要知道,Java 的泛型不允許是基本數(shù)據(jù)類(lèi)型,只能是包裝器類(lèi)型。

除此之外,Pizza 的泛型還可以直接使用 new 關(guān)鍵字進(jìn)行聲明,并且 Pizza 編譯器會(huì)從構(gòu)造方法的參數(shù)上推斷出具體的對(duì)象類(lèi)型,究竟是 String 還是 int。要知道,Java 的泛型因?yàn)轭?lèi)型擦除的原因,程序員是無(wú)法知道一個(gè) ArrayList 究竟是 ArrayList<String> 還是 ArrayList<Integer> 的。
ArrayList<Integer> ints = new ArrayList<Integer>(); ArrayList<String> strs = new ArrayList<String>(); System.out.println(ints.getClass()); System.out.println(strs.getClass());
輸出結(jié)果:
class java.util.ArrayList class java.util.ArrayList
都是 ArrayList 而已。
那 Pizza 這種“真正意義上的泛型”為什么沒(méi)有被 Java 采納呢?這是大家都很關(guān)心的問(wèn)題。
事實(shí)上,Java 的核心開(kāi)發(fā)組對(duì) Pizza 的泛型設(shè)計(jì)非常感興趣,并且與 Pizza 的設(shè)計(jì)者 Martin 和 Phil 取得了聯(lián)系,新合作了一個(gè)項(xiàng)目 Generic Java,爭(zhēng)取在 Java 中添加泛型支持,但不引入 Pizza 的其他功能,比如說(shuō)函數(shù)式編程。
這里再補(bǔ)充一點(diǎn)維基百科上的資料,Martin Odersky 是一名德國(guó)計(jì)算機(jī)科學(xué)家,他和其他人一起設(shè)計(jì)了 Scala 編程語(yǔ)言,以及 Generic Java(還有之前的 Pizza),他實(shí)現(xiàn)的 Generic Java 編譯器成為了 Java 編譯器 javac 的基礎(chǔ)。
站在馬后炮的思維來(lái)看,Pizza 的泛型設(shè)計(jì)和函數(shù)式編程非常具有歷史前瞻性。然而 Java 的核心開(kāi)發(fā)組在當(dāng)時(shí)似乎并不想把函數(shù)式編程引入到 Java 中。
以至于 Java 在 1.4 之前仍然是不支持泛型的,為什么 Java 1.5 的時(shí)候又突然支持泛型了呢?
當(dāng)然是到了不支持不行的時(shí)候了。
沒(méi)有泛型之前,我們可以這樣寫(xiě)代碼:
ArrayList list = new ArrayList();
list.add("沉默王二");
list.add(new Date());
不管是 String 類(lèi)型,還是 Date 類(lèi)型,都可以一股腦塞進(jìn) ArrayList 當(dāng)中,這看起來(lái)似乎很方便,但取的時(shí)候就悲劇了。
String s = list.get(1);
這樣取行嗎?
不行。
還得加上強(qiáng)制轉(zhuǎn)換。
String s = (String) list.get(1);
但我們知道,這行代碼在運(yùn)行的時(shí)候必然會(huì)出錯(cuò):
Exception in thread "main" java.lang.ClassCastException: java.util.Date cannot be cast to java.lang.String
這就又回到“兼容性”的問(wèn)題了。
Java 語(yǔ)言和其他編程語(yǔ)言不一樣,有著沉重的歷史包袱,1.5 之前已經(jīng)有大量的程序部署在生產(chǎn)環(huán)境下了,這時(shí)候如果一刀切,原來(lái)沒(méi)有使用泛型的代碼直接扼殺了,后果不堪想象。
Java 一直以來(lái)都強(qiáng)調(diào)兼容性,我認(rèn)為這也是 Java 之所以能被廣泛使用的主要原因之一,開(kāi)發(fā)者不必?fù)?dān)心 Java 版本升級(jí)的問(wèn)題,一個(gè)在 JDK 1.4 上可以跑的代碼,放在 JDK 1.5 上仍然可以跑。
這里必須得說(shuō)明一點(diǎn),J2SE1.5 的發(fā)布,是 Java 語(yǔ)言發(fā)展史上的重要里程碑,為了表示該版本的重要性,J2SE1.5 也正式更名為 Java SE 5.0,往后去就是 Java SE 6.0,Java SE 7.0。。。。
但 Java 并不支持高版本 JDK 編譯生成的字節(jié)碼文件在低版本的 JRE(Java 運(yùn)行時(shí)環(huán)境)上跑。

針對(duì)泛型,兼容性具體表現(xiàn)在什么地方呢?
ArrayList<Integer> ints = new ArrayList<Integer>(); ArrayList<String> strs = new ArrayList<String>(); ArrayList list; list = ints; list = strs;
表現(xiàn)在上面這段代碼必須得能夠編譯運(yùn)行。怎么辦呢?
就只能搞類(lèi)型擦除了!
真所謂“表面上一套,背后玩另外一套”呀!
編譯前進(jìn)行泛型檢測(cè),ArrayList<Integer> 只能放 Integer,ArrayList<String> 只能放 String,取的時(shí)候就不用擔(dān)心類(lèi)型強(qiáng)轉(zhuǎn)出錯(cuò)了。
但編譯后的字節(jié)碼文件里,是沒(méi)有泛型的,放的都是 Object。
Java 神奇就神奇在這,表面上萬(wàn)物皆對(duì)象,但為了性能上的考量,又存在 int、double 這種原始類(lèi)型,但原始類(lèi)型又沒(méi)辦法和 Object 兼容,于是我們就只能寫(xiě) ArrayList<Integer> 這樣很占用內(nèi)存空間的代碼。
這恐怕也是 Java 泛型被吐槽的原因之一了。
總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- Java:泛型知識(shí)知多少
- Java泛型枚舉Annotation接口詳細(xì)解讀與Eclipse發(fā)展
- Java的類(lèi)型擦除式泛型詳解
- 一篇文章帶你了解Java泛型的super和extends
- 一篇文章帶你入門(mén)java泛型
- 深入了解Java核心類(lèi)庫(kù)--泛型類(lèi)
- Java泛型的類(lèi)型擦除示例詳解
- Java基礎(chǔ)之java泛型通配符詳解
- Java 泛型詳解(超詳細(xì)的java泛型方法解析)
- java泛型的局限探究及知識(shí)點(diǎn)總結(jié)
- java泛型基本知識(shí)和通用方法
- Java泛型機(jī)制與反射原理相關(guān)知識(shí)總結(jié)
- Java中的泛型
相關(guān)文章
Spring依賴(lài)注入Dependency Injection的三種方式
依賴(lài)注入(Dependency Injection)和控制反轉(zhuǎn)(Inversion of Control)是同一個(gè)概念。具體含義是:當(dāng)某個(gè)角色(可能是一個(gè)Java實(shí)例,調(diào)用者)需要另一個(gè)角色(另一個(gè)Java實(shí)例,被調(diào)用者)的協(xié)助時(shí),在傳統(tǒng)的程序設(shè)計(jì)過(guò)程中,通常由調(diào)用者來(lái)創(chuàng)建被調(diào)用者的實(shí)例2023-02-02
Java中JDBC實(shí)現(xiàn)動(dòng)態(tài)查詢(xún)的實(shí)例詳解
從多個(gè)查詢(xún)條件中隨機(jī)選擇若干個(gè)組合成一個(gè)DQL語(yǔ)句進(jìn)行查詢(xún),這一過(guò)程叫做動(dòng)態(tài)查詢(xún)。下面通過(guò)實(shí)例代碼給大家講解JDBC實(shí)現(xiàn)動(dòng)態(tài)查詢(xún)的方法,需要的朋友參考下吧2017-07-07
SpringMVC中事務(wù)是否可以加在Controller層的問(wèn)題
這篇文章主要介紹了SpringMVC中事務(wù)是否可以加在Controller層的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
java 讀取系統(tǒng)Properties代碼實(shí)例
這篇文章主要介紹了java 讀取系統(tǒng)Properties代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Spring Boot與Redisson實(shí)時(shí)排行榜功能
排行榜功能是常見(jiàn)且重要的需求之一,本文主要介紹了Spring Boot與Redisson實(shí)時(shí)排行榜功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05

