最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java ASM詳解:泛型

2022-02-13 12:51 作者:Nickid2018  | 我要投稿

在之前的字節(jié)碼學(xué)習(xí)中,我們都是對(duì)有確定類(lèi)型的數(shù)據(jù)/類(lèi)進(jìn)行操作??墒牵琂ava有著一種“黑”操作用于更好的檢查對(duì)象類(lèi)型:在Java 5加入的泛型。

一.泛型的聲明

在正式導(dǎo)入泛型聲明之前,先說(shuō)說(shuō)泛型描述符。

普通的非基本類(lèi)型的描述符的命名方法是“L+類(lèi)名+;”,但是泛型不能以這種方式命名——它需要和普通的描述符有區(qū)別以便于區(qū)分。所以,它的命名要求是“T+泛型名稱(chēng)+;”。所以一個(gè)命名為T(mén)的泛型的描述符是TT;。接下來(lái)說(shuō)回到聲明。

每個(gè)泛型在使用之前必須經(jīng)過(guò)聲明,聲明的位置是類(lèi)、方法、字段、內(nèi)部類(lèi)等的聲明部分。如果非靜態(tài)方法或字段使用的泛型在它們所在的類(lèi)中被聲明過(guò)了,那么在它們聲明時(shí)這個(gè)泛型不需要二次聲明。

泛型要求必須繼承于一個(gè)確切的類(lèi),對(duì)于沒(méi)有寫(xiě)extends限定的泛型,它們默認(rèn)繼承于Object。

在字節(jié)碼中,泛型的聲明要用尖括號(hào)包圍。每對(duì)泛型用冒號(hào)隔開(kāi),冒號(hào)前方是類(lèi)型參數(shù)名稱(chēng),后方是類(lèi)型參數(shù)的超類(lèi)或另一個(gè)已聲明的類(lèi)型參數(shù)。如果類(lèi)型參數(shù)繼承于一個(gè)接口,那么應(yīng)該使用雙冒號(hào)。(可以不用遵守這個(gè)約定,冒號(hào)數(shù)量不影響解析)

泛型與泛型之間不需要多余的分隔符分割它們的定義,這是因?yàn)槊枋龇家苑痔?hào)結(jié)尾從而阻止二義性的解析。(這里也就說(shuō)明了為什么基本類(lèi)型不能當(dāng)類(lèi)型參數(shù)的限制符號(hào),它們的描述符不帶分號(hào)會(huì)造成不可阻止的二義性解析)

下面是泛型聲明的例子:

但是上面的幾條規(guī)范不能包括我們使用的所有情況。就比如下面的例子:

類(lèi)型參數(shù)的超類(lèi)具有類(lèi)型參數(shù)的使用,這時(shí)我們需要寫(xiě)出超類(lèi)類(lèi)型參數(shù)的位置和限定關(guān)系。

對(duì)于一個(gè)使用了類(lèi)型參數(shù)的類(lèi),它需要在類(lèi)名之后分號(hào)之前用尖括號(hào)表示各個(gè)類(lèi)型參數(shù)的配置。每個(gè)參數(shù)都可以是確切的類(lèi)、聲明過(guò)的類(lèi)型參數(shù)或通配符。和上面的聲明一樣,因?yàn)槊枋龇苑痔?hào)結(jié)尾,不會(huì)產(chǎn)生二義性解析,因此不需要額外分隔符用于分割定義。

對(duì)于上面的例子,它的聲明就可以表示成:

這個(gè)T可以代指所有的枚舉類(lèi)型,因?yàn)樗械拿杜e都隱式繼承Enum。

如果超類(lèi)類(lèi)型參數(shù)使用通配符?代替,就有三種情況:

  • 沒(méi)有任何限定,需要用“*”填入

  • 有extends限定,需要用“+”加上超類(lèi)描述符填入

  • 有super限定,需要用“-”加上子類(lèi)描述符填入

下面是這三種情況的實(shí)例:

二.泛型簽名

泛型在聲明之后就可以用于描述類(lèi)、字段、方法等的具體描述符,這部分也叫泛型簽名,是另一種描述類(lèi)、字段、方法類(lèi)型的方式。

泛型簽名與修飾的結(jié)構(gòu)有關(guān)。它不僅包含了類(lèi)、字段、方法每個(gè)具體位置上需要的具體類(lèi)型,還包含對(duì)泛型的聲明。如果這個(gè)簽名中使用的泛型沒(méi)有被聲明過(guò),那么就應(yīng)該在簽名的前方加入它的聲明。(也就是說(shuō)聲明是簽名的一部分)

