Java實(shí)例化一個抽象類對象的方法教程
前言
最近在學(xué)習(xí)的過程中,發(fā)現(xiàn)了一個問題,抽象類在沒有實(shí)現(xiàn)所有的抽象方法前是不可以通過new來構(gòu)建該對象的,但是抽象方法卻是可以有自己的構(gòu)造方法的。這樣就把我搞糊涂了,既然有構(gòu)造方法,又不可以通過new來創(chuàng)建,那么抽象類在沒變成具體類的時候究竟可不可以實(shí)例化呢?
在Java 中抽象類是不能直接被實(shí)例化的。但是很多時候抽象類的該特點(diǎn)成為一個比較麻煩的阻礙。例如如果我想使用動態(tài)代理來給一個抽象類賦予其執(zhí)行抽象方法的能力,就會有兩個困難:1. 動態(tài)代理只能創(chuàng)建實(shí)現(xiàn)接口的一個代理對象,而不能是一個繼承抽象類的對象。為此標(biāo)準(zhǔn)的 JVM 中有一些實(shí)現(xiàn),例如 javassist 可以使用字節(jié)碼工具來完成這一目的(ProxyFactory)。
在 Android 中如果想構(gòu)造一個抽象類對象,恐怕只有 new ClassName() {}
或者繼承之后構(gòu)造了。但是這兩種方法都是不能由其 Class 對象直接操作的,這就導(dǎo)致一些問題上達(dá)不到我們需要的抽象能力。
這里詳細(xì)描述一下第一段所說的場景:
首先有一個 interface 文件定義如下(熟悉 Android 的朋友可以看出這是一個提供給 Retrofit 生成代理對象的 Api 配置接口):
public interface RealApi { @GET("api1") Observable<String> api1(); @GET("api2") Observable<String> api2(); @GET("api3") Observable<String> api3(); //...其他方法 }
其次再寫一個抽象類,只實(shí)現(xiàn)接口的其中一個方法(用來模擬接口數(shù)據(jù)):
@MockApi public abstract class MockApi implements RealApi { Observable<String> api3() { return Observable.just("mock data"); } }
然后我們需要有一個工具,例如 MockManager ,讓他結(jié)合我們已存在的 RealApi 對象和 MockApi 類,來構(gòu)造出一個混合對象,該對象在執(zhí)行 MockApi 中已經(jīng)定義的方法時,為直接執(zhí)行,在 MockApi 沒有定義該方法時,去調(diào)用 RealApi 的方法。其調(diào)用方式大概為:
RealApi api = MockManager.build(realApi, MockApi.class);
通過 javassist,完成上述功能很簡單,創(chuàng)建一個 ProxyFactory 對象,設(shè)置其 Superclass 為MockApi,然后過濾抽象方法,設(shè)置 method handler 調(diào)用 realApi 對象的同名同參方法。這里就不再給出代碼實(shí)現(xiàn)。
但是在 Android 上,javassist 的該方法會拋出
Caused by: java.lang.UnsupportedOperationException: can't load this type of class file at java.lang.ClassLoader.defineClass(ClassLoader.java:520) at java.lang.reflect.Method.invoke(Native Method) at javassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.java:182)
類似的異常。原因大概是 Android 上的虛擬機(jī)的實(shí)現(xiàn)和標(biāo)準(zhǔn)略微不同,所以這里把方向轉(zhuǎn)為了動態(tài)代碼生成的另一個方向 Annotation Processor。
使用 Annotation Processor 實(shí)現(xiàn)的話,思路就簡單的多了,但過程還是有些曲折:
首先定義一個注解,用來標(biāo)記需要構(gòu)造對象的抽象類
@Target(ElementType.TYPE) @Documented @Retention(RetentionPolicy.SOURCE) public @interface MockApi { }
Processor 根據(jù)注解來獲得類的 element 對象,該對象是一個類似 class 的對象。因?yàn)樵陬A(yù)編譯階段,class 尚未存在,此時使用 Class.forName
是不可以獲取運(yùn)行時需要的 Class 對象的,但是 Element 提供了類似 Class 反射相關(guān)的方法,也有 TypeElement、ExecutableElement 等區(qū)分。使用 Element 對象分析注解的抽象類的抽象方法有哪些,生成一個繼承該類的實(shí)現(xiàn)類(非抽象),并在該類中實(shí)現(xiàn)所有抽象方法,因?yàn)椴粫?shí)際用到這些抽象方法,所以只需要能編譯通過就可以了,我選擇的方式是每個方法體都拋出一個異常,提示該方法為抽象方法不能直接調(diào)用。生成代碼的方法可以使用一些工具來簡化工作,例如 AutoProcessor 和 JavaPoet,具體實(shí)現(xiàn)參考文尾的項(xiàng)目代碼,生成后的代碼大致像這樣:
// 生成的類名使用原類名+"$Impl"的后綴來命名,避免和其他類名沖突,后面也使用該約束進(jìn)行反射來調(diào)用該類 public final class MockApi$Impl extends MockApi { @Override public Observable<String> api1() { throw new IllegalStateException("api1() is an abstract method!"); } @Override public Observable<String> api2() { throw new IllegalStateException("api2() is an abstract method!"); } }
根據(jù)該抽象類的類名去反射獲得該實(shí)現(xiàn)類,然后再根據(jù)反射調(diào)用其構(gòu)造方法構(gòu)造出一個實(shí)現(xiàn)對象。
// 獲得生成代碼構(gòu)造的對象 private static <T> T getImplObject(Class<T> cls) { try { return (T) Class.forName(cls.getName() + "$Impl").newInstance(); } catch (Exception e) { return null; } }
構(gòu)造一個動態(tài)代理,傳入 RealApi 的真實(shí)對象,和上一步構(gòu)造出的抽象類的實(shí)現(xiàn)對象,根據(jù)抽象類中的定義來判斷由哪個對象代理其方法行為:如果抽象類中有定義,即該方法不是抽象方法,則抽象類的實(shí)現(xiàn)對象執(zhí)行;反之,由接口的真實(shí)對象執(zhí)行。
public static <Origin, Mock extends Origin> Origin build(final Origin origin, final Class<Mock> mockClass) { // 如果 Mock Class 標(biāo)記為關(guān)閉,則直接返回真實(shí)接口對象 if (!isEnable(mockClass)) { return origin; } final Mock mockObject = getImplObject(mockClass); Class<?> originClass = origin.getClass().getInterfaces()[0]; return (Origin) Proxy.newProxyInstance(originClass.getClassLoader(), new Class[]{originClass}, new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { // 獲取定義的抽象類中的同名方法,判斷是否已經(jīng)實(shí)現(xiàn) Method mockMethod = null; try { mockMethod = mockClass.getDeclaredMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException ignored) { } if (mockMethod == null || Modifier.isAbstract(mockMethod.getModifiers())) { return method.invoke(origin, objects); } else { return mockMethod.invoke(mockObject, objects); } } }); }
完成上述工作以后,就可以像開頭所說的那樣,使用 build 方法來構(gòu)造一個混合了真實(shí)接口和抽象類方法的代理對象了,雖然調(diào)用的類本質(zhì)上還是硬編碼,但是由 Annotation Processor 自動生成免于手動維護(hù),使用上來講和使用 Javassist 實(shí)現(xiàn)還是基本相同的。
我用本文中所屬的方法實(shí)現(xiàn)了一個模擬 retrofit 請求的工具(文尾有鏈接),但本質(zhì)上可以用它來實(shí)現(xiàn)很多需要構(gòu)造抽象類的需求,更多的使用場景還有待挖掘。
文中提到的源碼實(shí)現(xiàn)可以在項(xiàng)目 retrofit-mock-result 或本地下載中找到;
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Java的Spring框架中DAO數(shù)據(jù)訪問對象的使用示例
這篇文章主要介紹了Java的Spring框架中DAO數(shù)據(jù)訪問對象的使用示例,分為在Spring中DOA與JDBC以及與Hibernate的配合使用兩種情況來進(jìn)行演示,需要的朋友可以參考下2016-03-03spring mvc實(shí)現(xiàn)登錄賬號單瀏覽器登錄
這篇文章主要為大家詳細(xì)介紹了spring mvc實(shí)現(xiàn)登錄賬號單瀏覽器登錄,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04Mybatis-Plus-AutoGenerator 最詳細(xì)使用方法
這篇文章主要介紹了Mybatis-Plus-AutoGenerator 最詳細(xì)使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Java中EasyPoi導(dǎo)出復(fù)雜合并單元格的方法
這篇文章主要介紹了Java中EasyPoi導(dǎo)出復(fù)雜合并單元格的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01mybatisplus實(shí)現(xiàn)自動填充時間的項(xiàng)目實(shí)踐
在數(shù)據(jù)庫操作中,頻繁設(shè)置創(chuàng)建時間和更新時間字段非常繁瑣,通過使用MyBatis-Plus的自動填充功能,可以簡化操作,本文就來詳細(xì)的介紹一下,感興趣的可以了解一下2024-10-10給@Value設(shè)置默認(rèn)值以及為static變量賦值問題
在Spring框架中,@Value注解用于屬性注入,可將配置文件中的值賦給變量,未指定默認(rèn)值時,若配置文件缺少相應(yīng)屬性,程序啟動會報錯,可通過設(shè)定默認(rèn)值防止此問題,對于靜態(tài)變量,由于@Value無法直接注入,需通過Set方法賦值,該方法也支持默認(rèn)值設(shè)置2024-09-09