反射及注解
类加载机制
-
单个类自身的加载步骤:
-
类的加载:
- 将 .class 文件读入内存,并为之创建一个
java.lang.Class
对象。
- 将 .class 文件读入内存,并为之创建一个
-
类的连接:
- 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
- 准备阶段:负责为类的类变量分配内存,并设置默认初始化值;
- 解析阶段:将类的二进制数据中的符号引用替换为直接引用。
-
类的初始化:
- 主要是对类变量进行初始化。
JVM 会连续完成这三个步骤,所以这 3 个步骤也会统称为 类加载 或 类初始化 。
-
-
类加载/类初始化(统称)的总体步骤:
- 假如类还未被加载和连接,则程序先加载并连接该类。
- 假如该类的直接父类还未被初始化,则先初始化其直接父类。
- 假如类中有初始化语句,则系统依次执行这些初始化语句。
其中,在执行第 2 个步骤的时候,系统对直接父类的初始化步骤也是遵循上面的初始化步骤 1-3 。
-
一个类被加载的时机:
- 自己的静态方法被调用时;
- 自己的静态属性被访问时;
- 被创建实例时;
- 自己的子类被初始化时;
- 被使用反射方式来强制创建
java.lang.Class
对象时; - 若自己是一个入口类,被直接使用
java
命令运行时;
反射
概念
-
反射的概念:
- 指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象、调用方法的一种机制。
- 由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就要完成确定,而在运行期仍然可以扩展,因此反射被称为框架设计的灵魂。
-
要想通过反射去使用一个类,首先要获取到该类的字节码文件对象(类型为
Class
的对象),有如下三种常见的获取方式:-
使用类的
class
属性来获取该类对应的Class
对象,在代码中即为:类名.class
。注意:基本数据类型也是这样,如
int.class
,并且它和Integer.class
并不等价! -
使用对象调用
getClass()
方法来返回其所属类的Class
对象,在代码中即为:对象引用.getClass()
。 -
使用
Class
类静态方法传入类的完整路径名的字符串来返回该类的Class
对象,在代码中即为:Class.forName(String className)
。
-
常用方法
-
反射获取构造方法并使用:
Class<T> 类常用方法 说明 Constructor<?>[] getConstructors() 返回所有公共构造方法对象的数组 Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组 Constructor<T> getConstructor(Class<?>… parameterTypes) 返回单个公共构造方法对象 Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes) 返回单个构造方法对象 Constructor<T> 类常用方法 说明 T newInstance(Object… initargs) 根据指定的构造方法创建对象 void setAccessible(boolean flag) 设置此 Constructor 对象权限为可访问 -
反射获取成员变量并使用:
Class<T> 类常用方法 说明 Field[] getFields() 返回所有公共成员变量对象的数组 Field[] getDeclaredFields() 返回所有成员变量对象的数组 Field getField(String name) 返回单个公共成员变量对象 Field getDeclaredField(String name) 返回单个成员变量对象 Field 类常用方法 说明 void set(Object obj, Object value) 将 obj 对象的成员变量赋值为 value void setAccessible(boolean flag) 设置此 Field 对象权限为可访问 -
反射获取成员方法并使用:
Class<T> 类常用方法 说明 Method[] getMethods() 返回所有公共成员方法对象的数组(含继承得来的) Method[] getDeclaredMethods() 返回所有成员方法对象的数组(不含继承得来的) Method getMethod(String name, Class<?>… parameterTypes) 返回单个公共成员方法对象 Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象 Method 类常用方法 说明 Object invoke(Object obj, Object… args) 调用 obj 对象的成员方法,参数是 args,返回值是 Object 类型 void setAccessible(boolean flag) 设置此 Method 对象权限为可访问
注解
概念
-
注解( Annotation ),也称元数据,是一种代码级别的说明。
-
它与类、接口、枚举是在同一个层次。
-
它可以声明在包、类、字段、方法、局部变量、方法参数等前面,来对这些元素进行说明。
使用方式:
@注解名称
-
实际上我们大多数时候认为注解不是程序的一部分,而认为它就是一个标签,这个标签要么给编译器用,要么给解析程序用。
解析程序就是用反射来得到类的
Class
对象(及Method
、Field
等对象),调用类似getAnnotation(...)
方法获取该类上写的注解,从而进行逻辑操作。
-
-
注解与注释:
- 注释:用文字描述程序,给程序员看的;
- 注解:说明程序,给计算机看的。
-
按作用分类:
-
编写文档:通过代码里标识的注解生成文档 ( JDK 预定义的 )
如:在文档注释中使用
@author
、@since
、@param
、@return
等,使用javadoc
命令生产文档后,分别对应作者信息、起始版本信息、方法的形参信息、方法的返回值信息等。 -
编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查 ( JDK 预定义的 )
如:
@Override
可以检查一个方法是否是重写的父类/接口的方法,若不是则编译失败。 -
代码分析:通过代码里标识的注解对代码进行分析 ( 自定义的 )
如:
@自定义注解名
,再使用反射抽取注解。
-
自定义注解
定义注解
-
定义格式:
元注解 public @interface 注解名称 { 属性列表... }
- 注解本质上就是一个接口,该接口默认继承
java.lang.annotation.Annotation
接口; - 所以,注解里面能定义的内容就是接口里面能定义的内容是一种类型的。
- 注解本质上就是一个接口,该接口默认继承
-
注解中的 “属性” (这种特殊的接口中的抽象方法)
之所以 “方法” 在这里会被叫做 “属性” ,是因为在使用注解时的语法如:
@注解名(抽象方法名 = 值) ...
在形式上类似给属性赋值,所以就被称为 “属性” 了。
-
注解内定义的 “属性” 的返回值类型只能是以下几种:
-
基本数据类型或其数组;
-
String或其数组;
-
枚举或其数组;
-
注解或其数组。
-
-
注解内定义的 “属性” 在注解被使用时必须被赋值,并且:
- 如果定义属性时,使用
default
关键字给属性默认初始化值(如int age() default 18;
)则使用注解时,可以不进行属性的赋值。 - 如果只有一个属性需要赋值,并且属性的名称是
value
,则在赋值时的value=
可以省略,直接传值即可。 - 数组赋值时,值使用
{}
包裹。如果数组中只有一个值,则{}
可以省略不写。
- 如果定义属性时,使用
-
-
元注解:
它是描述注解的注解,是由 JDK 预定义的。常见以下几种:
-
@Target
: 描述注解能够作用的位置。该注解的属性被定义为一个枚举类型的数组:
java.lang.annotation.ElementType
,该枚举的常用取值有:TYPE
:传入该值则代表被注解的注解可以作用于类上;METHOD
:传入该值则代表被注解的注解可以作用于方法上;FIELD
:传入该值则代表被注解的注解可以作用于成员变量上;- ……
-
@Retention
: 描述注解被保留到的阶段。该注解的属性被定义为一个枚举类型的数组:
java.lang.annotation.RetentionPolicy
,该枚举的取值有:SOUECE
:传入该值则代表被注解的注解只保留在源码阶段,即只给编译器看,编译完的 .class 文件中就没有该注解的信息了;CLASS
:传入该值则代表被注解的注解保留到字节码阶段,编译完的 .class 文件中有它的信息,但在运行时就没有该注解的信息了;(默认值)RUNTIME
:传入该值则代表被注解的注解的信息一直保留到运行时,可以在运行时用反射机制读取到该注解。
-
@Inherited
: 描述该注解是否会被子类继承。 -
@Documented
: 描述注解是否被抽取到api文档中。
-
解析注解
-
解析注解,需要使用反射机制,从一个类的
Class
对象中(及Method
、Field
等)中得到它上面的注解信息(注解实现对象),从而进一步的判断分析操作等。 -
常用方法如:
-
判断是否具有某注解:
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
如:
-
注解的定义:
... @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Check { }
-
使用该注解的类:
... public class Calculator { @Check public void add() {System.out.println("1+0=" + (1+0));} @Check public void sub() {System.out.println("1-0=" + (1-0));} @Check public void mul() {System.out.println("1*0=" + (1*0));} @Check public void div() {System.out.println("1/0=" + (1/0));} }
-
解析程序:
... public class Test { public static void main(String[] args) { Calculator calculator = new Calculator(); Class<? extends Calculator> calculatorClass = calculator.getClass(); Method[] methods = calculatorClass.getMethods(); for (Method m : methods) { if (m.isAnnotationPresent(Check.class)) { try { m.invoke(calculator); } catch (Exception e) { System.out.println(m.toString() + "方法出现异常:" + e); } } } } } /* 控制台输出: 1+0=1 1-0=1 public void com.qiong.Calculator.div()方法出现异常:java.lang.reflect.InvocationTargetException 1*0=0 */
-
-
提取使用注解时标记的信息:
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
如:
-
注解的定义:
... @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface GroupInfo { int number(); String major(); }
-
使用该注解的类:
... @GroupInfo(number = 5, major = "IT") public class JavaLearner { private String major; public JavaLearner(String major) { this.major = major; } }
-
解析程序:
... public class Test { public static void main(String[] args) { GroupInfo anno = JavaLearner.class.getAnnotation(GroupInfo.class); /* 上述的操作相当于 Java 在内部生产了一个 GroupInfo 接口的实现类对象并返回,实现类相当于: public class GroupInfoImpl_xxx { public int number() { return 5; } public String major() { return "IT"; } } */ String major = anno.major(); int number = anno.number(); ArrayList<JavaLearner> list = new ArrayList<>(); for (int i = 0; i < number; i++) { list.add(new JavaLearner(major)); } // ..... } }
-
-