十五、反射

一、反射(reflection)

:::note

  • 反射: 加载完类之后,在堆中会产生一个 Class 类 对象(一个类只有一个 Class 类对象),这个类对象包含了该类的所有信息。
    • 通过这个类对象,可以操作该类的所有方法或属性;
      :::

反射相关的主要类(反射中, 万物皆对象):

java.lang.Class代表一个类Class 对象表示某个类加载后再堆中的对象
java.lang.reflect.Method代表类的方法Method 对象表示某个类的方法
java.lang.reflect.Field代表类的成员变量Field 对象表示某个类的成员变量
java.lang.refect.Constructor代表类的构造方法Constructor 对象表示构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// CatDemo1.java
public class CatDemo1 {
public String name = "波斯喵";

public CatDemo1() {
}

public CatDemo1(String name) {
this.name = name;
}

public void hi(){
System.out.println("招财猫~");
}

public void food(){
System.out.println("猫吃鱼,狗吃肉~");
}
}
1
2
3
4
// classCat.properties

className=com.reflection.CatDemo1
method:food
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 反射初体验
public class Demo1 {
public static void main(String[] args) throws Exception {
// 1. 读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/classCat.properties"));

String className = properties.getProperty("className");
System.out.println("className = " + className);
String method = properties.getProperty("method");
System.out.println("method = " + method);

// 1. 加载类, 返回 class 对象
Class<?> cls = Class.forName(className);
// 2. 通过类对象 cls, 加载该类的实例对象
Object o = cls.newInstance();

// 3. 通过 加载的类 cls 获取 方法对象(反射中,万物皆对象)
Method clsMethod = cls.getMethod(method);
// 通过 方法对象.invoke(类对象实例) 调用方法
clsMethod.invoke(o);

// 4. 通过 加载类 cls , 获取成员变量 对象
Field name = cls.getField("name");
// 通过 成员变量对象.get(加载对象实例) 获取成员变量
System.out.println(name.get(o));

// 5. 通过 加载类 cls 获取 构造器(无参)
Constructor constructors = cls.getConstructor();
System.out.println(constructors);

// 有参构造器, String.class 就是 String 类的 class 对象
Constructor constructors1 = cls.getConstructor(String.class);
System.out.println(constructors1);

}
}

1. Class 类

:::note

  • Class 也是类,也继承了 Object 类;
  • Class 类对象不是 new 出来的,而是系统创建的;
  • 对于某个类的 Class 类对象,在内存中只有一份,因此类只加载一次;
  • 每个类的实例都会记得自己是由那个 Class 实例做生成;
  • 通过 Class 对象可以完整地得到一个类的完成结构,通过一系列 API 操作;
  • Class 对象时存在堆中;
  • 类的字节码二进制数据,是放在方法区的,有的地方称为元数据(包括方法代码,变量名,方法名等)
    :::

:::tip
反射优化:

  • Method 和 Field、Constructor 对象都有 setAccessible() 方法;
  • setAccessible 作用是启动和禁用访问安全检查的开关
    • true : 表示反射的对象在使用时取消访问检查, 提高反射效率;
    • false : 表示反射对象执行访问检查;
      :::

class 类的常用方法:

方法名功能说明
static Class forName(String name)返回指定类名 name 的 class 对象
Object newInstance()调用缺省构造函数, 返回该 Class 对象的一个实例
getName()返回此 Class 对象所表示的实体(类、接口等)名称
Class getSuperClass()返回当前 Class 对象的父类的 Class 对象
Class [] getInterfaces()获取当前 Class 对象的接口
ClassLoader getClassLoader()返回该类的类加载器
Class getSuperclass()返回表示此 Class 所表示的实体的 超类的 Class
constructor[] getConstructors()返回一个包含某些 Constructor 对象的数组
Field[] getDeclaredFields()返回 Field 对象的一个数组
Method getMethod返回一个 Method 对象, 此对象的形参类型为 paramType
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Class 类常用方法
public class ClassDemo2 {
public static void main(String[] args) throws Exception {

String pathAll = "com.reflection.Car";
// 获取 Car 类 对应的 class 对象
Class<?> cls = Class.forName(pathAll);
System.out.println(cls); // 显示 cls 对象是那个类的 Class 对象 - class com.reflection.Car
System.out.println(cls.getClass()); // 输出 cls 运行类型 - class java.lang.Class

// 得到包名
System.out.println(cls.getPackage().getName()); // com.reflection

// 得到全类名
System.out.println(cls.getName()); // com.reflection.Car

// 通过 cls 创建实例对象
Car car = (Car) cls.getConstructor().newInstance();
System.out.println(car); // Car{brand='宝马', price=10000, color='蓝色'}

// 通过 反射 获取属性
Field brand = cls.getField("brand");
System.out.println(brand.get(car));

// 通过 反射 给属性赋值
brand.set(car, "大奔");
System.out.println(brand.get(car));

// 获取所有的属性
Field[] fields = cls.getFields();
for (int i = 0; i < fields.length; i++) {
System.out.println(fields[i].get(car));
}
}
}

