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