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

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

Java ASM詳解:類的結(jié)構(gòu)(二)

2022-06-02 18:00 作者:Nickid2018  | 我要投稿

上次專欄講解了普通類的結(jié)構(gòu),這篇專欄將繼續(xù)講解接口、注解、枚舉和記錄這四種特殊的類。

一.接口

接口類似于抽象類,內(nèi)部含有靜態(tài)方法、公開抽象方法、公開默認(rèn)實(shí)例方法、私有實(shí)例方法和常量字段。

聲明一個(gè)接口,需要在ClassWriter::visit時(shí)同時(shí)使用ACC_ABSTRACTACC_INTERFACE訪問標(biāo)志。如果只含有ACC_INTERFACE標(biāo)志,在加載這個(gè)類的時(shí)候JVM就會(huì)拋出java.lang.ClassFormatError: Illegal class modifiers in class *: 0x200的異常。接口的繼承本質(zhì)其實(shí)是實(shí)現(xiàn),也就是說接口的父類仍然是Object,但是實(shí)現(xiàn)接口列表可以加入其它的接口。

如果接口是一個(gè)內(nèi)部類,在使用時(shí)類似于靜態(tài)內(nèi)部類,也就是說內(nèi)部接口不需要外部類實(shí)例作為依托。(但是在使用visit時(shí)不用寫ACC_STATIC,這是一種等效)同樣的,如果接口內(nèi)部有內(nèi)部類,那么內(nèi)部類也等效于靜態(tài)內(nèi)部類。

緊接著說說接口的字段。接口內(nèi)只能存在一種字段,那就是公開常量字段,也就是PSF(public static final)字段。在visitField時(shí)必須同時(shí)使用ACC_PUBLIC、ACC_STATIC和ACC_FINAL訪問標(biāo)志,否則在加載這個(gè)類的時(shí)候就會(huì)拋出java.lang.ClassFormatError: Illegal field modifiers in class *: *的異常。

接著來看看方法。接口不存在構(gòu)造函數(shù),并且只允許兩種訪問修飾符:public和private(引入于Java 9),protected不能在接口中使用,不寫訪問修飾符則默認(rèn)公開。

接口內(nèi)可以定義靜態(tài)方法,與普通的類沒有太多差別,只是訪問修飾有差別。對(duì)于接口內(nèi)的實(shí)例方法,在Java層如果不定義default或者private那就會(huì)自動(dòng)加上abstract,但是對(duì)于字節(jié)碼來說,除了訪問修飾外所有的定義都和普通的類一樣。

接口也可以使用橋接方法。

下面是個(gè)例子,要生成下面的類:

將使用下面的代碼:

二.注解

注解類型是特殊的接口,除了接口都具有的特性之外還增加了一些限制。

先說一下聲明。注解類型除了接口要求的兩個(gè)訪問標(biāo)志外還需要添加一個(gè)ACC_ANNOTATION標(biāo)志,且必須只實(shí)現(xiàn)(或者說是Java層的繼承)于java.lang.annotation.Annotation。(注意:JVM對(duì)實(shí)現(xiàn)Annotation接口這件事不加以檢查,但是如果你使用了反射嘗試獲取這個(gè)注解時(shí)會(huì)報(bào)錯(cuò))

注解對(duì)于方法的要求很嚴(yán)格:要求不能有私有方法、默認(rèn)方法和靜態(tài)方法,只能存在公開抽象方法。

到這里你可能會(huì)問:注解方法的默認(rèn)值是怎么實(shí)現(xiàn)的?其實(shí)默認(rèn)值不是方法體,而是使用了visitAnnotationDefault這個(gè)方法用于寫入默認(rèn)值。

下面我們要生成這個(gè)注解:

可以使用下面的代碼生成:

三.枚舉

枚舉是一種特殊的類,主要用于存貯常量。和普通的類相比,它有下面的性質(zhì):

  • 所有枚舉都繼承于java.lang.Enum,并且都為final。

  • 枚舉的所有構(gòu)造函數(shù)都是私有的。

  • 自動(dòng)生成values和valueOf方法。

  • 作為內(nèi)部類時(shí)等效靜態(tài)。

寫入一個(gè)枚舉,在visit時(shí)要將ACC_ENUMACC_FINAL訪問標(biāo)志同時(shí)寫入,并且父類必須寫為java/lang/Enum。由于Enum帶有泛型,所以signature也要寫入。例如,下面這個(gè)枚舉:

在聲明時(shí)必須用下面的代碼:

在使用Java編寫枚舉類時(shí)可以寫兩種字段:一種是普通的字段,這個(gè)和普通的類一樣,沒有限制;另一種就是枚舉字段,它必須是枚舉類的對(duì)象,并且是PSF字段還帶有ACC_ENUM訪問標(biāo)志。例如下方的枚舉字段A:

在聲明時(shí)應(yīng)該遵照下面的方式:

在字節(jié)碼中,除了上面的兩種字段外,枚舉中還有一個(gè)字段是系統(tǒng)生成用于保存所有枚舉字段的私有常量,$VALUES。它的訪問標(biāo)志除了ACC_PRIVATE、ACC_STATIC和ACC_FINAL外還帶有ACC_SYNTHETIC,這代表它是編譯時(shí)自動(dòng)生成的。它的類型是這個(gè)類的數(shù)組。

$VALUES存在的價(jià)值是為了values方法和Enum的索引。在介紹它的用途之前先來說說$VALUES和枚舉字段的初始化。