:::tip
获取 Class 类对象的方法:

  • 已知全类名, 且该类在类路径下, 可通过 Class 类的静态方法 forName() 获取;
    • 如 Class cls = Class.forName(“com.reflection.Car”);
    • 主要应用于: 读取配置文件, 读取类全路径, 加载类
  • 已知具体的类, 通过类的 Class 获取, 该方法最 安全可靠, 性能最高
    • Class cls = Car.class;
    • 主要应用于: 参数传递, 如通过反射得到对应构造器对象;
  • 已知某个类的实例, 调用该实例的 getClass() 方法获取 class 对象
    • Class cls = 对象.getClass()
    • 主要应用于 : 通过创建好的对象, 获取 class 对象
      :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 4 种 获取 Class 类对象的方法
public class GetClass1 {
public static void main(String[] args) throws Exception {
// Class.forName
String classPath = "com.reflection.Car";
Class<?> cls1 = Class.forName(classPath);
System.out.println(cls1);

// 类名.class
Class<Car> cls2 = Car.class;
System.out.println(cls2);

// 对象.getClass()
Car car = new Car();
Class<? extends Car> cls3 = car.getClass();
System.out.println(cls3);

// 通过类加载器获取
// 得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
// 通过加载器 得到 Class 对象
Class<?> cls4 = classLoader.loadClass(classPath);
System.out.println(cls4);
}
}

2. 类加载

:::tip

  • 静态加载 : 编译时加载相关的类, 如果没有则报错, 依赖性太强
  • 动态加载 : 运行时加载需要的类, 如果运行时不用该类, 则不报错, 降低依赖性;
    • 即使 类 不存在, 也不会报错;
      :::

:::tip
类加载的时机:

  • 当创建对象时( new) - 静态加载;
  • 当子类被加载时 - 静态加载;
  • 调用类中的静态成员时 - 静态加载;
  • 通过反射 - 动态加载;
    :::

:::tip
类加载的三个阶段:

  1. 加载阶段 : 将字节码文件从不同的数据源 (可能是 class 文件、也可能是 jar 包、甚至网络) 转换为 二进制字节流加载到内存中, 并生成一个代表该类的 java.long.Class 对象
  2. 连接阶段 :
    1. 验证 : 为了确保 Class 文件的 字节流中包含的信息符合当前 JVM 的要求 , 且不危害 JVM 自身安全;
      1. 包括: 文件格式验证、元数据验证、字节码验证 和 符号引用验证
      2. 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施, 缩短 JVM 类加载的时间;
    2. 准备 : 对静态变量 分配内存并初始化 (对应数据类的默认初始值, 如 0, null, false 等), 这些变量的初始值都会在方法区中进行分配;
      1. public int n1 = 10; : 是实例属性, 在准备阶段不会分配内存
      2. public static int n2 = 20;: 是静态变量, 会分配内存, 初始值是 0, 而不是 20;
      3. public static final int n3 = 30;: 是常量, 一旦不知就不可变;
    3. 解析 : JVM 将常量池内的符号引用替换为直接引用的过程;
  3. 初始化阶段 : 真正开始执行类中定义的 Java 程序代码, 此阶段是执行 <clinit>() 方法的过程;
    1. <clinit>() : 是由编译器按语句在源文件中出现的顺序, 依次自动收集类中的所有 静态变量 的赋值动作和静态代码块 中的语句, 并进行合并
    2. JVM 会保证一个类 <clinit>() 方法在多线程环境中被正确的加锁、同步, 如果多线程同时去初始化一个类, 那么只会有一个线程去执行这个类 <clinit>() 方法, 其他线程都会阻塞等待, 直到活动线程执行完毕;
      :::

3. 获取类的结构信息

1. 类对象