下面對(duì)簽名的不同作用位置分開(kāi)說(shuō)明:

  • 泛型簽名修飾了一個(gè)類(lèi)。類(lèi)有兩個(gè)地方需要泛型的信息:超類(lèi)和實(shí)現(xiàn)接口。這些類(lèi)的具體泛型信息要以超類(lèi)和實(shí)現(xiàn)接口的順序排列寫(xiě)入,寫(xiě)入規(guī)則和上面的類(lèi)型參數(shù)超類(lèi)寫(xiě)入規(guī)則一樣。

  • 泛型簽名修飾了一個(gè)字段。字段只需要描述字段本身具體的泛型信息,并且字段不能定義泛型,所以泛型簽名只包含它的類(lèi)型的泛型信息。

  • 泛型簽名修飾了一個(gè)方法。這時(shí)這里就是方法描述符的具體泛型信息。格式類(lèi)似普通的方法描述符,只是泛型信息的寫(xiě)入要遵照上方規(guī)則。如果方法帶有拋出異常聲明并且異常列表含有泛型,那么在描述符之后還要加上異常列表。異常列表的每個(gè)類(lèi)都要用^開(kāi)頭并且需要寫(xiě)出具體的泛型信息。

下面我們定義一個(gè)類(lèi)作為例子,寫(xiě)出所有成員的泛型簽名:

三. 泛型擦除

在閱讀完上面的文本后,你可能會(huì)有一個(gè)疑問(wèn):Java已經(jīng)有方法描述符可以用來(lái)描述方法、字段描述符來(lái)描述字段等等,為什么還要再加入一個(gè)泛型簽名用于額外的檢查呢?這就有關(guān)于Java對(duì)泛型的具體實(shí)現(xiàn)方式,也就是“泛型擦除”。

在正式介紹這個(gè)機(jī)制之前,我們先看看反射對(duì)于泛型的處理。定義下面這個(gè)類(lèi):

接下來(lái)我們想要反射調(diào)用test這個(gè)方法??墒俏覀兛吹竭@個(gè)方法的形參列表里面含有一個(gè)類(lèi)型參數(shù)T,這個(gè)我們沒(méi)有辦法具體表示。所以,我們可以用Class類(lèi)的getDeclaredMethods獲取所有方法檢查它的真正形參列表的樣子:

可以看到,類(lèi)型參數(shù)的形參的位置上使用了它的超類(lèi)InputStream。字節(jié)碼在所有的含有類(lèi)型參數(shù)的地方都用它們的超類(lèi)的原始類(lèi)型代替,這種現(xiàn)象就是泛型擦除。

泛型擦除保證了JVM獲取方法時(shí)不含有未知量,在本質(zhì)上其實(shí)是保證各個(gè)字節(jié)碼中只存在靜態(tài)的信息,這樣能保證運(yùn)行的正確性,不會(huì)產(chǎn)生方法的二義性調(diào)用等。

對(duì)于具體的字節(jié)碼,所有字節(jié)碼指令都不能用帶有泛型信息的類(lèi)。具體來(lái)說(shuō),new/checkcast/instanceof/invokeXXX字節(jié)碼都不能使用任何泛型信息,它們只能使用原始類(lèi)型和替換的超類(lèi)。

在JVM中,泛型擦除之后泛型的真正實(shí)現(xiàn)其實(shí)是checkcast等字節(jié)碼指令的約束和運(yùn)行時(shí)對(duì)于對(duì)象真實(shí)類(lèi)型推斷的方法。在下面的例子中能體現(xiàn)字節(jié)碼對(duì)于泛型的約束。

四. 實(shí)例:泛型方法

下面,我們要生成這樣的代碼:

首先寫(xiě)出它的方法描述符和泛型簽名:

接下來(lái)注意到第一行有一個(gè)lambda表達(dá)式,它實(shí)現(xiàn)的是Comparator類(lèi)的compare方法。根據(jù)comparator變量的類(lèi)型,得知實(shí)現(xiàn)方法的參數(shù)列表應(yīng)該是(Number, Number)。所以我們應(yīng)該生成下面的lambda方法:

字節(jié)碼寫(xiě)入如下:

這時(shí)候就能把comparator給構(gòu)造出來(lái)了。注意我們實(shí)現(xiàn)的方法是compare(Object, Object)——類(lèi)型參數(shù)T被擦除到父類(lèi)型Object(這和你代碼中的使用無(wú)關(guān),取決于類(lèi)和方法的定義),而我們需要的實(shí)現(xiàn)是(Number, Number),這兩個(gè)不沖突,但是會(huì)損失類(lèi)型信息(下文會(huì)講)。因此我們inDy的參數(shù)第一個(gè)是實(shí)現(xiàn)的目標(biāo)的描述符,第三個(gè)是我們真正實(shí)現(xiàn)目標(biāo)的描述符。

剩下的代碼就比較簡(jiǎn)單了,就像下面這樣:

這樣我們的使用泛型的方法就構(gòu)建好了。我們可以通過(guò)反射傳入?yún)?shù):

得到的結(jié)果是:

這就說(shuō)明了我們的方法寫(xiě)入成功并成功地被JVM執(zhí)行。

五.泛型的安全性

