反射
作用
我们知道Java代码会被编译成字节码文件,当一个类编译完成后,在生成的.class文件中会产生一个Class对象,该对象用于表示这个类的信息,比如类的属性,字段,构造方法等等。 既然Class中包含了这么多有用的信息,那么我们就可以使用反射的技术,获得这些编译之后的Class对象当中的内容。反射的最大作用就是开发各种通用框架。比如在Android的数据库框架中,就是使用了反射和注解两个技术配合完成工作。
使用
获取Class的三种方式
1)类名.class
2)对象.getClass()
3)Class.forName(“类名”)
在获取到Class之后,就可以利用newInstance()方法生成一个对象:
实际上在调用newInstance()
方法时实际上是调用了该类的无参构造方法。
获取构造方法
1) getConstructors()
获取类的构造器,但获取不到私有构造器:
2) getDeclaredConstructors()
获取类的所有构造器:
获取类所有的构造器,这个没啥可说的。
3) getDeclaredConstructor()
获取指定的构造器:
一个类可能有多个重载的构造方法,它们的方法名都是一样的,那么怎么获取指定的构造器呢?所以此时需要从构造器的输入参数入手,比如:
就可以获取到如下的构造方法:
但是请注意该构造方法是private的,所以需要将该方法的accessible标志设置为true 表示取消语言访问检查。即:
获取字段
同获取构造函数一样,对于私有字段同样要先取消语言访问检查再进行操作。
1 )getFields()
获取类的字段,但是获取不到私有字段:
2) getDeclaredFields()
获取类的所有字段:
3) 获取指定的字段及其type:
4) 获取指定对象的某个字段值
5) 设置指定对象的某个字段值
获取方法
同上,对于私有方法同样要先取消语言访问检查再进行操作。
1 )getMethods()
获取该类及其父类的方法,但不能获取到私有方法
2) getDeclaredMethods()
获取该类本身所声明的所有方法
3)反射出类中的指定方法
获取泛型的参数类型
在许多框架中有这样的需求:根据不同的泛型参数响应不同的操作。但是,泛型主要是写给编译器看的;那么在编译完成之后生成的字节码里,泛型会被擦除
。比如ArrayList<String>
在编译后就变成了ArrayList
,它原本的泛型被”擦除”了。但是我们有时确实需要知道泛型的参数类型,又该怎么来实现呢?一共需要五步:
1)定义getGenericHelper()
方法,其输入参数为带泛型的参数,比如ArrayList<String,Integer>
。
2)利用反射获取到该getGenericHelper()方法,即:
3)获取到该方法的带泛型的输入参数,即:
注意getGenericParameterTypes()方法返回的是一个数组,因为方法可能有多个参数,但是依据我们的需求这个数组中是仅有一个元素的。
4)获取到该带泛型参数的所有泛型的类型,即:
因为一个参数的泛型可能有多个,所以getActualTypeArguments()的返值是一个数组。 比如,此处的ArrayList
5)获取每个泛型的类型,即:
关于Type
在JDK1.5之前还没有泛型时,我们的数据都是这样的:int,double,String,User,Girl…….在泛型出来之后又多了这些东西:
Type一共有四个子接口: TypeVariable,ParameterizedType,GenericArrayType,WildcardType。
它还有一个实现类Class
Class所表示的是原始类型,或者说是泛型出现之前的类型。比如String,User,Girl,Integer等等。
TypeVariable
ParameterizedType
GenericArrayType
WildcardType
注解
定义:用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。
作用
注解出现之前(包括之后),XML被广泛的应用于描述元数据,XML配置其实就是为了分离代码和配置而引入的。假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。
后来开发者希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(有时是完全分离的)代码描述。比如如果你想把某个方法声明为服务,那么使用注解会更好一些,因为这种情况下需要注解和方法紧密耦合起来。而且,注解定义了一种标准的描述元数据的方式,之前开发人员通常使用他们自己的方式定义元数据。
目前,许多框架将XML和注解两种方式结合使用,平衡两者之间的利弊。
总结注解的作用:
1、代码检查:例如@Override是告诉编译器这个方法是一个重写方法,如果父类中不存在该方法,编译器会报错,提示该方法不是父类中的方法。如果不小心拼写错误,将onCreate写成了onCreat,而且没有使用@Override注解,程序依然能够编译通过,但运行结果肯定就不正确了。
2、减少重复性工作:比如第三方框架xUtils,通过注解@ViewInject减少对findViewById的调用
3、用于搭建通用框架,例如在数据库框架中,注解的作用就是实现Model类中的成员变量与数据库表中的字段形成映射。
注解的分类
标准注解
在Java的JDK中内置了一些系统自带的注解,这些注解也常称为标准注解,常见的有:@Override, @Deprecated, @SuppressWarnings等。
元注解
注解是用来标记或者说明类,方法,变量的,与此类似,Java还提供了元注解用于标记注解。 常见的元注解有:@Target、@Retention、@Documented、@Inherited。
@Target
Target用于确定Annotation所修饰的对象范围,我们知道注解可用于packages、types(类、接口、枚举)、类型成员(方法、成员变量、枚举值)、方法参数等等。所以,可用@Target表示Annotation修饰的目标。比如@Override的源码:
常用的描述参数:
METHOD:用于对方法进行注解
TYPE:用于描述类
CONSTRUCTOR:用于描述构造器
FIELD:用于描述类成员变量
LOCAL_VARIABLE:用于描述局部变量
PACKAGE:用于描述包
PARAMETER:用于描述参数
@Retention
@Retention定义了Annotation的有效范围,类似于Android中常提到的生命周期。Java文件从生产到执行,要经过三个主要的阶段:java源文件,Class文件,JVM运行。与此类似,有的Annotation仅出现在源代码中而被编译器丢弃,而另一些却被编译在Class文件中;有的编译在Class文件中的Annotation在运行时会被虚拟机忽略,而另一些在运行时被读取读取。
SOURCE:在源文件中有效
该注解只保留在源文件即.java文件中。RetentionPolicy.SOURCE是我们平常见得最多的保留策略。比如:@Override, @SuppressWarnings
CLASS:在Class文件中有效
该注解会保留至.class阶段扔不会被丢弃,但加载到JVM虚拟机时会被丢弃。RetentionPolicy.CLASS是默认的注解保留策略
RUNTIME:在运行时有效
该注解保留至程序运行时即被装载进JVM虚拟机后,此时可通过反射获取与注解的相关信息,比如注解的属性及其值。RetentionPolicy.RUNTIME是一个非常有用的保留策略,尤其是在框架开发的时候。
@Documented
@Documented表示在生成javadoc文档时将该Annotation也写入到帮助文档。
@Inherited
@Inherited用于指示注释类型被自动继承。
自定义注解
顾名思义就是由程序员自己来定义的注解。
定义注解
自定义注解和创建接口非常相似,但注解需要以@开头。如下我举了一个例子,在大括号里面看着像是声明了一个方法,但实际上是声明了一个属性,其中“方法名”是属性的名称,“方法的返回值类型“就是属性的类型,返回值类型只能是基本类型:String、enum、Class等。此外,可以通过default来声明属性的默认值。 我在这个例子中还用到了元注解:
|
|
使用注解
|
|
提取注解的值
|
|
在提取注解时,我们用到了反射技术。上面这段代码只是提取了对这个类的注解的值,如果对里面的成员变量也使用了注解,那我们则要依次判断每一个成员变量是否使用了注解,如果有则取出。