getName获取全类名
getSimpleName获取简单类名
getFields获取所有 public 修饰的属性, 包括本类以及父类的
getDeclaredFields获取本类中所有属性
getMethods获取所有 public 修饰的方法, 包括 本类以及父类的
getDeclaredMethods获取本类中的所有方法
getConstructors获取所有 public 修饰的本类构造器
getDeclaredConstructors获取本类中所有的构造器
getPackage以 package 形式返回 包信息
getSuperClass以 class 形式返回 父类信息
getInterfaces以 class[] 形式返回 接口信息
getAnnotations以 Annotation[] 形式返回注释信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 通过类对象获取
public class ReflectionDemo1 {
@Test
public void api_01() throws Exception {
Class<?> cls = Class.forName("com.reflection.Person");

// getName 获取全类名
System.out.println(cls.getName()); // com.reflection.Person

// getSimpleName 获取简单类名
System.out.println(cls.getSimpleName()); // Person

// getFields 获取所有 public 修饰的属性, 包括本类以及父类的
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println("本类以及父类的public属性"+field.getName()); // name hobby
}

// getDeclaredFields 获取本类中所有属性
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("获取本类中所有属性" + declaredField.getName()); // name age job sal
}

// getMethods 获取所有 public 修饰的方法, 包括 本类以及父类的
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println("本类以及父类的public方法"+method.getName());
}

// getDeclaredMethods 获取本类中的所有方法
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("获取本类中的所有方法" + declaredMethod.getName());
}
// getConstructors 获取所有 public 修饰的本类构造器
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类以及父类的public构造器" + constructor.getName());
}

// getDeclaredConstructors 获取本类中所有的构造器
Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("获取本类中所有的构造器"+declaredConstructor);
}

// getPackage 以 package 形式返回 包信息
Package aPackage = cls.getPackage();
System.out.println(aPackage);

// getSuperClass 以 class 形式返回 父类信息
Class<?> superclass = cls.getSuperclass();
System.out.println(superclass);

// getInterfaces 以 class[] 形式返回 接口信息
Class<?>[] interfaces = cls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface.getName());
}

// getAnnotations 以 Annotation[] 形式返回注释信息
Annotation[] annotations = cls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}

class A{
public String hobby;
}

class Person extends A{
public String name;
protected int age;
String job;
private double sal;

public void m1(){}
protected void m2(){}
void m3(){}
private void m4(){}

}

2. 属性对象

命令说明
getModifiers以 int 形式返回修饰符, 默认修饰符 为 0 [ public 为 1, private 为 2, protected 为 4, static 为 8, final 为 16] 如: protected static int age; 返回 int 值为 12
getType以 Class 形式返回类型
getName返回属性名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 属性对象 方法
public class ReflectionDemo1 {
@Test
public void api_01() throws Exception {
Class<?> cls = Class.forName("com.reflection.Person");
Field[] fields1 = cls.getDeclaredFields();
for (Field field : fields1) {
System.out.println("int 形式返回修饰符: " + field.getName() +"="+field.getModifiers() + " "+ field.getType());
}
}
}

class A{
public String hobby;
}

class Person extends A{
public String name;
protected static int age;
String job;
private double sal;

public void m1(){}
protected void m2(){}
void m3(){}
private void m4(){}
}

// 运行结果
int 形式返回修饰符: name=1 class java.lang.String name
int 形式返回修饰符: age=12 int age
int 形式返回修饰符: job=0 class java.lang.String job
int 形式返回修饰符: sal=2 double sal

3. 方法对象

命令说明
getModifiers以 int 形式返回修饰符, 默认修饰符 为 0[ public 为 1, private 为 2, protected 为 4, static 为 8, final 为 16] 如: protected static int age; 返回 int 值为 12
getReturnType以 Class 形式获取, 返回类型
getName返回方法名
getParameterTypes以 Class[] 返回参数类型数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 方法对象 方法
public class ReflectionDemo1 {
@Test
public void api_01() throws Exception {
Class<?> cls = Class.forName("com.reflection.Person");
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println("本类中所有的方法: " + method.getName() + " 修饰符值="+ method.getModifiers() +" "+ method.getReturnType());
Class<?>[] types = method.getParameterTypes();
for (Class<?> type : types) {
System.out.println("形参数据类型"+type.getName());
}
}
}
}

class A{
public String hobby;
}

class Person extends A{
public String name;
protected static int age;
String job;
private double sal;

public String m1(String name, int age, String job){
return "";
}
protected void m2(){}
void m3(){}
private void m4(){}
}

// 运行结果
本类中所有的方法: m3 修饰符值=0 void
本类中所有的方法: m2 修饰符值=4 void
本类中所有的方法: m1 修饰符值=1 class java.lang.String
形参数据类型java.lang.String
形参数据类型int
形参数据类型java.lang.String
本类中所有的方法: m4 修饰符值=2 void

