最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實(shí)例化一個(gè)抽象類對(duì)象的方法教程

 更新時(shí)間:2017年12月13日 09:19:46   作者:twiceyuan  
大家都知道抽象類無(wú)法實(shí)例化,就無(wú)法創(chuàng)建對(duì)象。所以下面這篇文章主要給大家介紹了關(guān)于Java實(shí)例化一個(gè)抽象類對(duì)象的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。

前言

最近在學(xué)習(xí)的過(guò)程中,發(fā)現(xiàn)了一個(gè)問(wèn)題,抽象類在沒(méi)有實(shí)現(xiàn)所有的抽象方法前是不可以通過(guò)new來(lái)構(gòu)建該對(duì)象的,但是抽象方法卻是可以有自己的構(gòu)造方法的。這樣就把我搞糊涂了,既然有構(gòu)造方法,又不可以通過(guò)new來(lái)創(chuàng)建,那么抽象類在沒(méi)變成具體類的時(shí)候究竟可不可以實(shí)例化呢?

在Java 中抽象類是不能直接被實(shí)例化的。但是很多時(shí)候抽象類的該特點(diǎn)成為一個(gè)比較麻煩的阻礙。例如如果我想使用動(dòng)態(tài)代理來(lái)給一個(gè)抽象類賦予其執(zhí)行抽象方法的能力,就會(huì)有兩個(gè)困難:1. 動(dòng)態(tài)代理只能創(chuàng)建實(shí)現(xiàn)接口的一個(gè)代理對(duì)象,而不能是一個(gè)繼承抽象類的對(duì)象。為此標(biāo)準(zhǔn)的 JVM 中有一些實(shí)現(xiàn),例如 javassist 可以使用字節(jié)碼工具來(lái)完成這一目的(ProxyFactory)。

在 Android 中如果想構(gòu)造一個(gè)抽象類對(duì)象,恐怕只有 new ClassName() {} 或者繼承之后構(gòu)造了。但是這兩種方法都是不能由其 Class 對(duì)象直接操作的,這就導(dǎo)致一些問(wèn)題上達(dá)不到我們需要的抽象能力。

這里詳細(xì)描述一下第一段所說(shuō)的場(chǎng)景:

首先有一個(gè) interface 文件定義如下(熟悉 Android 的朋友可以看出這是一個(gè)提供給 Retrofit 生成代理對(duì)象的 Api 配置接口):

public interface RealApi { 
 @GET("api1")
 Observable<String> api1(); 
 @GET("api2")
 Observable<String> api2(); 
 @GET("api3")
 Observable<String> api3();
 //...其他方法
}

其次再寫一個(gè)抽象類,只實(shí)現(xiàn)接口的其中一個(gè)方法(用來(lái)模擬接口數(shù)據(jù)):

@MockApi
public abstract class MockApi implements RealApi {
 Observable<String> api3() {
 return Observable.just("mock data");
 }
}

然后我們需要有一個(gè)工具,例如 MockManager ,讓他結(jié)合我們已存在的 RealApi 對(duì)象和 MockApi 類,來(lái)構(gòu)造出一個(gè)混合對(duì)象,該對(duì)象在執(zhí)行 MockApi 中已經(jīng)定義的方法時(shí),為直接執(zhí)行,在 MockApi 沒(méi)有定義該方法時(shí),去調(diào)用 RealApi 的方法。其調(diào)用方式大概為:

RealApi api = MockManager.build(realApi, MockApi.class);

通過(guò) javassist,完成上述功能很簡(jiǎn)單,創(chuàng)建一個(gè) ProxyFactory 對(duì)象,設(shè)置其 Superclass 為MockApi,然后過(guò)濾抽象方法,設(shè)置 method handler 調(diào)用 realApi 對(duì)象的同名同參方法。這里就不再給出代碼實(shí)現(xiàn)。

但是在 Android 上,javassist 的該方法會(huì)拋出

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)為了動(dòng)態(tài)代碼生成的另一個(gè)方向 Annotation Processor。

使用 Annotation Processor 實(shí)現(xiàn)的話,思路就簡(jiǎn)單的多了,但過(guò)程還是有些曲折:

首先定義一個(gè)注解,用來(lái)標(biāo)記需要構(gòu)造對(duì)象的抽象類

@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.SOURCE)
public @interface MockApi {
}

Processor 根據(jù)注解來(lái)獲得類的 element 對(duì)象,該對(duì)象是一個(gè)類似 class 的對(duì)象。因?yàn)樵陬A(yù)編譯階段,class 尚未存在,此時(shí)使用 Class.forName 是不可以獲取運(yùn)行時(shí)需要的 Class 對(duì)象的,但是 Element 提供了類似 Class 反射相關(guān)的方法,也有 TypeElement、ExecutableElement 等區(qū)分。使用 Element 對(duì)象分析注解的抽象類的抽象方法有哪些,生成一個(gè)繼承該類的實(shí)現(xiàn)類(非抽象),并在該類中實(shí)現(xiàn)所有抽象方法,因?yàn)椴粫?huì)實(shí)際用到這些抽象方法,所以只需要能編譯通過(guò)就可以了,我選擇的方式是每個(gè)方法體都拋出一個(gè)異常,提示該方法為抽象方法不能直接調(diào)用。生成代碼的方法可以使用一些工具來(lái)簡(jiǎn)化工作,例如 AutoProcessor 和 JavaPoet,具體實(shí)現(xiàn)參考文尾的項(xiàng)目代碼,生成后的代碼大致像這樣:

// 生成的類名使用原類名+"$Impl"的后綴來(lái)命名,避免和其他類名沖突,后面也使用該約束進(jìn)行反射來(lái)調(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)造出一個(gè)實(shí)現(xiàn)對(duì)象。

