转载自:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/
前言
如今在咱们构建本身或公司的项目中,或多或少都会依赖几个流行比较屌的第三方库,好比:Butter Knife
、Retrofit 2
、Dagger 2
、GreenDao
等,若是你没用过,那你须要找时间补一下啦;有时在使用后咱们会好奇他们究竟是怎么作到这种简洁、高效、松耦合等诸多优势的,固然这里我不探讨它们具体怎么实现的 (能够看看我以前写的几篇文章) ,而关心的是它们都用到一样的技术那就是本篇所讲的反射和注解,并实现的依赖注入。html
阅读本篇文章有助于你更好的理解这些大形框架的原理和复习Java的知识点。为何要把反射放在前面讲呢,其实是由于咱们学习注解的时候须要用到反射机制,因此,先学习反射有助于理解后面的知识。java
JAVA反射
主要是指程序能够访问,检测和修改它自己状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。面试
反射机制是什么
面试有可能会问到,这句话无论你能不能理解,可是你只要记住就能够了数组
反射机制就是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。app
用一句话总结就是反射能够实如今运行时能够知道任意一个类的属性和方法。框架
反射机制能作什么
反射机制主要提供了如下功能:ide
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具备的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 生成动态代理(ps:这个知识点也很重要,后续会为你们讲到)
Java 反射机制的应用场景
- 逆向代码 ,例如反编译
- 与注解相结合的框架 例如Retrofit
- 单纯的反射机制应用框架 例如EventBus
- 动态生成类框架 例如Gson
反射机制的优势与缺点
为何要用反射机制?直接建立对象不就能够了吗,这就涉及到了动态与静态的概念函数
-
静态编译:在编译时肯定类型,绑定对象,即经过。工具
-
动态编译:运行时肯定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以下降类之间的藕合性。post
优势
- 能够实现动态建立对象和编译,体现出很大的灵活性,特别是在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获得的 |
这里,c一、c二、c3都是Class的对象,他们是彻底同样的,并且有个学名,叫作Code的类类型(class type)。
这里就让人奇怪了,前面不是说Code是Class的对象吗,而c一、c二、c3也是Class的对象,那么Code和c一、c二、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 |
/***********注解声明***************/ |