vector父类类型可以存放子类吗_拼夕夕三轮面经:被问到反射和泛型的bug,你踏空了吗?...
? 點擊上方“JavaEdge”,關注公眾號
設為“星標”,好文章不錯過!1 當反射遇見方法重載
重載grade方法,入參分別為int、Integer。若不通過反射這種高級編程方式,選用哪個重載方法自然很清晰,比如傳666走int參數重載方法,傳入Integer.valueOf(“666”)走Integer重載。
但你若墨守成規認為反射調用方法也是根據入參類型確定方法重載,那就掉坑了。
使用getDeclaredMethod獲取?grade方法,然后傳入Integer.valueOf(“36”)
因為通過反射進行方法調用首先是
通過方法簽名來確定方法
本例的getDeclaredMethod傳入的參數類型Integer.TYPE其實一直代表int。
所以實際執行方法時傳包裝類型、基本類型,最終都是調用int入參的grade方法。
修正方案
將Integer.TYPE改為Integer.class,實際執行的參數類型就是Integer了。且無論傳包裝類型/基本類型,最終都會調用Integer為入參的grade方法。
所以反射調用方法,是以反射獲取方法時傳入的方法名和參數類型來確定調用的方法。
2 當泛型因類型擦除遇見橋接方法
泛型作為一種編程范式,使得開發者可以使用類型參數替代精確類型,實例化時再指明具體類型。也利于代碼重用,將一套代碼應用到多種數據類型。
泛型的類型檢測,可以在編譯時暴露大多數泛型編碼錯誤。但由于歷史兼容性而妥協的泛型類型擦除,在運行時才會暴露很多坑。
案例
期望在類字段內容變動時記錄日志,于是開發同學就想到定義一個泛型父類,并在父類中定義一個統一的日志記錄方法,子類可繼承該方法。上線后總出現日志重復記錄問題。
父類
子類Child1 未提供父類泛型參數且定義了一個參數為String而非T的setValue。期望覆蓋父類的setValue實現。
子類方法的調用是通過反射。
雖Parent的value字段正確設置JavaEdge,但父類setValue調用了兩次,計數器而顯示2兩次Parent的setValue方法調用,是因為getMethods找到了兩個setValue的,分屬于父類/子類。
子類重寫父類方法失敗原因
子類未指定String泛型參數,父類的泛型方法setValue(T value)泛型擦除后是setValue(Object value),于是子類入參String的setValue被當作了新方法
子類的setValue方法未加@Override注解,編譯器未能檢測到重寫失敗。
重寫子類方法時,務必使用@Override注解。
但有人認為問題是反射API使用不當而未意識到重寫失敗。查文檔后才發現
getMethods能獲得當前類和父類的所有public方法
getDeclaredMethods僅獲得當前類所有的public、protected、package和private方法
于是用getDeclaredMethods替換getMethods:
這雖能解決重復記錄日志,但未解決子類重寫父類方法失敗,日志:
當其他人使用Child1時還是會發現有倆setValue,讓人困惑。
重新實現Child2,繼承Parent時String作為泛型T類型,并使用@Override注解setValue,實現有效的方法重寫
還是出現重復日志
Child2的setValue調了兩次。難道是JDK的反射出Bug了!
通過getDeclaredMethods查找到的方法肯定來自Child2本身;而且Child2類中看起來也只有一個setValue,怎么可能還重復?
調試發現,Child2類其實有倆setValue:入參分別是String/Object。
這就是泛型類型擦除導致。
解密反射下的泛型擦除天坑
Java泛型類型在編譯后被擦除為Object。子類雖指定父類泛型T類型是String,但編譯后T會被擦除成為Object,所以父類setValue入參是Object,value也是Object。
若Child2?setValue想覆蓋父類,那入參也須為Object。所以,編譯器會為我們生成一個橋接方法
Child2類的class字節碼:
若編譯器未幫我們實現該橋接方法,那Child2重寫的是父類泛型類型擦除后、入參是Object的setValue。這倆方法參數,一個String一個Object,明顯不符合Java語義:
class Parent {AtomicInteger updateCount = new AtomicInteger();private Object value;public void setValue(Object value) {
System.out.println("調用 Parent 的 setValue");this.value = value;
updateCount.incrementAndGet();}}class Child2 extends Parent {@Overridepublic void setValue(String value) {
System.out.println("調用 Child2 的 setValue");super.setValue(value);}}
驗證:使用jclasslib打開Child2,可看到入參為Object的橋接方法上標記public synthetic bridge。synthetic代表由編譯器生成的不可見代碼,bridge代表這是泛型類型擦除后生成的橋接代碼
修正方案
使用method的isBridge方法,來判斷方法是不是橋接方法:
通過getDeclaredMethods方法獲取到所有方法后,必須同時根據方法名setValue和非isBridge兩個條件過濾,才能實現唯一過濾
使用Stream時,如果希望只匹配0或1項的話,可以考慮配合ifPresent來使用findFirst方法。
使用反射查詢類方法清單時:
getMethods和getDeclaredMethods是有區別的,前者可以查詢到父類方法,后者只能查詢到當前類
反射進行方法調用要注意過濾橋接方法。
往期推薦
大廠如何解決數值精度/舍入/溢出問題
大廠數據庫事務實踐-事務生效就能保證正確回滾?
線上問題事跡(一)數據庫事務居然都沒生效?
硬核干貨:HTTP超時、重復請求必見坑點及解決方案
給大忙人們看的Java NIO教程之Channel
目前交流群已有?800+人,旨在促進技術交流,可關注公眾號添加筆者微信邀請進群
喜歡文章,點個“在看、點贊、分享”素質三連支持一下~
總結
以上是生活随笔為你收集整理的vector父类类型可以存放子类吗_拼夕夕三轮面经:被问到反射和泛型的bug,你踏空了吗?...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 那些年踩过的Java异常,简直了!
- 下一篇: python中哪个符号用于从包中导入模块