反射泛型這些坑,你踏空了嗎?

      網(wǎng)友投稿 986 2022-05-30

      1 當(dāng)反射遇到方法重載

      重載grade方法,入?yún)⒎謩e是int和Integer。

      不通過反射,選用哪個重載方法很清晰,比如傳入666走int參數(shù)重載方法,傳入Integer.valueOf(“666”)走Integer重載。

      但你若墨守成規(guī)認(rèn)為反射調(diào)用方法也是根據(jù)入?yún)㈩愋痛_定方法重載。

      使用getDeclaredMethod獲取 grade方法,然后傳入Integer.valueOf(“36”)

      因為通過反射進行方法調(diào)用都是

      通過方法簽名來確定方法

      本例的getDeclaredMethod傳入的參數(shù)類型Integer.TYPE其實一直代表int

      所以實際執(zhí)行方法時不管傳包裝類型/基本類型,最終都是調(diào)用int入?yún)⒌膅rade方法。

      將Integer.TYPE改為Integer.class,實際執(zhí)行的參數(shù)類型就是Integer了。且無論傳包裝類型/基本類型,最終都會調(diào)用Integer為入?yún)⒌膅rade方法。

      所以反射調(diào)用方法,是以反射獲取方法時傳入的方法名和參數(shù)類型來確定調(diào)用的方法。

      2 泛型經(jīng)過類型擦除多出橋接方法的坑

      泛型是一種編程范式,允許開發(fā)者使用類型參數(shù)替代精確類型,實例化時再指明具體類型。也利于代碼重用,將一套代碼應(yīng)用到多種數(shù)據(jù)類型。

      泛型的類型檢測,可以在編譯時暴露大多數(shù)泛型編碼錯誤。但由于歷史兼容性而妥協(xié)的泛型類型擦除,在運行時才會暴露很多坑。

      案例

      期望在類字段內(nèi)容變動時記錄日志,于是開發(fā)同學(xué)就想到定義一個泛型父類,并在父類中定義一個統(tǒng)一的日志記錄方法,子類可繼承該方法。上線后總出現(xiàn)日志重復(fù)記錄問題。

      父類

      子類Child1 未提供父類泛型參數(shù)且定義了一個參數(shù)為String而非T的setValue。期望覆蓋父類的setValue實現(xiàn)。

      子類方法的調(diào)用是通過反射。

      雖Parent的value字段正確設(shè)置JavaEdge,但父類setValue調(diào)用了兩次,計數(shù)器而顯示2

      兩次Parent的setValue方法調(diào)用,是因為getMethods找到了兩個setValue的,分屬于父類/子類。

      子類重寫父類方法失敗原因

      子類未指定String泛型參數(shù),父類的泛型方法setValue(T value)泛型擦除后是setValue(Object value),于是子類入?yún)tring的setValue被當(dāng)作了新方法

      子類的setValue方法未加@Override注解,編譯器未能檢測到重寫失敗。

      重寫子類方法時,務(wù)必使用@Override注解。

      但有人認(rèn)為問題是反射API使用不當(dāng)而未意識到重寫失敗。查文檔后才發(fā)現(xiàn)

      getMethods能獲得當(dāng)前類和父類的所有public方法

      getDeclaredMethods僅獲得當(dāng)前類所有的public、protected、package和private方法

      于是用getDeclaredMethods替換getMethods:

      這雖能解決重復(fù)記錄日志,但未解決子類重寫父類方法失敗,日志:

      當(dāng)其他人使用Child1時還是會發(fā)現(xiàn)有倆setValue,讓人困惑。

      反射和泛型的這些坑,你踏空了嗎?

      重新實現(xiàn)Child2,繼承Parent時String作為泛型T類型,并使用@Override注解setValue,實現(xiàn)有效的方法重寫

      但還是出現(xiàn)重復(fù)日志

      Child2的setValue調(diào)了兩次。難道是JDK的反射出Bug了!

      通過getDeclaredMethods查找到的方法肯定來自Child2本身;而且Child2類中看起來也只有一個setValue,怎么可能還重復(fù)?

      調(diào)試發(fā)現(xiàn),Child2類其實有倆setValue:入?yún)⒎謩e是String/Object。

      這就是泛型類型擦除導(dǎo)致。

      解密反射下的泛型擦除大坑

      Java泛型類型在編譯后被擦除為Object。子類雖指定父類泛型T類型是String,但編譯后T會被擦除成為Object,所以父類setValue入?yún)⑹荗bject,value也是Object。

      若Child2 setValue想覆蓋父類,那入?yún)⒁岔殲镺bject。所以,編譯器會為我們生成一個橋接方法

      Child2類的class字節(jié)碼:

      Compiled from "GenericAndInheritanceApplication.java" class Child2 extends Parent { Child2(); Code: 0: aload_0 1: invokespecial #1 // Method Parent."":()V 4: return public void setValue(java.lang.String); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Child2.setValue called 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: aload_0 9: aload_1 10: invokespecial #5 // Method Parent.setValue:(Ljava/lang/Object;)V 13: return public void setValue(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #6 // class java/lang/String // 入?yún)镺bject的setValue在內(nèi)部調(diào)用了入?yún)镾tring的setValue方法 5: invokevirtual #7 // Method setValue:(Ljava/lang/String;)V 8: return }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      若編譯器未幫我們實現(xiàn)該橋接方法,那Child2重寫的是父類泛型類型擦除后、入?yún)⑹荗bject的setValue。這兩個方法的參數(shù),一個String一個Object,明顯不符合Java語義:

      class Parent { AtomicInteger updateCount = new AtomicInteger(); private Object value; public void setValue(Object value) { System.out.println("Parent.setValue called"); this.value = value; updateCount.incrementAndGet(); } } class Child2 extends Parent { @Override public void setValue(String value) { System.out.println("Child2.setValue called"); super.setValue(value); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      使用jclasslib打開Child2,可看到入?yún)镺bject的橋接方法上標(biāo)記public synthetic bridge。synthetic代表由編譯器生成的不可見代碼,bridge代表這是泛型類型擦除后生成的橋接代碼

      修改

      使用method的isBridge方法,來判斷方法是不是橋接方法:

      通過getDeclaredMethods方法獲取到所有方法后,必須同時根據(jù)方法名setValue和非isBridge兩個條件過濾,才能實現(xiàn)唯一過濾

      使用Stream時,如果希望只匹配0或1項的話,可以考慮配合ifPresent來使用findFirst方法。

      Arrays.stream(child2.getClass().getDeclaredMethods()) .filter(method -> method.getName().equals("setValue") && !method.isBridge()) .findFirst().ifPresent(method -> { try { method.invoke(chi2, "test"); } catch (Exception e) { e.printStackTrace(); } });

      1

      2

      3

      4

      5

      6

      7

      8

      9

      使用反射查詢類方法清單時:

      getMethods和getDeclaredMethods是有區(qū)別的,前者可以查詢到父類方法,后者只能查詢到當(dāng)前類

      反射進行方法調(diào)用要注意過濾橋接方法。

      參考

      https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html

      https://docs.oracle.com/javase/tutorial/reflect/index.html

      https://docs.oracle.com/javase/8/docs/technotes/guides/language/annotations.html

      https://docs.oracle.com/javase/tutorial/java/annotations/index.html

      https://docs.oracle.com/javase/8/docs/technotes/guides/language/generics.html

      https://docs.oracle.com/javase/tutorial/java/generics/index.html

      Java

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:JavaWeb項目部署到Linux服務(wù)器
      下一篇:瀏覽器自動化框架比較:Selenium,Puppeteer和Cypress.io
      相關(guān)文章
      亚洲成av人在片观看| 亚洲欧洲日韩在线电影| 亚洲AV无码一区二区三区网址| 亚洲精品少妇30p| 亚洲成av人片天堂网老年人 | 亚洲精品天堂在线观看| 亚洲性猛交xx乱| 亚洲狠狠综合久久| 亚洲中文久久精品无码ww16| 国产日韩成人亚洲丁香婷婷| 亚洲国产黄在线观看| va亚洲va日韩不卡在线观看| 午夜在线亚洲男人午在线| 亚洲a∨无码一区二区| 精品国产亚洲AV麻豆| 偷自拍亚洲视频在线观看| 亚洲av午夜精品一区二区三区 | 亚洲蜜芽在线精品一区| 亚洲男人的天堂在线| 亚洲福利电影一区二区?| 亚洲熟妇无码久久精品| 亚洲国产成人九九综合| 精品亚洲成a人片在线观看少妇| 亚洲国产日韩一区高清在线| 99久久精品国产亚洲| 亚洲免费人成视频观看| 亚洲无码一区二区三区| 亚洲日韩亚洲另类激情文学| 亚洲砖码砖专无区2023| 黑人粗长大战亚洲女2021国产精品成人免费视频 | 在线观看亚洲精品国产| 亚洲va久久久噜噜噜久久男同| 色拍自拍亚洲综合图区| 亚洲人成毛片线播放| 亚洲中文字幕乱码熟女在线| 亚洲av无码专区青青草原| 亚洲国产精品毛片av不卡在线| 国产AV无码专区亚洲AWWW | 亚洲精华液一二三产区| 国产亚洲女在线线精品| 亚洲中文字幕无码日韩|