4. 构造器对象

命令说明
getModifiers以 int 形式返回修饰符
getName返回构造器(全类名)
getParameterTypes以 Class[] 返回参数类型数组

4. 通过反射创建对象

Class 类 相关方法:

命令说明
newInstance调用类中的无参构造器, 获取对应类的对象
getConstructor(Class…)根据参数列表, 获取对应的 public 构造器对象
getDecalaredConstructor(Class…)根据参数列表, 获取对应的所有构造器对象

Constructor 类相关方法:

命令说明
setAccessible爆破, 使反射可以访问 private 构造器
newInstance(object…)调用构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 通过反射创建对象
public class Demo2 {
public static void main(String[] args) throws Exception {
// 获取 user 类的 Class 对象
Class<?> userClass = Class.forName("com.reflection.User");

// 通过 public 无参构造器 创建实例
User user = (User) userClass.getConstructor().newInstance();
System.out.println(user);

// 通过 pubic 有参构造器 创建实例 (先得到对应的构造器, 再创建实例,传入实参)
// String.class : 形参的 Class 对象
Constructor<?> constructor = userClass.getConstructor(String.class);
Object o = constructor.newInstance("宝塔镇河妖");
System.out.println(o);

// 通过 非 pubic 有参构造器 创建实例
Constructor<?> dc = userClass.getDeclaredConstructor(int.class, String.class);
dc.setAccessible(true); // 爆破, 使用反射可以访问 private 构造器
Object o1 = dc.newInstance(200, "小妖怪");
System.out.println(o1);
}
}

class User{
private int age = 10;
private String name = "天王盖地虎";

public User() {}

public User(String name) {
this.name = name;
}

private User(int age, String name) {
this.age = age;
this.name = name;
}

@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}

5. 通过反射访问属性

:::tip

  • 根据属性名获取 Field 对象
    • Field f = class对象.getDeclaredField(属性名);
  • 爆破 : f.setAccessible(true);
  • 访问:
    • f.set(o, 值); // o 表示对象
    • syso(f.get(o));
  • 如果是静态属性, 则 set 和 get 中的参数 o, 可以写成 null
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 反射爆破 访问 属性
public class Demo3 {
public static void main(String[] args)throws Exception {
Class<?> cla = Class.forName("com.reflection.Student");

// 使用反射 操作 public 属性对象
Object o = cla.getConstructor().newInstance();
Field age = cla.getField("age");
age.set(o, 88);
System.out.println(o); // Student{age=88,name=null}
System.out.println(age.get(o)); // 88

// 使用反射 操作 private 属性对象
Field name = cla.getDeclaredField("name");
name.setAccessible(true);

// name.set(o, "张三");
name.set(null, "李四"); // 因为 name 是 static 属性, o 可以写成 null
System.out.println(o); // Student{age=88,name=张三}

// 等价的
// System.out.println(name.get(0));
System.out.println(name.get(null)); // 只适用于静态属性
}
}

class Student{
public int age;
private static String name;

public Student() {}

@Override
public String toString() {
return "Student{" + "age=" + age + ",name="+ name +'}';
}
}

6. 通过反射访问方法

:::tip

  • 根据方法名 和 参数列表获取 Method 方法对象;
    • Method m = 类对象.getDeclared Method(方法名, XX.class);
  • 获取对象: Object o = 类对象.newInstance();
  • 爆破 : m.setAccessible(true);
  • 访问: Object returnValue = m.invoke(o.实参列表);
  • 在反射中, 如果有返回值, 统一返回 Object 类型, 但是运行类型和定义类型一致

*如果是静态方法, 则 invoke 的参数 o, 可以写成 null
:::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 通过反射访问方法
public class Demo4 {
public static void main(String[] args) throws Exception{
Class<?> cls = Class.forName("com.reflection.Boss");
Object o = cls.getConstructor().newInstance();

// 调用 public 的 方法对象
Method hi = cls.getMethod("hi", String.class);
hi.invoke(o, "王五");

// 调用 private 的 方法对象
Method say = cls.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);
Object o1 = say.invoke(null, 30, "赵六", '叼');
System.out.println(o1);
}
}

class Boss{
public int age;
public static String name;

public Boss() {
}

private static String say(int n, String s, char c){
return n+" "+s+" "+c;
}

public void hi(String s){
System.out.println("hi "+ s);
}
}