本篇內(nèi)容主要講解“Java泛型的實(shí)現(xiàn)方式是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Java泛型的實(shí)現(xiàn)方式是什么”吧!
讓客戶(hù)滿(mǎn)意是我們工作的目標(biāo),不斷超越客戶(hù)的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶(hù),將通過(guò)不懈努力成為客戶(hù)在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:主機(jī)域名、網(wǎng)站空間、營(yíng)銷(xiāo)軟件、網(wǎng)站建設(shè)、從江網(wǎng)站維護(hù)、網(wǎng)站推廣。
Java 泛型實(shí)現(xiàn)方式
Java 采用**類(lèi)型擦除(Type erasure generics)**的方式實(shí)現(xiàn)泛型。用大白話(huà)講就是這個(gè)泛型只存在源碼中,編譯器將源碼編譯成字節(jié)碼之時(shí),就會(huì)把泛型『擦除』,所以字節(jié)碼中并不存在泛型。
對(duì)于下面這段代碼,編譯之后,我們使用 javap -s class 查看字節(jié)碼。
方法源碼
字節(jié)碼
觀察setParam 部分的字節(jié)碼,從 descriptor 可以看到,泛型 T 已被擦除,最終替換成了 Object。
“ps:并不是每一個(gè)泛型參數(shù)被擦除類(lèi)型后都會(huì)變成 Object 類(lèi),如果泛型類(lèi)型為 T extends String 這種方式,最終泛型擦除之后將會(huì)變成 String。
同理getParam 方法,泛型返回值也被替換成了 Object。
為了保證 String param = genericType.getParam(); 代碼的正確性,編譯器還得在這里插入類(lèi)型轉(zhuǎn)換。
除此之外,編譯器還會(huì)對(duì)泛型安全性防御,如果我們往 ArrayList
最終類(lèi)型擦除后的代碼等同與如下:
類(lèi)型擦除帶來(lái)的缺陷
作為對(duì)比,我們?cè)賮?lái)簡(jiǎn)單聊下 C# 泛型的實(shí)現(xiàn)方式。
**C#**泛型實(shí)現(xiàn)方式為「具現(xiàn)化式泛型(Reifiable generics)」,不熟悉的 C#小伙伴可以不用糾結(jié)具現(xiàn)化技術(shù)概念,我也不了解這些特性--!
簡(jiǎn)單點(diǎn)來(lái)講,**C#**實(shí)現(xiàn)的泛型,無(wú)論是在程序源碼,還是在編譯之后的,甚至是運(yùn)行期間都是切實(shí)存在的。
相對(duì)比與 C# 泛型,Java 泛型看起來(lái)就像是個(gè)「?jìng)巍狗盒汀ava 泛型只存在程序源碼中,編譯之后就被擦除,這種缺陷相應(yīng)的會(huì)帶來(lái)一些問(wèn)題。
不支持基本數(shù)據(jù)類(lèi)型
泛型參數(shù)被擦除之后,強(qiáng)制變成了 Object 類(lèi)型。這么做對(duì)于引用類(lèi)型來(lái)說(shuō)沒(méi)有什么問(wèn)題,畢竟 Object 是所有類(lèi)型的父類(lèi)型。但是對(duì)于 int/long 等八個(gè)基本數(shù)據(jù)類(lèi)型說(shuō),這就難辦了。因?yàn)?Java 沒(méi)辦法做到int/long 與 Object 的強(qiáng)制轉(zhuǎn)換。
如果要實(shí)現(xiàn)這種轉(zhuǎn)換,需要進(jìn)行一系列改造,改動(dòng)難度還不小。所以當(dāng)時(shí) Java 給出一個(gè)簡(jiǎn)單粗暴的解決方案:既然沒(méi)辦法做到轉(zhuǎn)換,那就索性不支持原始類(lèi)型泛型了。
如果需要使用,那就規(guī)定使用相關(guān)包裝類(lèi)的泛型,比如 ArrayList
正是這種「偷懶」的做法,導(dǎo)致現(xiàn)在我們沒(méi)辦法使用原始類(lèi)型泛型,又要忍受包裝類(lèi)裝箱/拆箱帶來(lái)的開(kāi)銷(xiāo),從而又帶來(lái)運(yùn)行效率的問(wèn)題。
運(yùn)行效率
上面字節(jié)碼例子我們已經(jīng)看到,泛型擦除之后類(lèi)型將會(huì)變成 Object。當(dāng)泛型出現(xiàn)在方法輸入位置的時(shí)候,由于 Java 是可以向上轉(zhuǎn)型的,這里并不需要強(qiáng)制類(lèi)型轉(zhuǎn)換,所以沒(méi)有什么問(wèn)題。
但是當(dāng)泛型參數(shù)出現(xiàn)在方法的輸出位置(返回值)的時(shí)候,調(diào)用該方法的地方就需要進(jìn)行向下轉(zhuǎn)換,將 Object 強(qiáng)制轉(zhuǎn)換成所需類(lèi)型,所以編譯器會(huì)插入一句 checkcast 字節(jié)碼。
除了這個(gè),上面我們還說(shuō)到原始基本數(shù)據(jù)類(lèi)型,編譯器還需幫助我們進(jìn)行裝箱/拆箱。
所以對(duì)于下面這段代碼來(lái)說(shuō):
List<Integer> list = new ArrayList<Integer>(); list.add(66); // 1 int num = list.get(0); // 2
對(duì)于①處,編譯器要做就是增加基本類(lèi)型的裝箱即可。但是對(duì)于第二步來(lái)說(shuō),編譯器首先需要將 Object 強(qiáng)制轉(zhuǎn)換成 Integer,接著編譯器還需要進(jìn)行拆箱。
類(lèi)型擦除之后,上面代碼等同于:
List list = new ArrayList(); list.add(Integer.valueOf(66)); int num = ((Integer) list.get(0)).intValue();
如果上面泛型代碼在 C# 實(shí)現(xiàn),就不會(huì)有這么多額外步驟。所以 Java 這種類(lèi)型擦除式泛型實(shí)現(xiàn)方式無(wú)論使用效果與運(yùn)行效率,還是全面落后于 C# 的具現(xiàn)化式泛型。
運(yùn)行期間無(wú)法獲取泛型實(shí)際類(lèi)型
由于編譯之后,泛型就被擦除,所以在代碼運(yùn)行期間,Java 虛擬機(jī)無(wú)法獲取泛型的實(shí)際類(lèi)型。
下面這段代碼,從源碼上兩個(gè) List 看起來(lái)是不同類(lèi)型的集合,但是經(jīng)過(guò)泛型擦除之后,集合都變?yōu)?ArrayList。所以 if語(yǔ)句中代碼將會(huì)被執(zhí)行。
ArrayList<Integer> li = new ArrayList<Integer>(); ArrayList<Float> lf = new ArrayList<Float>(); if (li.getClass() == lf.getClass()) { // 泛型擦除,兩個(gè) List 類(lèi)型是一樣的 System.out.println("6666"); }
這樣代碼看起來(lái)就有點(diǎn)反直覺(jué),這對(duì)新手來(lái)說(shuō)不是很友好。
另外還會(huì)給我們?cè)趯?shí)際使用中帶來(lái)一些限制,比如說(shuō)我們沒(méi)辦法直接實(shí)現(xiàn)以下代碼:
最后再舉個(gè)例子,比如說(shuō)我們需要實(shí)現(xiàn)一個(gè)泛型 List 轉(zhuǎn)換成數(shù)組的方法,我們就沒(méi)辦法直接從 List 去獲取泛型實(shí)際類(lèi)型,所以我們不得不額外再傳入一個(gè) Class 類(lèi)型,指定數(shù)組的類(lèi)型:
public static <E> E[] convert(List<E> list, Class<E> componentType) { E[] array = (E[]) Array.newInstance(componentType, list.size()); .... }
從上面的例子我們可以看到,Java 采用類(lèi)型擦除式實(shí)現(xiàn)泛型,缺陷很多。那為什么 Java 不采用 C# 的那種泛型實(shí)現(xiàn)方式?或者說(shuō)采用一種更好實(shí)現(xiàn)方式?
這個(gè)問(wèn)題等我們了解 Java 泛型機(jī)制的歷史,以及當(dāng)時(shí) Java 語(yǔ)言的現(xiàn)狀,我們才能切身體會(huì)到當(dāng)時(shí) Java 采用這種泛型實(shí)現(xiàn)方式的原因。
Java 泛型歷史背景
Java 泛型最早是在 JDK5 的時(shí)候才被引入,但是泛型思想最早來(lái)自來(lái)自 C++ 模板(template)。1996 年 Martin Odersky(Scala 語(yǔ)言締造者) 在剛發(fā)布的 Java 的基礎(chǔ)上擴(kuò)展了泛型、函數(shù)式編程等功能,形成一門(mén)新的語(yǔ)言-「Pizza」。
后來(lái),Java 核心開(kāi)發(fā)團(tuán)隊(duì)對(duì) Pizza 的泛型設(shè)計(jì)深感興趣,與 Martin 合作,一起合作開(kāi)發(fā)的一個(gè)新的項(xiàng)目「Generic Java」。這個(gè)項(xiàng)目的目的是為了給 Java 增加泛型支持,但是不引入函數(shù)式編程等功能。最終成功在 Java5 中正式引入泛型支持。
泛型移植過(guò)程,一開(kāi)始并不是朝著類(lèi)型擦除的方向前進(jìn),事實(shí) Pizza 中泛型更加類(lèi)似于 C# 中的泛型。
但是由于 Java 自身特性,自帶嚴(yán)格的約束,讓 Martin 在Generic Java 開(kāi)發(fā)過(guò)程中,不得不放棄了 Pizza 中泛型設(shè)計(jì)。
這個(gè)特性就是,Java 需要做到嚴(yán)格的向后兼容性。也就是說(shuō)一個(gè)在 JDK1.2 編譯出來(lái) Class 文件,不僅能在 JDK 1.2 能正常運(yùn)行,還得必須保證在后續(xù) JDK,比如 JDK12 中也能保證正常的運(yùn)行。
這種特性是明確寫(xiě)入 Java 語(yǔ)言規(guī)范的,這是一個(gè)對(duì) Java 使用者的一個(gè)嚴(yán)肅承諾。
“這里強(qiáng)調(diào)一下,這里的向后兼容性指的是二進(jìn)制兼容性,并不是源碼兼容性。也不保證高版本的 Class 文件能夠運(yùn)行在低版本的 JDK 上。
現(xiàn)在困難點(diǎn)在于,Java 1.4.2 之前都沒(méi)有支持泛型,而 Java5 之后突然要支持泛型,還要讓 JDK1.4 之前編譯的程序能在新版本中正常運(yùn)行,這就意味著以前沒(méi)有的限制,就不能突然增加。
舉個(gè)例子:
ArrayList arrayList=new ArrayList(); arrayList.add("6666"); arrayList.add(Integer.valueOf(666));
沒(méi)有泛型之前, List 集合是可以存儲(chǔ)不同類(lèi)型的數(shù)據(jù),那么引入泛型之后,這段代碼必須的能正確運(yùn)行。
為了保證這些舊的 Clas 文件能在 Java5 之后正常運(yùn)行,設(shè)計(jì)者基本有兩條路:
需要泛型化的容器(主要是容器類(lèi)型),以前有的保持不變,平行增加一套新的泛型化的版本。
直接把已有的類(lèi)型原地泛型化,不增加任何新的已有類(lèi)型的泛型版本。
如果 Java 采用第一條路實(shí)現(xiàn)方式,那么現(xiàn)在我們可能就會(huì)有兩套集合類(lèi)型。以 ArrayList 為例,一套為普通的 java.util.ArrayList,一套可能為 java.util.generic.ArrayList
采用這種方案之后,如果開(kāi)發(fā)中需要使用泛型特性,那么直接使用新的類(lèi)型。另外舊的代碼不改動(dòng),也可以直接運(yùn)行在新版本 JDK 中。
這套方案看起來(lái)沒(méi)什么問(wèn)題,實(shí)際上C# 就是采用這套方案。但是為什么 Java 卻沒(méi)有使用這套方案那?
這是因?yàn)楫?dāng)時(shí) C# 才發(fā)布兩年,歷史代碼并不多,如果舊代碼需要使用泛型特性,改造起來(lái)也很快。但是 Java 不一樣,當(dāng)時(shí) Java 已經(jīng)發(fā)布十年了,已經(jīng)有很多程序已經(jīng)運(yùn)行部署在生產(chǎn)環(huán)境,可以想象歷史代碼非常多。
如果這些應(yīng)用在新版本 Java 需要使用泛型,那就需要做大量源碼改動(dòng),可以想象這個(gè)開(kāi)發(fā)工作量。
另外 Java 5 之前,其實(shí)我們就已經(jīng)有了兩套集合容器,一套為 Vector/Hashtable 等容器,一套為 ArrayList/ HashMap。這兩套容器的存在,其實(shí)已經(jīng)引來(lái)一些不便,對(duì)于新接觸的 Java 的開(kāi)發(fā)人員來(lái)說(shuō),還得學(xué)習(xí)這兩者的區(qū)別。
如果此時(shí)為了泛型再引入新類(lèi)型,那么就會(huì)有四套容器同時(shí)并存。想想這個(gè)畫(huà)面,一個(gè)新接觸開(kāi)發(fā)人員,面對(duì)四套容器,完全不知道如何下手選擇。如何 Java 真的這么實(shí)現(xiàn)了,想必會(huì)有更多人吐槽 Java。
所以 Java 選擇第二條路,采用類(lèi)型擦除,只需要改動(dòng) Javac 編譯器,不需要改動(dòng)字節(jié)碼,不需要改動(dòng)虛擬機(jī),也保證了之前歷史沒(méi)有泛型的代碼還可以在新的 JDK 中運(yùn)行。
但是第二條路,并不代表一定需要使用類(lèi)型擦除實(shí)現(xiàn),如果有足夠時(shí)間好好設(shè)計(jì),也許會(huì)有更好的方案。
當(dāng)年留下的技術(shù)債,現(xiàn)在只能靠 Valhalla 項(xiàng)目來(lái)還了。這個(gè)項(xiàng)目從2014 年開(kāi)始立項(xiàng),原本計(jì)劃在 JDK10 中解決現(xiàn)有語(yǔ)言的各種缺陷。但是結(jié)果我們也知道了,現(xiàn)在都 JDK14 了,還只是完成少部分目標(biāo),并沒(méi)有解決核心目標(biāo),可見(jiàn)這個(gè)改動(dòng)的難度啊。
到此,相信大家對(duì)“Java泛型的實(shí)現(xiàn)方式是什么”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!
當(dāng)前題目:Java泛型的實(shí)現(xiàn)方式是什么
網(wǎng)站網(wǎng)址:http://jinyejixie.com/article24/jjisce.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、用戶(hù)體驗(yàn)、外貿(mào)建站、虛擬主機(jī)、服務(wù)器托管、網(wǎng)頁(yè)設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)