Java使用泛型Class實(shí)現(xiàn)消除模板代碼
Class作為實(shí)現(xiàn)反射功能的類,在開發(fā)中經(jīng)常會(huì)用到,最常見的就是啟動(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遇上泛型后,事情就變得不是那么簡單了。當(dāng)你需要使用泛型的Class時(shí),你可能會(huì)略微感到一絲蛋疼:無法通過上面三種方法獲取到泛型的Class,因?yàn)榉盒退皇且粋€(gè)確定的具體的類,自然就無法通過上面方法獲取到Class。
舉個(gè)栗子
假設(shè)我們有這樣一個(gè)需求:用魚缸養(yǎng)魚。首先定義一個(gè)接口魚:
interface Fish { String swim(); //魚的通用動(dòng)作游泳 }
魚有很多種類,我們創(chuàng)建兩種魚:
public class GoldFish implements Fish{ @Override public String swim() { return "金魚優(yōu)雅的游動(dòng)"; } public void beautiful(){ System.out.println("金魚很漂亮"); //金魚特有的屬性 } } //魚可殺不可辱,兄弟你可以釣我但你不能侮辱我是草魚 public class GrassFish implements Fish{ @Override public String swim() { return "細(xì)紋獅子魚迅速的游動(dòng)"; } public void agile(){ System.out.println("細(xì)紋獅子魚非常敏捷"); //細(xì)紋獅子魚特有的屬性 } }
好魚有了,下面我們來創(chuàng)建魚缸,魚缸也有不同材質(zhì)的,所以將魚缸定義為抽象類,并且創(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(); }
魚缸里要有魚,但是魚缸里又不一定會(huì)養(yǎng)什么魚,因此為魚缸添加了泛型<F extends Fish>
,并聲明魚的變量F fish
。
現(xiàn)在魚缸也有了,接下來我們買一個(gè)玻璃魚缸來養(yǎng)金魚:
//創(chuàng)建(買)一個(gè)玻璃魚缸,泛型傳入(養(yǎng))金魚 public class GlassGoldFishTank extends FishTank<GoldFish>{ @Override public String material() { return "在玻璃魚缸里"; } }
將魚缸放進(jìn)屋子中,我們來看一看結(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)
不慌,問題不大,原因很簡單,變量fish沒有實(shí)例化,實(shí)例化一下即可。但此時(shí)你會(huì)發(fā)現(xiàn),無法在FishTank中實(shí)例化fish,因?yàn)樽兞縡ish的類型是泛型。嗯..問題也不大,創(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 "在玻璃魚缸里"; } }
ok,再來試一下:
在玻璃魚缸里金魚優(yōu)雅的游動(dòng)
我們還可以展示一下金魚特有的屬性:
public class GlassGoldFishTank extends FishTank<GoldFish>{ public GlassGoldFishTank() { fish.beautiful(); } @Override void initFish() { fish = new GoldFish(); } @Override String material() { return "在玻璃魚缸里"; } }
結(jié)果如下:
在玻璃魚缸里金魚優(yōu)雅的游動(dòng)
金魚很漂亮
簡直完美,無敵~
過了兩天,你看金魚看夠了,又買了個(gè)玻璃魚缸,這次養(yǎng)的是草魚
//創(chuàng)建(買)一個(gè)玻璃魚缸,泛型傳入(養(yǎng))細(xì)紋獅子魚 public class GlassGrassFishTank extends FishTank<GrassFish>{ public GlassGrassFishTank() { fish.alige(); } @Override void initFish() { fish = new GrassFish(); } @Override String material() { return "在玻璃魚缸里"; } } //放入屋子中: public class Room { public static void main(String[] args) { new GlassGoldFishTank(); new GlassGrassFishTank(); } }
試下效果:
在玻璃魚缸里金魚優(yōu)雅的游動(dòng)
金魚很漂亮
在玻璃魚缸里細(xì)紋獅子魚迅速的游動(dòng)
細(xì)紋獅子魚非常敏捷
完美~
又過兩天,你又看夠了,這回你買了個(gè)水晶魚缸來養(yǎng)金魚:
public class CrystalGoldFishTank extends FishTank<GoldFish>{ ... @Override void initFish() { fish = new GoldFish(); } ... }
又過兩天,你又看夠了,又買了個(gè)鉆石魚缸來養(yǎng)草魚:
public class DiamondGrassFishTank extends FishTank<GrassFish>{ ... @Override void initFish() { fish = new GrassFish(); } ... }
一個(gè)問題
不知道你有沒有發(fā)現(xiàn),每買一個(gè)魚缸,就要實(shí)現(xiàn)一下initFish()方法,但是這個(gè)方法里面的代碼功能是一樣的,每個(gè)魚缸的initFish()方法里面都是做的實(shí)例化fish的操作,只不過實(shí)例化的對象不同罷了。這就是典型的模板代碼,隨著魚缸越來越多,寫這些模板代碼浪費(fèi)的時(shí)間也就越來越多。其實(shí)一開始崩潰的時(shí)候你的第一反應(yīng)就是在FishTank里面實(shí)例化fish,但是又發(fā)現(xiàn)fish是泛型類型,無法實(shí)例化,沒辦法只能通過抽象方法交給子類去實(shí)例化,也就產(chǎn)生了無用的模板代碼。
其實(shí)不光是這個(gè)例子,在實(shí)際開發(fā)中也會(huì)遇到這種情況,比如我們在封裝BaseActivity的時(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一樣是模板代碼了。
如何解決
那到底有沒有辦法在基類中實(shí)例化泛型呢?答案是有的,就是使用Class類中的getGenericSuperclass()
方法。這個(gè)方法的作用是獲取該類的帶有準(zhǔn)確泛型類型的父類,它與getSuperclass()
方法的區(qū)別是getSuperclass方法返回的是寬泛的父類類型,不包含具體泛型信息。如果該類的父類不是參數(shù)化類型(即沒有泛型),那么這兩個(gè)方法的效果是一樣的。光說不容易理解,上代碼:
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í)例化了。話不多說,直接上代碼:
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對象數(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(); }
如此一來,實(shí)例化fish的代碼就只需在FishTank中寫一次,無需在每個(gè)魚缸中都實(shí)例化fish了,玻璃魚缸現(xiàn)在變成了這樣:
public class GlassFishTank extends FishTank<GrassFish>{ public GlassFishTank() { fish.agile(); } @Override String material() { return "在玻璃魚缸里"; } }
試一下效果:
在玻璃魚缸里細(xì)紋獅子魚迅速的游動(dòng)
細(xì)紋獅子魚非常敏捷
完美~
甚至還可以再簡潔一點(diǎn),將魚特有的屬性也放入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類中的方法(為了過濾掉父類中的方法)并且返回值類型為viod(為了過濾掉swim方法),說明是不同種類的魚特有的屬性,就執(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í)玻璃魚缸變成了這樣:
public class GlassFishTank extends FishTank<GoldFish>{ @Override String material() { return "在玻璃魚缸里"; } }
是不是非常簡潔了,想養(yǎng)其他魚只需修改一下泛型類型即可,完全不需要做其他任何操作??匆幌聢?zhí)行效果:
在玻璃魚缸里金魚優(yōu)雅的游動(dòng)
金魚很漂亮
灰常完美~
我們也可以將這個(gè)技巧應(yīng)用到BaseActivity中,這樣就不用每個(gè)具體業(yè)務(wù)Activity中都要寫實(shí)例化ViewModel的模板代碼了,你的代碼是不是變得更優(yōu)雅了呢~
這只是一個(gè)小技巧,并不是什么很牛逼的技術(shù),只不過上面幾個(gè)方法你可能平時(shí)很少用到或者根本沒用過,不知道有這幾個(gè)方法,當(dāng)然也就不知道可以這么實(shí)現(xiàn)了。所以我們在鉆研技術(shù)的深度時(shí),也不要太忽視技術(shù)的廣度,畢竟地基打的牢,房子才能蓋的結(jié)實(shí)漂亮。
到此這篇關(guān)于Java使用泛型Class實(shí)現(xiàn)消除模板代碼的文章就介紹到這了,更多相關(guān)Java泛型Class內(nèi)容請搜索腳本之家以前的文章或繼續(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è)常見的需求,這個(gè)功能乍一看很簡單,甚至很多初學(xué)者會(huì)覺得:"不就是加個(gè)定時(shí)器么?" 但真到了實(shí)際工作中,細(xì)節(jié)的復(fù)雜程度往往會(huì)超乎預(yù)期,本文給大家介紹了Java實(shí)現(xiàn)訂單超時(shí)自動(dòng)取消的7種方案2024-12-12Java中的轉(zhuǎn)換流、壓縮流、序列化流、打印流及應(yīng)用場景
這篇文章主要介紹了Java中的轉(zhuǎn)換流、壓縮流、序列化流、打印流及應(yīng)用場景,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06200行Java代碼如何實(shí)現(xiàn)依賴注入框架詳解
依賴注入對大家來說應(yīng)該都不陌生,下面這篇文章主要給大家介紹了關(guān)于利用200行Java代碼如何實(shí)現(xiàn)依賴注入框架的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05SecurityUtils.getSubject().getPrincipal()為null的問題
這篇文章主要介紹了SecurityUtils.getSubject().getPrincipal()為null的問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(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ù)庫配置文件明文密碼加密解密的問題
這篇文章主要介紹了Springboot數(shù)據(jù)庫配置文件明文密碼加密解密的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03詳解Spring Data JPA系列之投影(Projection)的用法
本篇文章主要介紹了詳解Spring Data JPA系列之投影(Projection)的用法,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07