Java使用泛型Class實(shí)現(xiàn)消除模板代碼
Class作為實(shí)現(xiàn)反射功能的類,在開(kāi)發(fā)中經(jīng)常會(huì)用到,最常見(jiàn)的就是啟動(dòng)一個(gè)Activity: startActivity(new Intent(this,AnotherActivity.class))
。眾所周知,獲取某一個(gè)類的Class有如下三種方法:
Class d1 = Demo.class; Class d2 = new Demo().getClass(); Class d3 = Class.forName("com.xxx.xxx.Demo);
然而,當(dāng)Class遇上泛型后,事情就變得不是那么簡(jiǎn)單了。當(dāng)你需要使用泛型的Class時(shí),你可能會(huì)略微感到一絲蛋疼:無(wú)法通過(guò)上面三種方法獲取到泛型的Class,因?yàn)榉盒退皇且粋€(gè)確定的具體的類,自然就無(wú)法通過(guò)上面方法獲取到Class。
舉個(gè)栗子
假設(shè)我們有這樣一個(gè)需求:用魚(yú)缸養(yǎng)魚(yú)。首先定義一個(gè)接口魚(yú):
interface Fish { String swim(); //魚(yú)的通用動(dòng)作游泳 }
魚(yú)有很多種類,我們創(chuàng)建兩種魚(yú):
public class GoldFish implements Fish{ @Override public String swim() { return "金魚(yú)優(yōu)雅的游動(dòng)"; } public void beautiful(){ System.out.println("金魚(yú)很漂亮"); //金魚(yú)特有的屬性 } } //魚(yú)可殺不可辱,兄弟你可以釣我但你不能侮辱我是草魚(yú) public class GrassFish implements Fish{ @Override public String swim() { return "細(xì)紋獅子魚(yú)迅速的游動(dòng)"; } public void agile(){ System.out.println("細(xì)紋獅子魚(yú)非常敏捷"); //細(xì)紋獅子魚(yú)特有的屬性 } }
好魚(yú)有了,下面我們來(lái)創(chuàng)建魚(yú)缸,魚(yú)缸也有不同材質(zhì)的,所以將魚(yú)缸定義為抽象類,并且創(chuàng)建抽象方法abstract String material()
:
public abstract class FishTank<F extends Fish> { F fish; public FishTank() { show(); } private void show(){ System.out.println(material() + fish.swim()); } abstract String material(); }
魚(yú)缸里要有魚(yú),但是魚(yú)缸里又不一定會(huì)養(yǎng)什么魚(yú),因此為魚(yú)缸添加了泛型<F extends Fish>
,并聲明魚(yú)的變量F fish
。
現(xiàn)在魚(yú)缸也有了,接下來(lái)我們買一個(gè)玻璃魚(yú)缸來(lái)養(yǎng)金魚(yú):
//創(chuàng)建(買)一個(gè)玻璃魚(yú)缸,泛型傳入(養(yǎng))金魚(yú) public class GlassGoldFishTank extends FishTank<GoldFish>{ @Override public String material() { return "在玻璃魚(yú)缸里"; } }
將魚(yú)缸放進(jìn)屋子中,我們來(lái)看一看結(jié)果:
public class Room { public static void main(String[] args) { new GlassFishTank(); } }
發(fā)現(xiàn)崩潰了...
Exception in thread "main" java.lang.NullPointerException
at com.testapplication.FishTank.show(FishTank.java:10)
at com.testapplication.FishTank.<init>(FishTank.java:6)
at com.testapplication.GlassGoldFishTank.<init>(GlassGoldFishTank.java:3)
at com.testapplication.Room.main(Room.java:13)
不慌,問(wèn)題不大,原因很簡(jiǎn)單,變量fish沒(méi)有實(shí)例化,實(shí)例化一下即可。但此時(shí)你會(huì)發(fā)現(xiàn),無(wú)法在FishTank中實(shí)例化fish,因?yàn)樽兞縡ish的類型是泛型。嗯..問(wèn)題也不大,創(chuàng)建一個(gè)負(fù)責(zé)實(shí)例化fish的抽象方法abstract void initFish()
去交給子類實(shí)現(xiàn)即可:
public abstract class FishTank<F extends Fish> { F fish; public FishTank() { initFish(); show(); } private void show(){ System.out.println(material() + fish.swim()); } abstract void initFish(); abstract String material(); } public class GlassGoldFishTank extends FishTank<GoldFish>{ @Override public void initFish(){ fish = new GoldFish(); } @Override public String material() { return "在玻璃魚(yú)缸里"; } }
ok,再來(lái)試一下:
在玻璃魚(yú)缸里金魚(yú)優(yōu)雅的游動(dòng)
我們還可以展示一下金魚(yú)特有的屬性:
public class GlassGoldFishTank extends FishTank<GoldFish>{ public GlassGoldFishTank() { fish.beautiful(); } @Override void initFish() { fish = new GoldFish(); } @Override String material() { return "在玻璃魚(yú)缸里"; } }
結(jié)果如下:
在玻璃魚(yú)缸里金魚(yú)優(yōu)雅的游動(dòng)
金魚(yú)很漂亮
簡(jiǎn)直完美,無(wú)敵~
過(guò)了兩天,你看金魚(yú)看夠了,又買了個(gè)玻璃魚(yú)缸,這次養(yǎng)的是草魚(yú)
//創(chuàng)建(買)一個(gè)玻璃魚(yú)缸,泛型傳入(養(yǎng))細(xì)紋獅子魚(yú) public class GlassGrassFishTank extends FishTank<GrassFish>{ public GlassGrassFishTank() { fish.alige(); } @Override void initFish() { fish = new GrassFish(); } @Override String material() { return "在玻璃魚(yú)缸里"; } } //放入屋子中: public class Room { public static void main(String[] args) { new GlassGoldFishTank(); new GlassGrassFishTank(); } }
試下效果:
在玻璃魚(yú)缸里金魚(yú)優(yōu)雅的游動(dòng)
金魚(yú)很漂亮
在玻璃魚(yú)缸里細(xì)紋獅子魚(yú)迅速的游動(dòng)
細(xì)紋獅子魚(yú)非常敏捷
完美~
又過(guò)兩天,你又看夠了,這回你買了個(gè)水晶魚(yú)缸來(lái)養(yǎng)金魚(yú):
public class CrystalGoldFishTank extends FishTank<GoldFish>{ ... @Override void initFish() { fish = new GoldFish(); } ... }
又過(guò)兩天,你又看夠了,又買了個(gè)鉆石魚(yú)缸來(lái)養(yǎng)草魚(yú):
public class DiamondGrassFishTank extends FishTank<GrassFish>{ ... @Override void initFish() { fish = new GrassFish(); } ... }
一個(gè)問(wèn)題
不知道你有沒(méi)有發(fā)現(xiàn),每買一個(gè)魚(yú)缸,就要實(shí)現(xiàn)一下initFish()方法,但是這個(gè)方法里面的代碼功能是一樣的,每個(gè)魚(yú)缸的initFish()方法里面都是做的實(shí)例化fish的操作,只不過(guò)實(shí)例化的對(duì)象不同罷了。這就是典型的模板代碼,隨著魚(yú)缸越來(lái)越多,寫(xiě)這些模板代碼浪費(fèi)的時(shí)間也就越來(lái)越多。其實(shí)一開(kāi)始崩潰的時(shí)候你的第一反應(yīng)就是在FishTank里面實(shí)例化fish,但是又發(fā)現(xiàn)fish是泛型類型,無(wú)法實(shí)例化,沒(méi)辦法只能通過(guò)抽象方法交給子類去實(shí)例化,也就產(chǎn)生了無(wú)用的模板代碼。
其實(shí)不光是這個(gè)例子,在實(shí)際開(kāi)發(fā)中也會(huì)遇到這種情況,比如我們?cè)诜庋bBaseActivity的時(shí)候,如果項(xiàng)目使用MVVM架構(gòu),那么BaseActivity里就要持有ViewModel,因此就要給BaseActivity加上ViewModel的泛型:
public abstract class BaseActivity<VM extends BaseViewModel>{ protected VM mViewModel; ... }
ViewModel需要實(shí)例化,但是它的類型卻是個(gè)泛型,那一些經(jīng)驗(yàn)不夠豐富的童鞋可能就不知道如何在BaseActivity中進(jìn)行實(shí)例化,就只能創(chuàng)建抽象方法abstract void initViewModel()
在具體子類業(yè)務(wù)Activity中去進(jìn)行實(shí)例化,這就跟上面例子中的實(shí)例化fish一樣是模板代碼了。
如何解決
那到底有沒(méi)有辦法在基類中實(shí)例化泛型呢?答案是有的,就是使用Class類中的getGenericSuperclass()
方法。這個(gè)方法的作用是獲取該類的帶有準(zhǔn)確泛型類型的父類,它與getSuperclass()
方法的區(qū)別是getSuperclass方法返回的是寬泛的父類類型,不包含具體泛型信息。如果該類的父類不是參數(shù)化類型(即沒(méi)有泛型),那么這兩個(gè)方法的效果是一樣的。光說(shuō)不容易理解,上代碼:
public class GlassFishTank extends FishTank<GrassFish>{ @RequiresApi(api = Build.VERSION_CODES.P) public GlassFishTank() { //在構(gòu)造方法中打印兩個(gè)方法獲取到的類 print(getClass().getGenericSuperclass().getTypeName()); print(getClass().getSuperclass().getTypeName()); } }
運(yùn)行結(jié)果:
com.testapplication.FishTank<com.testapplication.GrassFish>
com.testapplication.FishTank
可以看到,getGenericSuperclass方法獲取到了代碼運(yùn)行時(shí)FishTank類的泛型的準(zhǔn)確類型,即GrassFish。既然知道了準(zhǔn)確的泛型類型了,那么就可以獲取到泛型的Class,獲取到了泛型的Class,自然就可以實(shí)例化了。話不多說(shuō),直接上代碼:
public abstract class FishTank<F extends Fish> { F fish; @RequiresApi(api = Build.VERSION_CODES.P) public FishTank() { initFish(); } private void initFish(){ // ① 獲取該類的帶有準(zhǔn)確泛型類型的父類 Type genericSuperclass = getClass().getGenericSuperclass(); // ② 判斷父類是否是參數(shù)化類型。由于本例中父類必定是參數(shù)化類型,因此也可以去掉②③步,在①中直接聲明變量為ParameterizedType類型 if (genericSuperclass instanceof ParameterizedType){ // ③ 如果是參數(shù)化類型,就將genericSuperclass強(qiáng)轉(zhuǎn)為ParameterizedType ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; // ④ 獲取泛型類型的Class對(duì)象數(shù)組。因?yàn)榭赡苡卸鄠€(gè)泛型,所以返回的是數(shù)組 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); //本例中只有一個(gè)泛型,數(shù)組中只有一個(gè)元素,所以直接取數(shù)組的第一個(gè)元素即可 Class<F> fishClass = (Class<F>) actualTypeArguments[0]; try { // ⑤ 實(shí)例化fish變量 fish = fishClass.newInstance(); show(); } catch (Exception e) { e.printStackTrace(); } } } private void show(){ print(material() + fish.swim()); } abstract String material(); }
如此一來(lái),實(shí)例化fish的代碼就只需在FishTank中寫(xiě)一次,無(wú)需在每個(gè)魚(yú)缸中都實(shí)例化fish了,玻璃魚(yú)缸現(xiàn)在變成了這樣:
public class GlassFishTank extends FishTank<GrassFish>{ public GlassFishTank() { fish.agile(); } @Override String material() { return "在玻璃魚(yú)缸里"; } }
試一下效果:
在玻璃魚(yú)缸里細(xì)紋獅子魚(yú)迅速的游動(dòng)
細(xì)紋獅子魚(yú)非常敏捷
完美~
甚至還可以再簡(jiǎn)潔一點(diǎn),將魚(yú)特有的屬性也放入FishTank中:
public abstract class FishTank<F extends Fish> { ... try { // ⑤ 實(shí)例化fish變量 fish = fishClass.newInstance(); show(); // ⑥ 獲取fish類中的所有方法并遍歷 Method[] methods = fishClass.getMethods(); for (int i = 0; i < methods.length; i++) { //如果該方法是fish類中的方法(為了過(guò)濾掉父類中的方法)并且返回值類型為viod(為了過(guò)濾掉swim方法),說(shuō)明是不同種類的魚(yú)特有的屬性,就執(zhí)行該方法 if (methods[i].getDeclaringClass() == fishClass && methods[i].getReturnType() == void.class) methods[i].invoke(fish); } } private void show(){ print(material() + fish.swim()); } abstract String material(); }
此時(shí)玻璃魚(yú)缸變成了這樣:
public class GlassFishTank extends FishTank<GoldFish>{ @Override String material() { return "在玻璃魚(yú)缸里"; } }
是不是非常簡(jiǎn)潔了,想養(yǎng)其他魚(yú)只需修改一下泛型類型即可,完全不需要做其他任何操作。看一下執(zhí)行效果:
在玻璃魚(yú)缸里金魚(yú)優(yōu)雅的游動(dòng)
金魚(yú)很漂亮
灰常完美~
我們也可以將這個(gè)技巧應(yīng)用到BaseActivity中,這樣就不用每個(gè)具體業(yè)務(wù)Activity中都要寫(xiě)實(shí)例化ViewModel的模板代碼了,你的代碼是不是變得更優(yōu)雅了呢~
這只是一個(gè)小技巧,并不是什么很牛逼的技術(shù),只不過(guò)上面幾個(gè)方法你可能平時(shí)很少用到或者根本沒(méi)用過(guò),不知道有這幾個(gè)方法,當(dāng)然也就不知道可以這么實(shí)現(xiàn)了。所以我們?cè)阢@研技術(shù)的深度時(shí),也不要太忽視技術(shù)的廣度,畢竟地基打的牢,房子才能蓋的結(jié)實(shí)漂亮。
到此這篇關(guān)于Java使用泛型Class實(shí)現(xiàn)消除模板代碼的文章就介紹到這了,更多相關(guān)Java泛型Class內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java并發(fā)編程之LongAdder執(zhí)行情況解析
這篇文章主要為大家介紹了Java并發(fā)編程之LongAdder執(zhí)行情況解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04深入解析java HashMap實(shí)現(xiàn)原理
這篇文章主要介紹了深入解析java HashMap實(shí)現(xiàn)原理的相關(guān)資料,需要的朋友可以參考下2015-09-09Java實(shí)現(xiàn)訂單超時(shí)自動(dòng)取消的7種方案
在電商、外賣、票務(wù)等系統(tǒng)中,訂單超時(shí)未支付自動(dòng)取消是一個(gè)常見(jiàn)的需求,這個(gè)功能乍一看很簡(jiǎn)單,甚至很多初學(xué)者會(huì)覺(jué)得:"不就是加個(gè)定時(shí)器么?" 但真到了實(shí)際工作中,細(xì)節(jié)的復(fù)雜程度往往會(huì)超乎預(yù)期,本文給大家介紹了Java實(shí)現(xiàn)訂單超時(shí)自動(dòng)取消的7種方案2024-12-12Java中的轉(zhuǎn)換流、壓縮流、序列化流、打印流及應(yīng)用場(chǎng)景
這篇文章主要介紹了Java中的轉(zhuǎn)換流、壓縮流、序列化流、打印流及應(yīng)用場(chǎng)景,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06200行Java代碼如何實(shí)現(xiàn)依賴注入框架詳解
依賴注入對(duì)大家來(lái)說(shuō)應(yīng)該都不陌生,下面這篇文章主要給大家介紹了關(guān)于利用200行Java代碼如何實(shí)現(xiàn)依賴注入框架的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05SecurityUtils.getSubject().getPrincipal()為null的問(wèn)題
這篇文章主要介紹了SecurityUtils.getSubject().getPrincipal()為null的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07java中Class類的基礎(chǔ)知識(shí)點(diǎn)及實(shí)例
在本篇文章里小編給大家分享了關(guān)于java中Class類的基礎(chǔ)知識(shí)點(diǎn)及實(shí)例內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2021-05-05關(guān)于Springboot數(shù)據(jù)庫(kù)配置文件明文密碼加密解密的問(wèn)題
這篇文章主要介紹了Springboot數(shù)據(jù)庫(kù)配置文件明文密碼加密解密的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03詳解Spring Data JPA系列之投影(Projection)的用法
本篇文章主要介紹了詳解Spring Data JPA系列之投影(Projection)的用法,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07