Java 反射原理与实际使用场景
反射就是 Reflection,Java 的反射是指程序在运行期可以拿到一个对象的所有信息。
一. 类的加载
加载的流程
class
是由JVM
在执行过程中动态加载的。JVM
在第一次读取到一种class
类型时,将其加载进内存。Java
的所有类都是一个叫做Class
的类的一个实例,Class
类大概如下所示。每加载一种class
,JVM
就为其创建一个Class
类型的实例,并关联起来。注意:这里的Class
类型是一个名叫Class
的class
。
public final class Class {
private Class() {}
}
注意:JVM
在执行Java
程序的时候,并不是一次性把所有用到的class
全部加载到内存,而是第一次需要用到class
时才加载。
类加载举例
以String
类为例,当JVM
加载String
类时,它首先读取String.class
文件(String.java
编译而来)到内存,然后,为String
类创建一个Class
实例并关联起来,如下。这个Class
实例是JVM
内部创建的,如果我们查看JDK
源码,可以发现Class
类的构造方法是private
,只有JVM
能创建Class
实例,我们自己的Java
程序是无法创建Class
实例的。所以,JVM
持有的每个Class
实例都指向一个数据类型(class
或interface
):
Class cls = new Class(String);
二. 获取class方法
JVM
为每个加载的class
创建了对应的Class
实例,并在实例中保存了该class
的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class
实例,我们就可以通过这个Class
实例获取到该实例对应的class
的所有信息。这种通过Class
实例获取class
信息的方法称为反射(Reflection
)。如何获取一个class
的Class
实例?有三个方法:
- 方法一:直接通过一个
class
的静态变量class
获取:Class<?> cls = String.class;
- 方法二:如果我们有一个实例变量,可以通过该实例变量提供的
getClass()
方法获取:Class<?> cls = "Hello".getClass();
- 方法三:如果知道一个
class
的完整类名,可以通过静态方法Class.forName()
获取:Class<?> cls = Class.forName("java.lang.String");
public class Main {
public static void main(String[] args) throws Exception {
Class<?> cls1 = String.class;
Class<?> cls2 = "Hello".getClass();
Class<?> cls3 = Class.forName("java.lang.String");
System.out.println("cls1.equals(cls2) = " + cls1.equals(cls2)); // true
System.out.println("cls2.equals(cls3) = " + cls2.equals(cls3)); // true
}
}
三. 反射场景
- 测试类
package org.example;
class Person {
protected String name;
protected int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Student extends Person {
public int score;
public Student() {
}
public Student(String name, int age, int score) {
super(name, age);
this.score = score;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "name:" + this.name + " " +
"age:" + this.age + " " +
"score:" + this.score;
}
}
- 常用方法
public class Main {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("org.example.Student");
Method[] methods1 = cls.getDeclaredMethods(); // 获取 class 对象的所有方法(不包括父类)
Method[] methods2 = cls.getMethods(); // 获取 class 对象的public方法(包括父类)
Field[] fields1 = cls.getDeclaredFields(); // 获取 class 对象的所有属性(不包括父类)
Field[] fields2 = cls.getFields(); // 获取 class 对象的public属性(包括父类)
Class<?> superclass = cls.getSuperclass(); // 获取 class 对象的父类
Class<?>[] interfaces = cls.getInterfaces(); // 获取 class 对象的所有接口
Constructor<?>[] constructors1 = cls.getDeclaredConstructors(); // 获取 class 对象的所有声明构造函数
Constructor<?>[] constructors2 = cls.getConstructors(); // 获取 class 对象public构造函数
Constructor<?> constructor1 = cls.getDeclaredConstructor( // 获取指定声明构造函数
String.class, int.class, int.class);
Constructor<?> constructor2 = cls.getConstructor( // 获取指定声明的 public 构造函数
String.class, int.class, int.class);
Annotation[] annotations = cls.getAnnotations(); // 获取 class 对象的所有注解
Annotation annotation = cls.getAnnotation(Deprecated.class); // 获取 class 对象指定注解
Type type = cls.getGenericSuperclass(); // 获取 class 对象的直接父类的 Type
Type[] types = cls.getGenericInterfaces(); // 获取 class 对象的所有接口的 Type 集合
Object zhang = constructor1.newInstance("zhang", 20, 98); // 初始化实例
Field scoreField = cls.getField("score"); // 获取某个 public 字段
scoreField.set(zhang, 90); // 设置某个 public 字段的值
Method setAgeMethod = cls.getMethod("setAge", int.class); // 获取某个 public 方法
setAgeMethod.invoke(zhang, 30); // 调用某个 public 方法
}
}
实际场景1
编码阶段不知道需要实例化的类名是哪个,需要在 runtime 从配置文件中加载
Class<?> cls = Class.forName("org.example.Student");
实际场景2
在 runtime 阶段,需要临时访问类的某个私有属性
package org.example;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Person {
private int age;
public Person(int age) {
this.age = age;
}
private void setAge(int age) {
this.age = age;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("org.example.Person");
Constructor<?> declaredConstructor = cls.getDeclaredConstructor(int.class);
Object zhang = declaredConstructor.newInstance(18);
// 属性赋值(私有、公有)
Field ageField = cls.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(zhang, 19);
System.out.println(ageField.get(zhang));
// 调用方法(私有、公有)
Method setAge = cls.getDeclaredMethod("setAge", int.class);
setAge.setAccessible(true);
setAge.invoke(zhang, 22);
System.out.println(ageField.get(zhang));
}
}
四. Trick奇技淫巧
- 强行设置 Integer 的值
package org.example;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Main {
public static void main(String[] args) throws ReflectiveOperationException {
Integer i = 10;
Field field = Integer.class.getDeclaredField("value");
// 这里要获取 Field 类的 modifiers 字段进行修改
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
// 去除 final 标记
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setAccessible(true);
// 强行设置值
field.set(i, 100);
System.out.println(i);
}
}
- 强制修改 list 的 size
package org.example;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) throws ReflectiveOperationException {
List<String> list = new ArrayList<>();
Field field = ArrayList.class.getDeclaredField("size");
field.setAccessible(true);
field.set(list, 10); // 强制修改size的大小为 10
list.add("test"); // 只添加一个元素
System.out.println(list.size()); // 大小直接变成 11
}
}