一、反射
1.什么是反射
看一下官方对反射的描述
Reflection is commonly used by programs which require
the ability to examine or modify the runtime behavior
of applications running in the Java virtual machine.
翻译一下的意思就是:反射通常用于运行在java虚拟机上的程序,需要检查能力或者修改运行时行为的程序。
这段话有点绕,我们换个说法就是:反射可以获取一个类的所有方法和属性;并且对于任意一个对象,可以调用它的任意方法或者获取其属性。
2.类对象
2.1 什么是类对象
使用反射机制的前提就是获取类对象,类对象是虚拟机将class文件读入内存,创建的一个类对象,用于描述类有什么属性,方法等。
2.2 如何获取类对象
获取类对象有三种方式,每一种方式获取的类对象都是相等的,所以在虚拟机中一个类只会有一个类对象。
其中Class.forName
不仅仅表示类的类类型,还代表了动态加载类。编译时加载是静态加载。
Class c1 = Demo.class;
Class c2 = new Demo().getClass();
Class c3 = Class.forName("com.lee.provider.controller.demo.Demo");
// true
System.out.println(c1 == c2);
// true
System.out.println(c2 == c3);
2.3 动态和静态加载类
- 静态加载
编译时刻加载是静态加载,new
创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类。下述代码在编译的时候如果Teacher
和Student
类不存在的话,就直接会报错
public void method(String case) {
if ("case1".equals(case)) {
Teacher teacher = new Teacher();
}
if ("case2".equals(case)) {
Student student = new Student();
}
}
- 动态加载
运行时加载是动态加载。用到哪个就加载哪个,不用就不加载。下述代码动态加载类,在编译的时候,如果classPath不存在也不会报错,只有在运行的时候才会报错
public void method(String classPath) {
try{
Class clazz = Class.forName(classPath);
} catch (Exception e) {
e.printStackTrace();
}
}
二、反射包体系
反射的相关类和接口都在java.lang.reflect
包下面。
1.类的继承体系
1.1 AccessibleObject
AccessibleObject
类是Field
、Method
和Constructor
对象的基类。它提供了将反射的对象标记为在使用时取消默认Java
语言访问控制检查的能力。
对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用Field
、Method
或Constructor
对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
1.2 Member
Member
是一个接口,在JDK
描述中是用于标记一个成员(字段或者方法)或者一个构造方法。Member
接口有四个方法。
-
getDeclaringClass:获取声明成员类对应的
class
-
getModifiers:获取成员对应的修饰符,修饰符以累加的形式返回,如
public static
就返回9
修饰符 值 public 1 private 2 protected 4 static 8 final 16 synchronized 32 volatile 64 transient 128 native 256 interface 512 abstract 1024 strict 2048 -
getName:获取这个成员对应的名称
-
isSynthetic:判断这个成员是不是由编译器注入的
1.3 Executable
一个共享的父类用来描述Constructor
和Method
的公共方法。
1.4 Field
Field
提供了动态访问类或者接口的单个字段、反射的字段可以是类(静态)字段或实例字段。
1.5 Constructor
Constructor
提供了访问类的单个构造方法,一个构造方法对应一个Constructor
类。
1.6 Method
Method
提供访问类或者接口的单个方法,反射的方法可以是类方法或者实例方法包括抽象方法。
2.接口的继承体系
2.1 AnnotatedElement
代表运行在虚拟机中的程序的一个注解元素,此接口允许以反射的方式读取注解。在这个接口中返回的所有注解是不可变的和序列化的。调用者可以修改这个接口返回的数组,不会影响其他调用者。
2.2 AnnotatedType
AnnotatedType
表示在当前虚拟机运行的程序中可能使用的注解类型、 在Java
中使用的可以是任何类型,包括数组类型、参数类型、变量类型、通配符类型。
三、反射基本操作
1.反射使用流程
-
获取类对象
-
获取构造方法
-
创建对象
-
使用对象变量
-
使用对象方法
-
反射获取注解
2.使用反射的准备
要使用反射我们要有基本的类,这里我们准备一个类如下
package com.lee.study.basic.reflect;
public class ReflectDemo {
public String name;
private int age;
public ReflectDemo(String name, int age) {
this.name = name;
this.age = age;
}
public ReflectDemo() {
}
public void sayHello (String message) {
System.out.println("说消息:" + message);
}
public void getMessage () {
System.out.println("无参数");
}
public String getReturn () {
return "惊喜";
}
private void secret() {
System.out.println("秘密");
}
public String getName () {
return this.name;
}
public int getAge () {
return this.age;
}
}
3. 使用反射
3.1 获取类的class
上面我们说了获取类的class
有三种方法,这里我们使用forName方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
3.2 获取构造方法
- 公共构造方法
通过getConstructors()
方法可以获取公共的构造方法,如果类里面没有写构造方法,也会默认获取无参构造方法,因为无参构造方法是每个类默认有的,一旦自己添加构造方法,那就没有默认的构造方法。
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor[] constructors = clazz.getConstructors();
- 所有构造方法
通过getDeclaredConstructors()
方法获取所有的构造方法,包括私有的构造方法。
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor[] constructors = clazz.getDeclaredConstructors();
- 获取无参构造方法
通过getConstructor
方法可以获取单个无参构造方法。
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
- 获取有参构造方法
通过getConstructor
方法可以获取单个有参构造方法。
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor(String.class, int.class);
3.3 反射创建对象
上面我们通过获取class
,然后获取了构造方法,通过构造方法就可以创建实例对象了
- 无参构造方法创建对象
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
- 有参构造方法创建对象
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor(String.class, int.class);
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance("张三", 19);
3.4 使用对象变量
Field
类是反射字段类,用于变量反射,一个Field
对应一个变量。
- 公共变量
通过getFields()方法可以获取所有的公共成员变量,包括从父类继承和实现接口中的公共成员变量
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Field[] fields = clazz.getFields();
- 所有变量
通过getDeclaredFields()
方法可以获取所有的成员变量,公有和私有变量,不包括父类和实现接口的成员变量。
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Field[] fields = clazz.getDeclaredFields();
- 反射公共字段
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo")
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Field field = clazz.getField("name");
field.set(reflectDemo, "老六");
System.out.println(reflectDemo.name);
- 反射私有字段
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Field field = clazz.getDeclaredField("age");
// 设置私有字段可访问
field.setAccessible(Boolean.TRUE);
field.set(reflectDemo, 20);
System.out.println(reflectDemo.getAge());
3.5 使用对象方法
Method
类,是反射方法的对象,一个成员方法就是一个Method
对象。
- 公共方法
通过getMethods
方法获取到所有的公共方法,包括父类的公共方法。
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Method[] methods = clazz.getMethods();
- 所有方法
通过getDeclaredMethods方法获取到改类的所有方法,包括私有方法,但是不包括父类的任何方法。
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Method[] methods = clazz.getDeclaredMethods();
- 无参方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getMethod("getMessage");
method.invoke(reflectDemo);
- 有参方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getMethod("sayHello", String.class);
method.invoke(reflectDemo, "可以的");
- 有返回值方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getMethod("getReturn");
Object obj = method.invoke(reflectDemo);
System.out.println(obj);
- 私有方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getDeclaredMethod("secret");
method.setAccessible(Boolean.TRUE);
method.invoke(reflectDemo);
4. 反射注解
4.1 使用反射注解准备
-
用于类的注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyClass { String name(); }
-
用于字段的注解
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface MyField { }
-
用于方法的注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyMethod { int value(); }
-
被修饰的类
MyClass(name = "反射什么的") public class ReflectDemo { @MyField public int age; @MyMethod(1) public void method () { } }
4.2 反射获取注解
-
获取类上的注解
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo"); // 获取类上所有的注解 Annotation[] annotations = clazz.getAnnotations(); // 获取类上指定的注解 Annotation annotation = clazz.getAnnotation(MyClass.class); // 判断类上是否有该注解 boolean isHave = clazz.isAnnotationPresent(MyClass.class);
-
获取字段上的注解
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo"); Field field = clazz.getField("age"); // 获取所有的注解 Annotation[] annotations = field.getAnnotations(); // 获取指定的注解 Annotation annotation = field.getAnnotation(MyField.class); // 判断字段上是否有指定注解 boolean isHave = field.isAnnotationPresent(MyField.class);
-
获取方法上的注解
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo"); Method method = clazz.getMethod("method"); // 获取方法上的所有注解 Annotation[] annotations = method.getAnnotations(); // 获取方法上的指定注解 Annotation annotation = method.getAnnotation(MyMethod.class); // 判断方法上是否有指定注解 boolean isHave = method.isAnnotationPresent(MyMethod.class);