注解
概述
Java注解(Annotation)又称Java标注,是JDK 5.0引入的一种注释机制。注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。因此注解对其注解的代码没有直接影响。
注解声明
基本使用
Java中所有的注解。默认实现 $Annotation$ 接口:
package java.lang.annotation; |
注解的声明使用 $@interface$ 关键字,注解声明如下:
public XXXXX{ |
元注解
在定义注解时,注解类也能够使用其他注解声明。对注解类型进行注解的注解类,亦即注解上面的注解,我们称之为“meta-annotation”(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个:
@Target
注解标记另一个注解,从而限制注解的Java元素类型。目标注解的元素类型如下:
- ElementType.ANNOTATION_TYPE 应用注解类型
- ElementType.CONSTRUCTOR 应用构造函数
- ElementType.FIELD 应用字段或属性
- ElementType.LOCAL_VARIABLE 应用局部变量
- ElementType.METHOD 应用于方法
- ElementType.PACKAGE 应用于包声明
- ElementType.PARAMETER 应用于方法的参数
- ElementType.TYPE 应用于类的任何元素(只能注解类)
@Retention
注解指定标记注解保留方式
- RetentionPolicy.SOURCE 标记的注解仅保留在源级别中,并被编译器忽略
- RetentionPolicy.CLASS 标记的注解在编译时由编译器保留,但JVM会忽略
- RetentionPolicy.RUNTIME 标记的注解由JVM保留,因此运行时环境可以使用
注解类型元素
在元注解中,允许使用注解时传递参数,同时也能让自定义注解的主体包含annotation type element(注解类型元素),例如:
//可以注解类也可以注解字段 ({ELementType.TYPE, ElementType.FIELD}) |
注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。
"干爆弟弟们") //如果只存在value元素需要传值的情况,则可以省略元素名= ( |
注解应用场景
RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
CLASS
注解会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。
这种注解的应用场景为字节码操作,如:Aspectj、热修复Robust
所谓的字节码操作即为:直接修改字节码class文件以打到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断
具体讲解如下如果我们使用普通的编程方式,需要在代码中疯狂进行if-else判断,如果存在十处,就需要在这十个判断点加入校验判断。此时,我们可以借助AOP(面向切面) 编程思想,将程序中所有功能点划分为:$需要登录$ 和 $无需登录$ 两种类型。即两个切面。对于切面的切分即可采用注解。
//注解为方法 (ElementType.METHOD) |
上述代码中,$jumpA$ 方法需要具备登录条件,而 $Login$ 注解的定义被设置为 $CLASS$ ,因此我们能够在该类所编译的字节码中获得方法的注解 $Login$。在操作字节码时,就能够根据方法是否具备该注解来修改 class 中该方法的内容加入 if-else的代码段:
//Class字节码 |
SOURCE
IDE语法检查
在Android开发中, $support-annotations$ 与 $androidx.annotation$ 中均有提供 @IntDef 注解,此注解的定义如下:
//源码级别注解 (SOURCE) |
Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。
比常量多5到10倍的内存占用。
此注解的意义在于能够取代枚举,实现如方法入参限制。
APT注解处理器
APT全称为:”Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文
件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工
具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac
调起,并将注解信息传递给注解处理器进行处理。
注解处理器是对注解应用最为广泛的场景,在Glide、EventBus3、ButterKnife、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,
TIPS:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
反射
概述
一般情况下,我们使用的某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
而反射不一样,它应用于一开始并不知道自己要初始化的类对象是什么情形下,自然也无法使用new关键字来创建对象。此时,我们可以使用JDKJDK提供的反射API进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,并且能够改变它的属性。 这也是Java被视为动态语言的关键。
Java反射机制提供了以下功能:
- 在运行时构造任意一个类的对象
- 在运行时获取或者修改任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
Class
反射始于Class,Class是一个类,封装了当前对象所对应的类的信息。 一个类中有属性、方法、构造器等,比如说又一个Animal类,一个Car类、一个Tree类,这些都是不同的类,现在需要一个类,用来描述这些对象,这就是Class,它包含雷类名,属性、方法、构造函数等等。
CLass类是一个对象镜面映射出的结果,对象可以在Class这面镜子里看到自己的属性、方法、构造器,实现了哪些接口等等,即使是私有的,藏在内心处的,也可以通过手段查到。对于每个类而言,Jre都为其保留了一个不变的Class类型的对象,同样,一个类在JVMJVM中只会有一个CLass类。
获得Class对象
获取Class对象的三种方法:
- 1.通过类名获取 类名.class
- 2.通过对象获取 对象名.getClass()
- 3.通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)
创建实例
通过反射来生成对象主要有两种方式:
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> cls = Integer.class; |
- 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance方法来构建对象。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象 |
获取构造器信息
得到构造器的方法:
Constructor getConstructor(Class[] params) -------- 获得使用特殊的参数类型的public构造函数(包括父类) |
获取类构造器方法与上述获取方法的使用类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例。而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs) |
获取类的成员变量(字段)信息
获取字段信息的方法:
Field getField(String name) -------- 获得指定名称的公共字段 |
调用方法
获取方法信息的方式:
Method getMethod(String name, Class[] params) -------- 使用特定的参数类型,获得命名的公共方法 |
当我们从类中获取一个方法之后,就可以使用 invoke() 方法来调用这个方法。 invoke 方法的原型为:
public Object invoke(Object obj, Object... args) |
利用反射创建数组
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object RefReference,其中的Array类为 java.lang.reflect.Array 类。通过Array.newInstance()创建数组对象,它的原型为:
public static Object newInstance(Class<?> componentType, int length); |
反射获取泛型的真实类型
当我们对一个泛型类进行反射时,需要得到泛型中的真实数据类型,来完成比如json反序列化的操作。此时需要通过 Type 体系来完成。Type 接口包含了一个实现类(Class)和四个实现接口,分别是:
TypeVariable
- 泛型类型变量。可以泛型上下限等信息;
ParameterizedType
- 具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
GenericArrayType
- 当需要描述的类型是泛型类的数组时,比如List[], Map[],此接口会作为Type接口的实现。
WildcardType
- 通配符泛型,获得上下限信息
TypeVariable
public class TestType <K extend Comparable & Serializable, V>{ |
ParameterizedType
public class TestType { |
GenericArrayType
public class testType<T> { |
WildcardType
public class TestType{ |
实战小项目 Gson反序列化
static class Response<T> { |
在进行gson反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成类型的反序列化。
但是为什么 TypeToken 要被定义成抽象类呢?
因为只有定义为抽象类或者接口,才能在使用中,对需要的实体类进行相对应的创建,此时确定泛型类型,编译才能够将泛型的签名信息记录正确的记录到Class元数据中。