【转】JAVA反射与注解
转载自:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/
前言
现在在我们构建自己或公司的项目中,或多或少都会依赖几个流行比较屌的第三方库,比如:Butter Knife
、Retrofit 2
、Dagger 2
、GreenDao
等,如果你没用过,那你需要找时间补一下啦;有时在使用后我们会好奇他们到底是怎么做到这种简洁、高效、松耦合等诸多优点的,当然这里我不探讨它们具体怎么实现的 ( 可以看看我之前写的几篇文章) ,而关心的是它们都用到同样的技术那就是本篇所讲的反射和注解,并实现的依赖注入。
阅读本篇文章有助于你更好的理解这些大形框架的原理和复习 Java 的知识点。为什么要把反射放在前面讲呢,实际上是因为我们学习注解的时候需要用到反射机制,所以,先学习反射有助于理解后面的知识。
JAVA 反射
主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
反射机制是什么
面试有可能会问到,这句话不管你能不能理解,但是你只要记住就可以了
反射机制就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。
反射机制能做什么
反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 生成动态代理(ps: 这个知识点也很重要,后续会为大家讲到)
Java 反射机制的应用场景
- 逆向代码 ,例如反编译
- 与注解相结合的框架 例如 Retrofit
- 单纯的反射机制应用框架 例如 EventBus
- 动态生成类框架 例如 Gson
反射机制的优点与缺点
为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念
-
静态编译:在编译时确定类型,绑定对象, 即通过。
-
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了 java 的灵活性,体现了多态的应用,有以降低类之间的藕合性。
优点
- 可以实现动态创建对象和编译,体现出很大的灵活性,特别是在 J2EE 的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
缺点
- 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。
理解 Class 类和类类型
想要了解反射首先理解一下 Class 类,它是反射实现的基础。
类是 java.lang.Class 类的实例对象,而 Class 是所有类的类(There is a class named Class)
对于普通的对象,我们一般都会这样创建和表示:
1
|
Code code1 = new Code();
|
上面说了,所有的类都是 Class 的对象,那么如何表示呢,可不可以通过如下方式呢:
1
|
Class c = new Class();
|
但是我们查看 Class 的源码时,是这样写的:
1
|
private Class(ClassLoader loader) {
|
可以看到构造器是私有的,只有 JVM 可以创建 Class 的对象,因此不可以像普通类一样 new 一个 Class 对象,虽然我们不能 new 一个 Class 对象,但是却可以通过已有的类得到一个 Class 对象,共有三种方式,如下:
1
|
Class c1 = Code.class; 这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class 得到的
|
这里,c1、c2、c3 都是 Class 的对象,他们是完全一样的,而且有个学名,叫做 Code 的类类型(class type)。
这里就让人奇怪了,前面不是说 Code 是 Class 的对象吗,而 c1、c2、c3 也是 Class 的对象,那么 Code 和 c1、c2、c3 不就一样了吗?为什么还叫 Code 什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。
举个简单例子代码:
1
|
public class ReflectDemo {
|
执行结果:
1
|
com.tengj.reflect.ReflectDemo
|
Java 反射相关操作
在这里先看一下 sun 为我们提供了那些反射机制中的类:
java.lang.Class;
java.lang.reflect.Constructor; java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;
前面我们知道了怎么获取 Class,那么我们可以通过这个 Class 干什么呢?
总结如下:
- 获取成员方法 Method
- 获取成员变量 Field
- 获取构造函数 Constructor
下面来具体介绍
-
获取成员方法信息
两个参数分别是方法名和方法参数类的类类型列表。
1
|
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的
|
举个例子:
例如类 A 有如下一个方法:
1
|
public void fun(String name,int age) {
|
现在知道 A 有一个对象 a,那么就可以通过:
1
|
Class c = Class.forName("com.tengj.reflect.Person"); // 先生成 class
|
完整代码如下:
1
|
public class Person {
|
执行结果:
我叫tengj,今年10岁
怎样,是不是感觉很厉害,我们只要知道这个类的路径全称就能玩弄它于鼓掌之间。
有时候我们想获取类中所有成员方法的信息,要怎么办。可以通过以下几步来实现:
1. 获取所有方法的数组:
1
|
Class c = Class.forName("com.tengj.reflect.Person");
|
2. 然后循环这个数组就得到每个方法了:
1
|
for (Method method : methods)
|
完整代码如下:
person 类跟上面一样,这里以及后面就不贴出来了,只贴关键代码
1
|
public class ReflectDemo {
|
执行结果:
getName
setName
setAge
fun
fun
getAge
这里如果把 c.getDeclaredMethods(); 改成 c.getMethods(); 执行结果如下,多了很多方法,以为把 Object 里面的方法也打印出来了,因为 Object 是所有类的父类:
getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
-
获取成员变量信息
想一想成员变量中都包括什么:成员变量类型 + 成员变量名
类的成员变量也是一个对象,它是java.lang.reflect.Field
的一个对象,所以我们通过java.lang.reflect.Field
里面封装的方法来获取这些信息。
单独获取某个成员变量,通过 Class 类的以下方法实现:
参数是成员变量的名字
1
|
public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
|
举个例子:
例如一个类 A 有如下成员变量:
1
|
private int n;
|
如果 A 有一个对象 a,那么就可以这样得到其成员变量:
1
|
Class c = a.getClass();
|
完整代码如下:
1
|
public class ReflectDemo {
|
执行结果:
hello wrold
同样,如果想要获取所有成员变量的信息,可以通过以下几步
1. 获取所有成员变量的数组:
1
|
Field[] fields = c.getDeclaredFields();
|
2. 遍历变量数组,获得某个成员变量 field
1
|
for (Field field : fields)
|
完整代码:
1
|
public class ReflectDemo {
|
执行结果:
name
age
msg
-
获取构造函数
最后再想一想构造函数中都包括什么:构造函数参数
同上,类的成构造函数也是一个对象,它是java.lang.reflect.Constructor
的一个对象,所以我们通过java.lang.reflect.Constructor
里面封装的方法来获取这些信息。
单独获取某个构造函数, 通过Class
类的以下方法实现:
这个参数为构造函数参数类的类类型列表
1
|
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 获得该类所有的构造器,不包括其父类的构造器
|
举个例子:
例如类 A 有如下一个构造函数:
1
|
public A(String a, int b) {
|
那么就可以通过:
1
|
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);
|
来获取这个构造函数。
完整代码:
1
|
public class ReflectDemo {
|
执行结果:
tengj
注意:Class 的 newInstance 方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式:
1
|
fromClass.getDeclaredConstructor(String.class).newInstance("tengj");
|
获取所有的构造函数,可以通过以下步骤实现:
1. 获取该类的所有构造函数,放在一个数组中:
1
|
Constructor[] constructors = c.getDeclaredConstructors();
|
2. 遍历构造函数数组,获得某个构造函数constructor
:
1
|
for (Constructor constructor : constructors)
|
完整代码:
1
|
public class ReflectDemo {
|
执行结果:
public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)
-
其他方法
注解需要用到的
1
|
Annotation[] annotations = (Annotation[]) class1.getAnnotations();// 获取 class 对象的所有注解
|
获取 class 对象的信息
1
|
boolean isPrimitive = class1.isPrimitive();// 判断是否是基础类型
|
通过反射了解集合泛型的本质
扩展的知识点,了解就可以了。后续会为大家写一篇关于泛型的文章。
首先下结论:
Java 中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。
下面通过一个实例来验证:
1
|
/**
|
执行结果:
list2的长度是:1
true
list2的长度是:2
思维导图
有助于理解上述所讲的知识点

拓展阅读
Java 反射机制深入详解 - 火星十一郎 - 博客园
Java 反射入门 - Trigl 的博客 - CSDN 博客
Java 反射机制 - ①块腹肌 - 博客园
Java 反射机制浅析 - 孤旅者 - 博客园
反射机制的理解及其用途 - 每天进步一点点! - ITeye 博客
Java 动态代理与反射详解 - 浩大王 - 博客园
JAVA 注解
概念及作用
- 概念
- 注解即元数据, 就是源代码的元数据
- 注解在代码中添加信息提供了一种形式化的方法, 可以在后续中更方便的 使用这些数据
- Annotation 是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由 JSR-175 标准选择用来描述元数据的一种工具。
- 作用
- 生成文档
- 跟踪代码依赖性,实现替代配置文件功能, 减少配置。如 Spring 中的一些注解
- 在编译时进行格式检查,如 @Override 等
- 每当你创建描述符性质的类或者接口时, 一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。
什么是 java 注解?
在 java 语法中,使用@
符号作为开头,并在 @后面紧跟注解名。被运用于类,接口,方法和字段之上,例如:
1
|
|
这其中 @Override 就是注解。这个注解的作用也就是告诉编译器,myMethod()方法覆写了父类中的 myMethod() 方法。
java 中内置的注解
java 中有三个内置的注解:
- @Override:表示当前的方法定义将覆盖超类中的方法,如果出现错误,编译器就会报错。
- @Deprecated:如果使用此注解,编译器会出现警告信息。
- @SuppressWarnings:忽略编译器的警告信息。
本文不在阐述三种内置注解的使用情节和方法,感兴趣的请看这里
元注解
自定义注解的时候用到的,也就是自定义注解的注解;(这句话我自己说的,不知道对不对)
元注解的作用就是负责注解其他注解。Java5.0
定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
Java5.0
定义的 4 个元注解:
-
@Target
-
@Retention
-
@Documented
-
@Inherited
java8 加了两个新注解,后续我会讲到。
这些类型和它们所支持的类在 java.lang.annotation 包中可以找到。
@Target
@Target 说明了 Annotation 所修饰的对象范围:Annotation 可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值 (ElementType) 有:
类型 | 用途 |
---|---|
CONSTRUCTOR | 用于描述构造器 |
FIELD | 用于描述域 |
LOCAL_VARIABLE | 用于描述局部变量 |
METHOD | 用于描述方法 |
PACKAGE | 用于描述包 |
PARAMETER | 用于描述参数 |
TYPE | 用于描述类、接口 (包括注解类型) 或 enum 声明 |
比如说这个注解表示只能在方法中使用:
1
|
|
@Retention
@Retention 定义了该 Annotation 被保留的时间长短:某些 Annotation 仅出现在源代码中,而被编译器丢弃;而另一些却被编译在 class 文件中;编译在 class 文件中的 Annotation 可能会被虚拟机忽略,而另一些在 class 被装载时将被读取(请注意并不影响 class 的执行,因为 Annotation 与 class 在使用上是被分离的)。使用这个 meta-Annotation 可以对 Annotation 的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
类型 | 用途 | 说明 |
---|---|---|
SOURCE | 在源文件中有效(即源文件保留) | 仅出现在源代码中,而被编译器丢弃 |
CLASS | 在 class 文件中有效(即 class 保留) | 被编译在 class 文件中 |
RUNTIME | 在运行时有效(即运行时保留) | 编译在 class 文件中 |
使用示例:
1
|
/***
|
@Documented
@Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。
作用:将注解包含在 javadoc 中
示例:
1
|
java.lang.annotation.Documented
|
@Inherited
- 是一个标记注解
- 阐述了某个被标注的类型是被继承的
- 使用了 @Inherited 修饰的 annotation 类型被用于一个 class, 则这个 annotation 将被用于该 class 的子类
@Inherited annotation 类型是被标注过的 class 的子类所继承。类并不从实现的接口继承 annotation, 方法不从它所重载的方法继承 annotation
- 当 @Inherited annotation 类型标注的 annotation 的 Retention 是 RetentionPolicy.RUNTIME,则反射 API 增强了这种继承性。如果我们使用 java.lang.reflect 去查询一个 @Inherited annotation 类型的 annotation 时,反射代码检查将展开工作:检查 class 和其父类,直到发现指定的 annotation 类型被发现,或者到达类继承结构的顶层。
作用:允许子类继承父类中的注解
示例,这里的 MyParentClass 使用的注解标注了 @Inherited,所以子类可以继承这个注解信息:
1
|
java.lang.annotation.Inherited
|
1
|
|
1
|
public class MyChildClass extends MyParentClass {
|
自定义注解
格式
1
|
public
|
注解参数的可支持数据类型:
- 所有基本数据类型 (int,float,double,boolean,byte,char,long,short)
- String 类型
- Class 类型
- enum 类型
- Annotation 类型
- 以上所有类型的数组
规则
- 修饰符只能是 public 或默认 (default)
- 参数成员只能用基本类型 byte,short,int,long,float,double,boolean 八种基本类型和 String,Enum,Class,annotations 及这些类型的数组
- 如果只有一个参数成员, 最好将名称设为”value”
- 注解元素必须有确定的值, 可以在注解中定义默认值, 也可以使用注解时指定, 非基本类型的值不可为 null, 常使用空字符串或 0 作默认值
- 在表现一个元素存在或缺失的状态时, 定义一下特殊值来表示, 如空字符串或负值
示例:
1
|
/**
|
注解处理器类库
java.lang.reflect.AnnotatedElement
Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。除此之外,Java 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
- Class:类定义
- Constructor:构造器定义
- Field:累的成员变量定义
- Method:类的方法定义
- Package:类的包定义
java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射 API 扩充了读取运行时 Annotation 信息的能力。当一个 Annotation 类型被定义为运行时的 Annotation 后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的 Annotation 才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象之后,程序就可以调用该对象的如下四个个方法来访问 Annotation 信息:
- 方法 1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回 null。
- 方法 2:Annotation[] getAnnotations(): 返回该程序元素上存在的所有注解。
- 方法 3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass): 判断该程序元素上是否包含指定类型的注解,存在则返回 true,否则返回 false.
- 方法 4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
注解处理器示例:
1
|
/*********** 注解声明 ***************/ |