Java实例化一个抽象类对象的方法教程
前言
最近在学习的过程中,发现了一个问题,抽象类在没有实现所有的抽象方法前是不可以通过new来构建该对象的,但是抽象方法却是可以有自己的构造方法的。这样就把我搞糊涂了,既然有构造方法,又不可以通过new来创建,那么抽象类在没变成具体类的时候究竟可不可以实例化呢?
在Java中抽象类是不能直接被实例化的。但是很多时候抽象类的该特点成为一个比较麻烦的阻碍。例如如果我想使用动态代理来给一个抽象类赋予其执行抽象方法的能力,就会有两个困难:1.动态代理只能创建实现接口的一个代理对象,而不能是一个继承抽象类的对象。为此标准的JVM中有一些实现,例如javassist可以使用字节码工具来完成这一目的(ProxyFactory)。
在Android中如果想构造一个抽象类对象,恐怕只有newClassName(){}或者继承之后构造了。但是这两种方法都是不能由其Class对象直接操作的,这就导致一些问题上达不到我们需要的抽象能力。
这里详细描述一下第一段所说的场景:
首先有一个interface文件定义如下(熟悉Android的朋友可以看出这是一个提供给Retrofit生成代理对象的Api配置接口):
publicinterfaceRealApi{ @GET("api1") Observableapi1(); @GET("api2") Observable api2(); @GET("api3") Observable api3(); //...其他方法 }
其次再写一个抽象类,只实现接口的其中一个方法(用来模拟接口数据):
@MockApi publicabstractclassMockApiimplementsRealApi{ Observableapi3(){ returnObservable.just("mockdata"); } }
然后我们需要有一个工具,例如MockManager,让他结合我们已存在的RealApi对象和MockApi类,来构造出一个混合对象,该对象在执行MockApi中已经定义的方法时,为直接执行,在MockApi没有定义该方法时,去调用RealApi的方法。其调用方式大概为:
RealApiapi=MockManager.build(realApi,MockApi.class);
通过javassist,完成上述功能很简单,创建一个ProxyFactory对象,设置其Superclass为MockApi,然后过滤抽象方法,设置methodhandler调用realApi对象的同名同参方法。这里就不再给出代码实现。
但是在Android上,javassist的该方法会抛出
Causedby:java.lang.UnsupportedOperationException:can'tloadthistypeofclassfile atjava.lang.ClassLoader.defineClass(ClassLoader.java:520) atjava.lang.reflect.Method.invoke(NativeMethod) atjavassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.java:182)
类似的异常。原因大概是Android上的虚拟机的实现和标准略微不同,所以这里把方向转为了动态代码生成的另一个方向AnnotationProcessor。
使用AnnotationProcessor实现的话,思路就简单的多了,但过程还是有些曲折:
首先定义一个注解,用来标记需要构造对象的抽象类
@Target(ElementType.TYPE) @Documented @Retention(RetentionPolicy.SOURCE) public@interfaceMockApi{ }
Processor根据注解来获得类的element对象,该对象是一个类似class的对象。因为在预编译阶段,class尚未存在,此时使用Class.forName是不可以获取运行时需要的Class对象的,但是Element提供了类似Class反射相关的方法,也有TypeElement、ExecutableElement等区分。使用Element对象分析注解的抽象类的抽象方法有哪些,生成一个继承该类的实现类(非抽象),并在该类中实现所有抽象方法,因为不会实际用到这些抽象方法,所以只需要能编译通过就可以了,我选择的方式是每个方法体都抛出一个异常,提示该方法为抽象方法不能直接调用。生成代码的方法可以使用一些工具来简化工作,例如AutoProcessor和JavaPoet,具体实现参考文尾的项目代码,生成后的代码大致像这样:
//生成的类名使用原类名+"$Impl"的后缀来命名,避免和其他类名冲突,后面也使用该约束进行反射来调用该类 publicfinalclassMockApi$ImplextendsMockApi{ @Override publicObservableapi1(){ thrownewIllegalStateException("api1()isanabstractmethod!"); } @Override publicObservable api2(){ thrownewIllegalStateException("api2()isanabstractmethod!"); } }
根据该抽象类的类名去反射获得该实现类,然后再根据反射调用其构造方法构造出一个实现对象。
//获得生成代码构造的对象 privatestaticTgetImplObject(Class cls){ try{ return(T)Class.forName(cls.getName()+"$Impl").newInstance(); }catch(Exceptione){ returnnull; } }
构造一个动态代理,传入RealApi的真实对象,和上一步构造出的抽象类的实现对象,根据抽象类中的定义来判断由哪个对象代理其方法行为:如果抽象类中有定义,即该方法不是抽象方法,则抽象类的实现对象执行;反之,由接口的真实对象执行。
publicstaticOriginbuild(finalOriginorigin,finalClass mockClass){ //如果MockClass标记为关闭,则直接返回真实接口对象 if(!isEnable(mockClass)){ returnorigin; } finalMockmockObject=getImplObject(mockClass); Class>originClass=origin.getClass().getInterfaces()[0]; return(Origin)Proxy.newProxyInstance(originClass.getClassLoader(),newClass[]{originClass},newInvocationHandler(){ @Override publicObjectinvoke(Objecto,Methodmethod,Object[]objects)throwsThrowable{ //获取定义的抽象类中的同名方法,判断是否已经实现 MethodmockMethod=null; try{ mockMethod=mockClass.getDeclaredMethod(method.getName(),method.getParameterTypes()); }catch(NoSuchMethodExceptionignored){ } if(mockMethod==null||Modifier.isAbstract(mockMethod.getModifiers())){ returnmethod.invoke(origin,objects); }else{ returnmockMethod.invoke(mockObject,objects); } } }); }
完成上述工作以后,就可以像开头所说的那样,使用build方法来构造一个混合了真实接口和抽象类方法的代理对象了,虽然调用的类本质上还是硬编码,但是由AnnotationProcessor自动生成免于手动维护,使用上来讲和使用Javassist实现还是基本相同的。
我用本文中所属的方法实现了一个模拟retrofit请求的工具(文尾有链接),但本质上可以用它来实现很多需要构造抽象类的需求,更多的使用场景还有待挖掘。
文中提到的源码实现可以在项目retrofit-mock-result或本地下载中找到;
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。