枚舉字段和普通常量的初始化一樣,也是簡單的創(chuàng)建、調(diào)用構(gòu)造函數(shù)和賦值。但是不同的是,枚舉的構(gòu)造函數(shù)和普通的構(gòu)造函數(shù)不同,它默認(rèn)帶有兩個(gè)形參。實(shí)際上,枚舉的默認(rèn)構(gòu)造函數(shù)是這樣的:

枚舉默認(rèn)構(gòu)造函數(shù)的寫入如下:

如果枚舉類有定義構(gòu)造函數(shù),那么在字節(jié)碼中仍然需要將這兩個(gè)形參添加到Java源代碼的形參列表之前,且必須調(diào)用Enum的這個(gè)父類構(gòu)造函數(shù)。

所以枚舉字段的創(chuàng)建對(duì)于上面的A來說就像下面這樣:

$VALUES的賦值不太一樣,它是委托到了另一個(gè)方法$values生成的。這個(gè)方法帶有ACC_PRIVATE、ACC_STATIC和ACC_SYNTHETIC訪問標(biāo)志,且方法返回值是該類的數(shù)組,形參列表為空。它的作用就是創(chuàng)建一個(gè)數(shù)組,并將所有枚舉字段按順序存儲(chǔ)進(jìn)數(shù)組之中并返回。對(duì)于上面的Test就是這樣的:

在靜態(tài)初始化中,$VALUES直接由$values的返回值賦值:

我們用到的values是另一個(gè)方法,也是由編譯器自動(dòng)生成的。它返回的是$VALUES的副本,Java的代碼像這樣:

// 可以注意到這個(gè)方法不存在try-catch,即使clone定義了拋出CloneNotSupportedException。JVM對(duì)這種異常處理不檢查,可以說在字節(jié)碼范圍內(nèi),異常處理是可有可無的。

字節(jié)碼像這樣:

另一個(gè)會(huì)自動(dòng)創(chuàng)建的方法,valueOf,用Java表示是這樣的:

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

到這里一個(gè)完整的枚舉才寫完??梢钥吹轿覀儽仨殞懗?VALUES、$values、values、valueOf這些字段和方法,非常的麻煩。即使一個(gè)非常簡單的枚舉也必須有所有這些要素,所以枚舉的寫入很繁瑣,還要注意別忘了它的組件。

四.記錄

記錄也是一種特殊的類,它在Java 14開始加入。和普通的類相比,它有下面的不同之處:

  • 不能單獨(dú)定義實(shí)例字段,所有終態(tài)實(shí)例字段都要在類之后的括號(hào)定義。

  • 繼承于java.lang.Record,且都為final。

  • 自動(dòng)生成toString、hashCode和equals。(除非自行定義)

  • 作為內(nèi)部類時(shí)等效靜態(tài)。

寫入一個(gè)記錄,在visit時(shí)必須帶有ACC_FINAL和ACC_RECORD訪問標(biāo)志,并且要繼承java/lang/Record。接下來以下面的記錄作為例子:

在寫入時(shí)要這樣定義:

對(duì)于記錄的終態(tài)實(shí)例字段(也可以叫記錄字段),它只能含有ACC_PRIVATE和ACC_FINAL這兩個(gè)訪問標(biāo)志。它需要兩次定義:一次是普通的字段定義,使用visitField;另一次是記錄組件的定義,使用visitRecordComponent,在定義字段之前寫入。這里的a就像下面這樣定義:

每個(gè)記錄字段都有自動(dòng)生成的對(duì)應(yīng)的getter,代碼很簡單,就像下面這樣:

字節(jié)碼像這樣寫:

記錄的默認(rèn)構(gòu)造函數(shù)和記錄字段有關(guān),形參列表正好和記錄字段的順序一致。對(duì)于上面的Test,構(gòu)造函數(shù)是這樣的:

轉(zhuǎn)換成字節(jié)碼如下:

記錄必須有toString、hashCode、equals這三個(gè)方法,這是因?yàn)樵赗ecord中聲明了它們3個(gè)是抽象的。如果我們不自己寫這三個(gè)方法,那么系統(tǒng)在編譯的時(shí)候會(huì)自動(dòng)生成。

自動(dòng)生成的這三個(gè)方法都用到了invokedynamic,使用的引導(dǎo)方法都是java.lang.runtime.ObjectMethods.bootstrap。這個(gè)方法的定義是:

其中names是記錄實(shí)例字段的名稱序列,用分號(hào);隔開。

下面僅給出toString的代碼,另兩個(gè)除了methodName和type不同外沒有差別。

到這里記錄才算寫入完畢。

到這里類的結(jié)構(gòu)就結(jié)束了,接下來的文章將討論好玩的東西(因?yàn)檫€沒想出來)。

這系列專欄沒有特殊聲明都是Java 17的字節(jié)碼,請注意使用。

如果對(duì)文章內(nèi)容有問題或文章有錯(cuò)誤可以評(píng)論區(qū)或私信指出。

Java ASM詳解:類的結(jié)構(gòu)(二)的評(píng)論 (共 條)

分享到微博請遵守國家法律
剑川县| 福鼎市| 堆龙德庆县| 黔东| 新乡县| 万宁市| 渭源县| 嘉峪关市| 工布江达县| 瓦房店市| 北海市| 株洲县| 中牟县| 和政县| 桐城市| 大安市| 涞源县| 麦盖提县| 宜都市| 漳平市| 伊金霍洛旗| 通化县| 南和县| 扎囊县| 临沭县| 黔东| 西丰县| 台南县| 荥经县| 遂川县| 大洼县| 兰溪市| 河西区| 安化县| 杭州市| 吉隆县| 固镇县| 会东县| 萝北县| 綦江县| 乌拉特前旗|