的反射(Reflection)是一种强大的机制,允许程序在运行时动态获取类的信息、操作类的成员(属性、方法、构造器),甚至修改类的行为。它是框架开发(如 Spring、MyBatis)、单元测试工具(如 JUnit)的核心技术之一。
一、认识反射
反射的核心是在运行时获取类的元数据(类的结构信息),突破了传统编程 “编译时确定类型” 的限制。例如:
运行时判断任意对象的所属类;
运行时构造任意类的对象;
运行时获取 / 修改类的属性、调用类的方法(包括私有成员);
运行时处理注解。
二、Class 类详解
在 中,Class 类是反射的入口。每个类被加载到 JVM 时,会生成唯一的 Class 对象,存储该类的所有元数据(如类名、父类、接口、属性、方法等)。
2.1 获取 Class 对象的 3 种方式
// 方式1:通过 类名.class(编译时已知类)
Class<String> stringClass = String.class;
// 方式2:通过 对象.getClass()(已知对象)
String str = "hello";Class<? extends String> strClass = str.getClass();
// 方式3:通过 Class.forName("全限定类名")(动态加载,最常用)
try {
Class<?> userClass = Class.forName("com.example.User"); }
catch (ClassNotFoundException e) {
e.printStackTrace();
}
2.2 Class 类的常用方法
方法 | 说明 |
getName() | 获取类的全限定名(如 .lang.String) |
getSimpleName() | 获取类的简单名(如 String) |
getSuperclass() | 获取父类的 Class 对象 |
getInterfaces() | 获取实现的接口数组 |
getFields() | 获取所有公有属性(含父类) |
getDeclaredFields() | 获取所有属性(含私有,不含父类) |
getMethods() | 获取所有公有方法(含父类) |
getDeclaredMethods() | 获取所有方法(含私有,不含父类) |
getConstructors() | 获取所有公有构造器 |
getDeclaredConstructors() | 获取所有构造器(含私有) |
三、Class 类与多态
多态的本质是 “父类引用指向子类对象”,但反射可以突破多态的表象,直接操作子类或父类的真实信息。
示例:通过反射获取多态对象的真实类信息
假设有继承关系:Animal(父类)→ Dog(子类)。
class Animal {
public void eat() {
System.out.println("Animal eat");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog eat");
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
Animal animal = new Dog(); // 多态:父类引用指向子类对象
// 传统方式调用方法(表现多态)
animal.eat(); // 输出:Dog eat
// 反射获取真实类的信息
Class<?> realClass = animal.getClass();
System.out.println("真实类名:" + realClass.getSimpleName()); // 输出:Dog
// 反射调用父类的方法(绕过多态)
try {
Method parentEat = realClass.getSuperclass().getMethod("eat");
parentEat.invoke(animal); // 输出:Animal eat(调用了父类的原始方法)
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:
Dog eat
真实类名:Dog
Animal eat
四、反射创建类对象
通过反射可以动态创建类的实例,即使类的构造器是私有的(需设置 setAccessible(true))。
4.1 无参构造创建对象
class User {
private String name;
public User() { System.out.println("无参构造被调用"); }
public User(String name) { this.name = name; }
}
public class CreateObjectDemo {
public static void main(String[] args) {
try {
// 1. 获取User的Class对象
Class<?> userClass = Class.forName("com.example.User");
// 2. 通过无参构造创建实例(等价于 new User())
User user = (User) userClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:无参构造被调用
4.2 有参构造创建对象
public class CreateObjectWithArgsDemo {
public static void main(String[] args) {
try {
Class<?> userClass = Class.forName("com.example.User");
// 获取有参构造器(参数类型为String)
Constructor<?> constructor = userClass.getDeclaredConstructor(String.class);
// 创建实例(等价于 new User("xxx"))
User user = (User) constructor.newInstance("xxx");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.3 私有构造器创建对象(突破访问限制)
class SecretClass {
private SecretClass() { System.out.println("私有构造被调用");
}
}
public class CreatePrivateObjectDemo {
public static void main(String[] args) {
try {
Class<?> secretClass = Class.forName("com.example.SecretClass");
// 获取私有构造器
Constructor<?> privateConstructor = secretClass.getDeclaredConstructor();
// 允许访问私有成员(关键!)
privateConstructor.setAccessible(true);
// 创建实例
SecretClass instance = (SecretClass) privateConstructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:私有构造被调用
五、反射调用类方法
通过反射可以调用任意对象的方法(包括私有方法),甚至可以调用未实现的方法(动态代理的基础)。
5.1 调用公有方法
class Calculator {
public int add(int a, int b) { return a + b; }
}
public class InvokeMethodDemo {
public static void main(String[] args) {
try {
Calculator calc = new Calculator();
Class<?> calcClass = calc.getClass();
// 获取add方法(参数类型为int, int)
Method addMethod = calcClass.getMethod("add", int.class, int.class);
// 调用方法(等价于 calc.add(3, 5))
int result = (int) addMethod.invoke(calc, 3, 5);
System.out.println("计算结果:" + result); // 输出:8
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.2 调用私有方法(突破访问限制)
class PrivateMethodClass {
private String formatName(String name) {
return "Hello, " + name + "!";
}
}
public class InvokePrivateMethodDemo {
public static void main(String[] args) {
try {
PrivateMethodClass obj = new PrivateMethodClass();
Class<?> clazz = obj.getClass();
// 获取私有方法(方法名、参数类型)
Method privateMethod = clazz.getDeclaredMethod("formatName", String.class);
// 允许访问私有成员
privateMethod.setAccessible(true);
// 调用方法(等价于 obj.formatName("xxx"))
String result = (String) privateMethod.invoke(obj, "xxx");
System.out.println(result); // 输出:Hello, xxx!
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、反射修改类属性
通过反射可以直接修改对象的属性值(包括私有属性),甚至绕过 setter 方法。
6.1 修改公有属性
class Book {
public String title = "默认书名";
}
public class ModifyFieldDemo {
public static void main(String[] args) {
try {
Book book = new Book();
Class<?> bookClass = book.getClass();
// 获取公有属性title
Field titleField = bookClass.getField("title");
// 修改属性值(等价于 book.title = "反射详解")
titleField.set(book, "反射详解");
System.out.println(book.title); // 输出:反射详解
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.2 修改私有属性(突破访问限制)
class User {
private String password = "123456";}
public class ModifyPrivateFieldDemo {
public static void main(String[] args) {
try {
User user = new User();
Class<?> userClass = user.getClass();
// 获取私有属性password
Field passwordField = userClass.getDeclaredField("password");
// 允许访问私有成员
passwordField.setAccessible(true);
// 修改属性值(等价于 user.password = "new_password")
passwordField.set(user, "new_password");
// 验证修改结果
System.out.println("新密码:" + passwordField.get(user)); // 输出:new_password
} catch (Exception e) {
e.printStackTrace();
}
}
}
七、类加载器
类加载器(Class Loader)负责将 .class 文件加载到 JVM 中,生成对应的 Class 对象。 采用双亲委派模型,确保类的唯一性和安全性。
7.1 类加载器的层级
引导类加载器(Bootstrap Class Loader):加载 JDK 核心类(如 .lang.*),由 C++ 实现,无法通过 代码获取。
扩展类加载器(Extension Class Loader):加载 jre/lib/ext 目录下的 JAR 包。
应用类加载器(Application Class Loader):加载用户项目中的类(classpath 下的类)。
7.2 示例:查看类的加载器
public class ClassLoaderDemo {
public static void main(String[] args) {
// 获取String类的加载器(引导类加载器,输出null)
ClassLoader stringLoader = String.class.getClassLoader();
System.out.println("String类的加载器:" + stringLoader); // 输出:null
// 获取当前类的加载器(应用类加载器)
ClassLoader selfLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("当前类的加载器:" + selfLoader);
// 输出:sun.misc.Launcher$AppClassLoader@18b4aac2
// 获取应用类加载器的父加载器(扩展类加载器)
ClassLoader parentLoader = selfLoader.getParent();
System.out.println("父加载器:" + parentLoader);
// 输出:sun.misc.Launcher$ExtClassLoader@1b6d3586
}
}
7.3 双亲委派模型的作用
当加载一个类时,类加载器会先委托父类加载器尝试加载,直到引导类加载器。如果父类无法加载,才由当前类加载器加载。
好处:避免重复加载,防止核心类被篡改(如自定义 .lang.String 不会被加载)。
反射是 的 “动态之魂”,但过度使用会降低代码可读性和安全性(如破坏封装性)。实际开发中,框架(如 Spring)已封装了反射的复杂操作,开发者只需理解原理即可。建议结合源码(如 Spring 的 BeanFactory)深入学习反射的应用。