Java使用泛型Class實現(xiàn)消除模板代碼
Class作為實現(xiàn)反射功能的類,在開發(fā)中經(jīng)常會用到,最常見的就是啟動一個Activity: startActivity(new Intent(this,AnotherActivity.class))。眾所周知,獲取某一個類的Class有如下三種方法:
Class d1 = Demo.class;
Class d2 = new Demo().getClass();
Class d3 = Class.forName("com.xxx.xxx.Demo);然而,當Class遇上泛型后,事情就變得不是那么簡單了。當你需要使用泛型的Class時,你可能會略微感到一絲蛋疼:無法通過上面三種方法獲取到泛型的Class,因為泛型它不是一個確定的具體的類,自然就無法通過上面方法獲取到Class。
舉個栗子
假設我們有這樣一個需求:用魚缸養(yǎng)魚。首先定義一個接口魚:
interface Fish {
String swim(); //魚的通用動作游泳
}魚有很多種類,我們創(chuàng)建兩種魚:
public class GoldFish implements Fish{
@Override
public String swim() {
return "金魚優(yōu)雅的游動";
}
public void beautiful(){
System.out.println("金魚很漂亮"); //金魚特有的屬性
}
}
//魚可殺不可辱,兄弟你可以釣我但你不能侮辱我是草魚
public class GrassFish implements Fish{
@Override
public String swim() {
return "細紋獅子魚迅速的游動";
}
public void agile(){
System.out.println("細紋獅子魚非常敏捷"); //細紋獅子魚特有的屬性
}
}好魚有了,下面我們來創(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();
}魚缸里要有魚,但是魚缸里又不一定會養(yǎng)什么魚,因此為魚缸添加了泛型<F extends Fish>,并聲明魚的變量F fish。
現(xiàn)在魚缸也有了,接下來我們買一個玻璃魚缸來養(yǎng)金魚:
//創(chuàng)建(買)一個玻璃魚缸,泛型傳入(養(yǎng))金魚
public class GlassGoldFishTank extends FishTank<GoldFish>{
@Override
public String material() {
return "在玻璃魚缸里";
}
}將魚缸放進屋子中,我們來看一看結果:
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沒有實例化,實例化一下即可。但此時你會發(fā)現(xiàn),無法在FishTank中實例化fish,因為變量fish的類型是泛型。嗯..問題也不大,創(chuàng)建一個負責實例化fish的抽象方法abstract void initFish()去交給子類實現(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)雅的游動
我們還可以展示一下金魚特有的屬性:
public class GlassGoldFishTank extends FishTank<GoldFish>{
public GlassGoldFishTank() {
fish.beautiful();
}
@Override
void initFish() {
fish = new GoldFish();
}
@Override
String material() {
return "在玻璃魚缸里";
}
}結果如下:
在玻璃魚缸里金魚優(yōu)雅的游動
金魚很漂亮
簡直完美,無敵~
過了兩天,你看金魚看夠了,又買了個玻璃魚缸,這次養(yǎng)的是草魚
//創(chuàng)建(買)一個玻璃魚缸,泛型傳入(養(yǎng))細紋獅子魚
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)雅的游動
金魚很漂亮
在玻璃魚缸里細紋獅子魚迅速的游動
細紋獅子魚非常敏捷
完美~
又過兩天,你又看夠了,這回你買了個水晶魚缸來養(yǎng)金魚:
public class CrystalGoldFishTank extends FishTank<GoldFish>{
...
@Override
void initFish() {
fish = new GoldFish();
}
...
}又過兩天,你又看夠了,又買了個鉆石魚缸來養(yǎng)草魚:
public class DiamondGrassFishTank extends FishTank<GrassFish>{
...
@Override
void initFish() {
fish = new GrassFish();
}
...
}一個問題
不知道你有沒有發(fā)現(xiàn),每買一個魚缸,就要實現(xiàn)一下initFish()方法,但是這個方法里面的代碼功能是一樣的,每個魚缸的initFish()方法里面都是做的實例化fish的操作,只不過實例化的對象不同罷了。這就是典型的模板代碼,隨著魚缸越來越多,寫這些模板代碼浪費的時間也就越來越多。其實一開始崩潰的時候你的第一反應就是在FishTank里面實例化fish,但是又發(fā)現(xiàn)fish是泛型類型,無法實例化,沒辦法只能通過抽象方法交給子類去實例化,也就產(chǎn)生了無用的模板代碼。
其實不光是這個例子,在實際開發(fā)中也會遇到這種情況,比如我們在封裝BaseActivity的時候,如果項目使用MVVM架構,那么BaseActivity里就要持有ViewModel,因此就要給BaseActivity加上ViewModel的泛型:
public abstract class BaseActivity<VM extends BaseViewModel>{
protected VM mViewModel;
...
}ViewModel需要實例化,但是它的類型卻是個泛型,那一些經(jīng)驗不夠豐富的童鞋可能就不知道如何在BaseActivity中進行實例化,就只能創(chuàng)建抽象方法abstract void initViewModel()在具體子類業(yè)務Activity中去進行實例化,這就跟上面例子中的實例化fish一樣是模板代碼了。
如何解決
那到底有沒有辦法在基類中實例化泛型呢?答案是有的,就是使用Class類中的getGenericSuperclass()方法。這個方法的作用是獲取該類的帶有準確泛型類型的父類,它與getSuperclass()方法的區(qū)別是getSuperclass方法返回的是寬泛的父類類型,不包含具體泛型信息。如果該類的父類不是參數(shù)化類型(即沒有泛型),那么這兩個方法的效果是一樣的。光說不容易理解,上代碼:
public class GlassFishTank extends FishTank<GrassFish>{
@RequiresApi(api = Build.VERSION_CODES.P)
public GlassFishTank() {
//在構造方法中打印兩個方法獲取到的類
print(getClass().getGenericSuperclass().getTypeName());
print(getClass().getSuperclass().getTypeName());
}
}運行結果:
com.testapplication.FishTank<com.testapplication.GrassFish>
com.testapplication.FishTank
可以看到,getGenericSuperclass方法獲取到了代碼運行時FishTank類的泛型的準確類型,即GrassFish。既然知道了準確的泛型類型了,那么就可以獲取到泛型的Class,獲取到了泛型的Class,自然就可以實例化了。話不多說,直接上代碼:
public abstract class FishTank<F extends Fish> {
F fish;
@RequiresApi(api = Build.VERSION_CODES.P)
public FishTank() {
initFish();
}
private void initFish(){
// ① 獲取該類的帶有準確泛型類型的父類
Type genericSuperclass = getClass().getGenericSuperclass();
// ② 判斷父類是否是參數(shù)化類型。由于本例中父類必定是參數(shù)化類型,因此也可以去掉②③步,在①中直接聲明變量為ParameterizedType類型
if (genericSuperclass instanceof ParameterizedType){
// ③ 如果是參數(shù)化類型,就將genericSuperclass強轉為ParameterizedType
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
// ④ 獲取泛型類型的Class對象數(shù)組。因為可能有多個泛型,所以返回的是數(shù)組
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
//本例中只有一個泛型,數(shù)組中只有一個元素,所以直接取數(shù)組的第一個元素即可
Class<F> fishClass = (Class<F>) actualTypeArguments[0];
try {
// ⑤ 實例化fish變量
fish = fishClass.newInstance();
show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void show(){
print(material() + fish.swim());
}
abstract String material();
}如此一來,實例化fish的代碼就只需在FishTank中寫一次,無需在每個魚缸中都實例化fish了,玻璃魚缸現(xiàn)在變成了這樣:
public class GlassFishTank extends FishTank<GrassFish>{
public GlassFishTank() {
fish.agile();
}
@Override
String material() {
return "在玻璃魚缸里";
}
}試一下效果:
在玻璃魚缸里細紋獅子魚迅速的游動
細紋獅子魚非常敏捷
完美~
甚至還可以再簡潔一點,將魚特有的屬性也放入FishTank中:
public abstract class FishTank<F extends Fish> {
...
try {
// ⑤ 實例化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();
}此時玻璃魚缸變成了這樣:
public class GlassFishTank extends FishTank<GoldFish>{
@Override
String material() {
return "在玻璃魚缸里";
}
}是不是非常簡潔了,想養(yǎng)其他魚只需修改一下泛型類型即可,完全不需要做其他任何操作??匆幌聢?zhí)行效果:
在玻璃魚缸里金魚優(yōu)雅的游動
金魚很漂亮
灰常完美~
我們也可以將這個技巧應用到BaseActivity中,這樣就不用每個具體業(yè)務Activity中都要寫實例化ViewModel的模板代碼了,你的代碼是不是變得更優(yōu)雅了呢~
這只是一個小技巧,并不是什么很牛逼的技術,只不過上面幾個方法你可能平時很少用到或者根本沒用過,不知道有這幾個方法,當然也就不知道可以這么實現(xiàn)了。所以我們在鉆研技術的深度時,也不要太忽視技術的廣度,畢竟地基打的牢,房子才能蓋的結實漂亮。
到此這篇關于Java使用泛型Class實現(xiàn)消除模板代碼的文章就介紹到這了,更多相關Java泛型Class內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java 字節(jié)數(shù)組(byte[])和整型(int)的相互轉換
在Java編程中,有時需要將字節(jié)類型(byte)轉換為整數(shù)類型(int),或者反過來轉換,本文主要介紹了Java 字節(jié)數(shù)組(byte[])和整型(int)的相互轉換,感興趣的可以了解一下2023-12-12
關于mybatis callSettersOnNulls 配置解析
這篇文章主要介紹了關于mybatis callSettersOnNulls 配置,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下2018-06-06
SpringBoot Actuator監(jiān)控的項目實踐
本文主要結合 Spring Boot Actuator,跟大家一起分享微服務Spring Boot Actuator 的常見用法,方便我們在日常中對我們的微服務進行監(jiān)控治理,感興趣的可以了解一下2024-01-01
SpringBoot整合flyway實現(xiàn)自動創(chuàng)建表的方法
這篇文章主要介紹了SpringBoot整合flyway實現(xiàn)自動創(chuàng)建表的方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
Spring Data JPA使用Sort進行排序(Using Sort)
本篇文章主要介紹了Spring Data JPA使用Sort進行排序(Using Sort),具有一定的參考價值,有興趣的可以了解一下2017-07-07
關于Spring?Validation數(shù)據(jù)校檢的使用流程分析
在實際項目中,對客戶端傳遞到服務端的參數(shù)進行校驗至關重要,SpringValidation提供了一種便捷的方式來實現(xiàn)這一需求,通過在POJO類的屬性上添加檢查注解,本文給大家介紹Spring?Validation數(shù)據(jù)校檢的使用流程,感興趣的朋友一起看看吧2024-11-11

