Java实例化一个抽象类对象的方法教程

 更新时间:2017年12月13日 09:19:46   作者:twiceyuan  
大家都知道抽象类无法实例化,就无法创建对象。所以下面这篇文章主要给大家介绍了关于Java实例化一个抽象类对象的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。

前言

最近在学习的过程中,发现了一个问题,抽象类在没有实现所有的抽象方法前是不可以通过new来构建该对象的,但是抽象方法却是可以有自己的构造方法的。这样就把我搞糊涂了,既然有构造方法,又不可以通过new来创建,那么抽象类在没变成具体类的时候究竟可不可以实例化呢?

在Java 中抽象类是不能直接被实例化的。但是很多时候抽象类的该特点成为一个比较麻烦的阻碍。例如如果我想使用动态代理来给一个抽象类赋予其执行抽象方法的能力,就会有两个困难:1. 动态代理只能创建实现接口的一个代理对象,而不能是一个继承抽象类的对象。为此标准的 JVM 中有一些实现,例如 javassist 可以使用字节码工具来完成这一目的(ProxyFactory)。

在 Android 中如果想构造一个抽象类对象,恐怕只有 new ClassName() {} 或者继承之后构造了。但是这两种方法都是不能由其 Class 对象直接操作的,这就导致一些问题上达不到我们需要的抽象能力。

这里详细描述一下第一段所说的场景:

首先有一个 interface 文件定义如下(熟悉 Android 的朋友可以看出这是一个提供给 Retrofit 生成代理对象的 Api 配置接口):

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

其次再写一个抽象类,只实现接口的其中一个方法(用来模拟接口数据):

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

然后我们需要有一个工具,例如 MockManager ,让他结合我们已存在的 RealApi 对象和 MockApi 类,来构造出一个混合对象,该对象在执行 MockApi 中已经定义的方法时,为直接执行,在 MockApi 没有定义该方法时,去调用 RealApi 的方法。其调用方式大概为:

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

通过 javassist,完成上述功能很简单,创建一个 ProxyFactory 对象,设置其 Superclass 为MockApi,然后过滤抽象方法,设置 method handler 调用 realApi 对象的同名同参方法。这里就不再给出代码实现。

但是在 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 上的虚拟机的实现和标准略微不同,所以这里把方向转为了动态代码生成的另一个方向 Annotation Processor。

使用 Annotation Processor 实现的话,思路就简单的多了,但过程还是有些曲折:

首先定义一个注解,用来标记需要构造对象的抽象类

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

Processor 根据注解来获得类的 element 对象,该对象是一个类似 class 的对象。因为在预编译阶段,class 尚未存在,此时使用 Class.forName 是不可以获取运行时需要的 Class 对象的,但是 Element 提供了类似 Class 反射相关的方法,也有 TypeElement、ExecutableElement 等区分。使用 Element 对象分析注解的抽象类的抽象方法有哪些,生成一个继承该类的实现类(非抽象),并在该类中实现所有抽象方法,因为不会实际用到这些抽象方法,所以只需要能编译通过就可以了,我选择的方式是每个方法体都抛出一个异常,提示该方法为抽象方法不能直接调用。生成代码的方法可以使用一些工具来简化工作,例如 AutoProcessor 和 JavaPoet,具体实现参考文尾的项目代码,生成后的代码大致像这样:

// 生成的类名使用原类名+"$Impl"的后缀来命名,避免和其他类名冲突,后面也使用该约束进行反射来调用该类
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!");
 }
}

根据该抽象类的类名去反射获得该实现类,然后再根据反射调用其构造方法构造出一个实现对象。

// 获得生成代码构造的对象
private static <T> T getImplObject(Class<T> cls) {
 try {
 return (T) Class.forName(cls.getName() + "$Impl").newInstance();
 } catch (Exception e) {
 return null;
 }
}

构造一个动态代理,传入 RealApi 的真实对象,和上一步构造出的抽象类的实现对象,根据抽象类中的定义来判断由哪个对象代理其方法行为:如果抽象类中有定义,即该方法不是抽象方法,则抽象类的实现对象执行;反之,由接口的真实对象执行。

public static <Origin, Mock extends Origin> Origin build(final Origin origin, final Class<Mock> mockClass) {
 // 如果 Mock Class 标记为关闭,则直接返回真实接口对象
 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 {  
  // 获取定义的抽象类中的同名方法,判断是否已经实现
  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 方法来构造一个混合了真实接口和抽象类方法的代理对象了,虽然调用的类本质上还是硬编码,但是由 Annotation Processor 自动生成免于手动维护,使用上来讲和使用 Javassist 实现还是基本相同的。

我用本文中所属的方法实现了一个模拟 retrofit 请求的工具(文尾有链接),但本质上可以用它来实现很多需要构造抽象类的需求,更多的使用场景还有待挖掘。

文中提到的源码实现可以在项目 retrofit-mock-result本地下载中找到;

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • Java日志API管理最佳实践详解

    Java日志API管理最佳实践详解

    这篇文章主要介绍了Java日志API管理最佳实践详解,记录日志只是有效地利用日志的第一步,更重要的是如何对程序运行时产生的日志进行处理和分析。,需要的朋友可以参考下
    2019-06-06
  • Java中Date和LocalDate区别与介绍

    Java中Date和LocalDate区别与介绍

    本文主要介绍了Java中Date和LocalDate区别与介绍,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Java为什么占用四个字节你知道吗

    Java为什么占用四个字节你知道吗

    这篇文章主要介绍了Java为什么占四个字节,文中介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • java 键盘输入一个数,输出数组中指定元素的示例

    java 键盘输入一个数,输出数组中指定元素的示例

    今天小编就为大家分享一篇java 键盘输入一个数,输出数组中指定元素的示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • java poi判断excel是xlsx还是xls类型

    java poi判断excel是xlsx还是xls类型

    这篇文章主要为大家详细介绍了如何利用java poi来判断excel是xlsx还是xls类型,文中的示例代码讲解详细,有需要的小伙伴可以参考一下
    2024-10-10
  • SpringBoot响应出现中文乱码的解决方法

    SpringBoot响应出现中文乱码的解决方法

    这篇文章主要介绍了SpringBoot响应出现中文乱码的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作有一定的参考价值,需要的朋友们下面随着小编来一起来学习吧
    2024-02-02
  • Java动态代理语法Proxy类原理详解

    Java动态代理语法Proxy类原理详解

    这篇文章主要介绍了Java动态代理语法Proxy类原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • POI对Excel自定义日期格式的读取(实例代码)

    POI对Excel自定义日期格式的读取(实例代码)

    下面小编就为大家带来一篇POI对Excel自定义日期格式的读取(实例代码)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-11-11
  • Java中5种输出换行方式小结

    Java中5种输出换行方式小结

    在Java中,输出换行符是一项非常基本的操作,它在控制台中输出文本时非常常见,本文主要介绍了Java中5种输出换行方式小结,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02
  • Java通过 Socket 实现 TCP服务端

    Java通过 Socket 实现 TCP服务端

    这篇文章主要介绍了Java通过 Socket 实现 TCP服务端的相关资料,需要的朋友可以参考下
    2017-05-05

最新评论