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接口和抽象类用法实例总结
  • Java中的接口和抽象类用法实例详解
  • JAVA 继承基本类、抽象类、接口介绍
  • Java接口和抽象类实例分析
  • java 抽象类和接口的区别详细解析
  • java 抽象类与接口的区别总结
  • Java设计模式编程中的工厂方法模式和抽象工厂模式
  • Java 抽象类定义与方法实例详解
  • java中抽象类、抽象方法、接口与实现接口实例详解
  • Java抽象类概念与用法实例分析
(0)

相关推荐

  • Java设计模式编程中的工厂方法模式和抽象工厂模式

    工厂方法模式 动机 创建一个对象往往需要复杂的过程,所以不适合包含在一个复合工厂中,当有新的产品时,需要修改这个复合的工厂,不利于扩展. 而且,有些对象的创建可以需要用到复合工厂访问不到的信息,所以,定义一个工厂接口,通过实现这个接口来决定实例化那个产品,这就是工厂方法模式,让类的实例化推迟到子类中进行. 目的 1. 定义一个接口,让子类决定实例化哪个产品. 2. 通过通用接口创建对象. 实现 1. 产品接口和具体产品很好理解. 2. 工厂类提供一个工厂方法,返回一个产品对象.但是这个工厂方法是

  • java 抽象类与接口的区别总结

    java 抽象类与接口的区别总结 abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力. abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于 abstract class和interface 选择显得比较随意. 其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的 理解

  • JAVA 继承基本类、抽象类、接口介绍

    封装:就是把一些属性和方法封装到一个类里. 继承:就如子类继承父类的一些属性和方法. 多态:就如一个父类有多个不同特色的子类. 这里我就不多讲解,下面我主要说明一个继承.继承是OOP(面向对象)的一个特色,java只支持单继承(如果继承两个有同样方法的父类,那么就不知道继承到那个父类的,所以java只支持单继承).继承是java的一个特色,我们用的所以类都继承Objict类,所以就要Object类的方法,如toString().getClass().wait()--所以我们建立的类都有父类. J

  • java中抽象类、抽象方法、接口与实现接口实例详解

    前言 对于java中的抽象类,抽象方法,接口,实现接口等具体的概念就不在这里详细的说明了,网上书本都有很多解释,主要是我懒,下面通过一个例子来说明其中的精髓要点,能不能练成绝世武功,踏上封王之路,就看自己的的啦(不要误会,我指的只是我自己啦啦) 用接口实现一个简单的计算器 1.利用接口做参数,写个计算器,能完成+-*/运算 (1)定义一个接口Compute含有一个方法int computer(int n,int m); (2)设计四个类分别实现此接口,完成+-*/运算 (3)设计一个类UseCo

  • java 抽象类和接口的区别详细解析

    abstractclass和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力.abstractclass和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstractclass和interface的选择显得比较随意.其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解.对于设计意图的理解是否正确.合理.本文将对它们

  • Java接口和抽象类用法实例总结

    本文实例讲述了Java接口和抽象类用法.分享给大家供大家参考,具体如下: 接口 1 因为java不支持多重继承,所以有了接口,一个类只能继承一个父类,但可以实现多个接口,接口本身也可以继承多个接口. 2 接口里面的成员变量默认都是public static final类型的.必须被显示的初始化. 3 接口里面的方法默认都是public abstract类型的.隐式声明. 4 接口没有构造方法,不能被实例化. 5 接口不能实现另一个接口,但可以继承多个接口. 6 类如果实现了一个接口,那么必须实现

  • Java中的接口和抽象类用法实例详解

    本文实例讲述了Java中的接口和抽象类用法.分享给大家供大家参考,具体如下: 在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 抽象类往往用来表征我们在对问题领域进行分析. 设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象,我们不能把它们实例化(拿不出一个具体的东西)所以称之为抽象. 比如:我们要描述"水果",它就是一个抽象,它有质量.体积等

  • Java接口和抽象类实例分析

    本文实例讲述了Java的接口和抽象类.分享给大家供大家参考.具体分析如下: 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类. 若有不正之处,请多多谅解并欢迎批评指正,不甚感激. 一.抽象类 在了解抽象类之前,先来了解一下抽象方法.抽象方法是一种特殊的方法:它只有声明,而没有具体的实

  • Java 抽象类定义与方法实例详解

    在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量.成员方法和构造方法的访问方式和普通类一样. 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用.也是因为这个原因,通常在设计阶段决定要不要设计抽象类. 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法. 抽象类 在Java语言中使

  • Java抽象类概念与用法实例分析

    本文实例讲述了Java抽象类概念与用法.分享给大家供大家参考,具体如下: 抽象:就是对一个事物的大概描述 抽象方法:以abstract修饰的方法,这种方法只声明返回数据类型,方法名和所需参数,并没有函数体.如 abstract void study(); 抽象类特点: 1.抽象类中不一定含有抽象方法:但抽象方法一定在抽象类中. 2.抽象类不具备实际功能,只能用于派生子类 3.抽象类中可以包含构造函数,但是构造函数不能被声明成抽象.抽象类中的成员方法包括一般方法和抽象方法 4.抽象方法和抽象类都必

随机推荐