// 獲得生成代碼構(gòu)造的對(duì)象
private static <T> T getImplObject(Class<T> cls) {
 try {
 return (T) Class.forName(cls.getName() + "$Impl").newInstance();
 } catch (Exception e) {
 return null;
 }
}

構(gòu)造一個(gè)動(dòng)態(tài)代理,傳入 RealApi 的真實(shí)對(duì)象,和上一步構(gòu)造出的抽象類的實(shí)現(xiàn)對(duì)象,根據(jù)抽象類中的定義來(lái)判斷由哪個(gè)對(duì)象代理其方法行為:如果抽象類中有定義,即該方法不是抽象方法,則抽象類的實(shí)現(xiàn)對(duì)象執(zhí)行;反之,由接口的真實(shí)對(duì)象執(zhí)行。

public static <Origin, Mock extends Origin> Origin build(final Origin origin, final Class<Mock> mockClass) {
 // 如果 Mock Class 標(biāo)記為關(guān)閉,則直接返回真實(shí)接口對(duì)象
 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);
  }
 }
 });
}

完成上述工作以后,就可以像開(kāi)頭所說(shuō)的那樣,使用 build 方法來(lái)構(gòu)造一個(gè)混合了真實(shí)接口和抽象類方法的代理對(duì)象了,雖然調(diào)用的類本質(zhì)上還是硬編碼,但是由 Annotation Processor 自動(dòng)生成免于手動(dòng)維護(hù),使用上來(lái)講和使用 Javassist 實(shí)現(xiàn)還是基本相同的。

我用本文中所屬的方法實(shí)現(xiàn)了一個(gè)模擬 retrofit 請(qǐng)求的工具(文尾有鏈接),但本質(zhì)上可以用它來(lái)實(shí)現(xiàn)很多需要構(gòu)造抽象類的需求,更多的使用場(chǎng)景還有待挖掘。

文中提到的源碼實(shí)現(xiàn)可以在項(xiàng)目 retrofit-mock-result本地下載中找到;

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Java日志API管理最佳實(shí)踐詳解

    Java日志API管理最佳實(shí)踐詳解

    這篇文章主要介紹了Java日志API管理最佳實(shí)踐詳解,記錄日志只是有效地利用日志的第一步,更重要的是如何對(duì)程序運(yùn)行時(shí)產(chǎn)生的日志進(jìn)行處理和分析。,需要的朋友可以參考下
    2019-06-06
  • Java中Date和LocalDate區(qū)別與介紹

    Java中Date和LocalDate區(qū)別與介紹

    本文主要介紹了Java中Date和LocalDate區(qū)別與介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Java為什么占用四個(gè)字節(jié)你知道嗎

    Java為什么占用四個(gè)字節(jié)你知道嗎

    這篇文章主要介紹了Java為什么占四個(gè)字節(jié),文中介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08
  • java 鍵盤輸入一個(gè)數(shù),輸出數(shù)組中指定元素的示例

    java 鍵盤輸入一個(gè)數(shù),輸出數(shù)組中指定元素的示例

    今天小編就為大家分享一篇java 鍵盤輸入一個(gè)數(shù),輸出數(shù)組中指定元素的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • java poi判斷excel是xlsx還是xls類型

    java poi判斷excel是xlsx還是xls類型

    這篇文章主要為大家詳細(xì)介紹了如何利用java poi來(lái)判斷excel是xlsx還是xls類型,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下
    2024-10-10
  • SpringBoot響應(yīng)出現(xiàn)中文亂碼的解決方法

    SpringBoot響應(yīng)出現(xiàn)中文亂碼的解決方法

    這篇文章主要介紹了SpringBoot響應(yīng)出現(xiàn)中文亂碼的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作有一定的參考價(jià)值,需要的朋友們下面隨著小編來(lái)一起來(lái)學(xué)習(xí)吧
    2024-02-02
  • Java動(dòng)態(tài)代理語(yǔ)法Proxy類原理詳解

    Java動(dòng)態(tài)代理語(yǔ)法Proxy類原理詳解

    這篇文章主要介紹了Java動(dòng)態(tài)代理語(yǔ)法Proxy類原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • POI對(duì)Excel自定義日期格式的讀取(實(shí)例代碼)

    POI對(duì)Excel自定義日期格式的讀取(實(shí)例代碼)

    下面小編就為大家?guī)?lái)一篇POI對(duì)Excel自定義日期格式的讀取(實(shí)例代碼)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-11-11
  • Java中5種輸出換行方式小結(jié)

    Java中5種輸出換行方式小結(jié)

    在Java中,輸出換行符是一項(xiàng)非常基本的操作,它在控制臺(tái)中輸出文本時(shí)非常常見(jiàn),本文主要介紹了Java中5種輸出換行方式小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • Java通過(guò) Socket 實(shí)現(xiàn) TCP服務(wù)端

    Java通過(guò) Socket 實(shí)現(xiàn) TCP服務(wù)端

    這篇文章主要介紹了Java通過(guò) Socket 實(shí)現(xiàn) TCP服務(wù)端的相關(guān)資料,需要的朋友可以參考下
    2017-05-05

最新評(píng)論

枣阳市| 湖南省| 喜德县| 黄平县| 泾源县| 凤山县| 故城县| 托克托县| 胶州市| 富民县| 晴隆县| 招远市| 塔河县| 江西省| 南平市| 平阳县| 潼南县| 额济纳旗| 高碑店市| 广州市| 河间市| 旅游| 肃南| 临江市| 志丹县| 紫金县| 保亭| 峨边| 江陵县| 凤山县| 秦安县| 正阳县| 灵丘县| 宜城市| 六枝特区| 亳州市| 太保市| 上杭县| 上栗县| 奉新县| 高邮市|