泛型的加入本質(zhì)是為了確保代碼的簡(jiǎn)潔和編譯時(shí)輔助類(lèi)型檢查,也就是說(shuō),泛型能阻止類(lèi)型的錯(cuò)誤轉(zhuǎn)換,就比如下面的例子:

這種代碼在javac編譯時(shí)是無(wú)法檢查出語(yǔ)法和類(lèi)型錯(cuò)誤的:List的封裝性造成了它內(nèi)部數(shù)據(jù)的類(lèi)型丟失,在get時(shí)只能得知對(duì)象是Object子類(lèi)的對(duì)象但是不能得知確切的類(lèi)型。但是在JVM運(yùn)行時(shí)這就不一樣了。JVM能知道任何對(duì)象的確切類(lèi)型,因此在強(qiáng)制轉(zhuǎn)換Integer時(shí)JVM能探測(cè)到String->Integer這種不可能的強(qiáng)制類(lèi)型轉(zhuǎn)換并拋出ClassCastException。

當(dāng)泛型加入后,這種情況被改變了:

這樣的代碼在javac就不能通過(guò)編譯了:get的返回值是類(lèi)型參數(shù)T(通過(guò)字節(jié)碼中的泛型簽名得知),在前面的聲明中已經(jīng)定義為String,因此javac可以探測(cè)到這個(gè)不可能的類(lèi)型轉(zhuǎn)換,拋出編譯異常阻止編譯,保證類(lèi)型的安全性。

總而言之,泛型確保了源碼級(jí)別上的類(lèi)型安全性。

但是在字節(jié)碼上看來(lái),這就是另外一回事了。因?yàn)椤胺盒筒脸睓C(jī)制,字節(jié)碼是不能使用泛型檢查的,只能通過(guò)類(lèi)型參數(shù)的已知超類(lèi)約束泛型。但是這種約束不能阻止我們使用錯(cuò)誤的類(lèi)型傳入:類(lèi)驗(yàn)證時(shí)是無(wú)法檢查泛型的。下面使用反射(本質(zhì)和字節(jié)碼差不多)舉一個(gè)例子,使用到了我們剛才寫(xiě)的泛型方法:

這里是不會(huì)產(chǎn)生任何警告的(即使在IDE中),但是我們能清楚的看出它不符合我們對(duì)于test的定義:test要求傳入的是List<? extends T>和T的形參,并且T要求是Number的子類(lèi),但是我們實(shí)際傳入的是List<String>和Number,明顯不符合test的形參列表。可是我們是能成功調(diào)用的:類(lèi)型擦除讓這些類(lèi)型回到了原始類(lèi)型,也就是說(shuō)test的形參列表變成了List和Number,這時(shí)與我們傳入的對(duì)象相符合,因此反射調(diào)用不會(huì)產(chǎn)生問(wèn)題,但是在方法內(nèi)的具體實(shí)現(xiàn)時(shí)會(huì)使用到checkcast用于類(lèi)型轉(zhuǎn)換,這里就會(huì)被JVM檢測(cè)到異常并拋出ClassCastException。對(duì)于我們的這個(gè)調(diào)用,它最后的結(jié)果是:

這樣的結(jié)果說(shuō)明了泛型在字節(jié)碼中是不能保證安全的,類(lèi)驗(yàn)證無(wú)法通過(guò)泛型簽名阻止錯(cuò)誤調(diào)用。因此在寫(xiě)入字節(jié)碼時(shí),遇到泛型必須嚴(yán)謹(jǐn)?shù)臋z查,否則就會(huì)因?yàn)轭?lèi)型信息的丟失造成了類(lèi)型的不安全行為,并且在寫(xiě)字節(jié)碼時(shí)應(yīng)盡量避免使用泛型。泛型的便利性僅體現(xiàn)在源碼上,字節(jié)碼中的泛型寫(xiě)入很麻煩。

有關(guān)于泛型的字節(jié)碼知識(shí)就到這里了,有錯(cuò)誤可以在評(píng)論指出。

到這里有關(guān)于具體方法字節(jié)碼的寫(xiě)入就結(jié)束了,之后主要是有關(guān)于類(lèi)結(jié)構(gòu)的詳解和ASM庫(kù)各種工具的講解。

下一篇文章暫定為類(lèi)的結(jié)構(gòu)(一)。

Java ASM詳解:泛型的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
福建省| 布尔津县| 聂荣县| 布尔津县| 灌云县| 大方县| 永靖县| 喀喇沁旗| 鸡西市| 鹤庆县| 无极县| 石首市| 兖州市| 永昌县| 清原| 辽源市| 禹州市| 安阳县| 巩留县| 西宁市| 博白县| 湛江市| 中山市| 繁峙县| 花莲市| 华阴市| 沾化县| 泽库县| 中西区| 新乡县| 勃利县| 伊宁市| 乌拉特前旗| 福建省| 贵阳市| 灌云县| 读书| 九龙县| 阳高县| 博爱县| 大同县|