Java反射全解(八股)
✅ Java反射机制(Reflection)简介
反射(Reflection)是 Java 的元编程特性,允许在运行时获取类的信息、访问属性、调用方法,甚至操作私有成员。它属于 java.lang.reflect
包的一部分。
反射功能主要通过 java.lang.Class
类及 java.lang.reflect
包中的类如 Method, Field, Constructor 等来实现。
Java 程序的执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM 进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。
🚀 反射的作用
-
运行时获取类的信息
- 类名、字段、方法、构造器等。
-
动态创建对象
- 无需显式调用构造器。
-
动态访问属性和方法
- 无需在编译期确定。
-
修改或访问私有成员
- 打破封装,修改
private
字段或调用private
方法。
- 打破封装,修改
-
框架底层实现
- Spring、MyBatis、Hibernate 等框架大量使用反射实现依赖注入、AOP 等功能。
✅ 反射的常见操作
📌 1️⃣ 获取 Class
对象
反射的入口是 Class
类,通过它可以访问类的元数据:
// 方式一:通过类名
Class<?> clazz1 = Person.class;// 方式二:通过对象
Person p = new Person();
Class<?> clazz2 = p.getClass();// 方式三:通过类的全限定名(常用)
Class<?> clazz3 = Class.forName("com.example.Person");// 创建对象
String className = "java.util.Date";
Class<?> cls = Class.forName(className);
Object obj = cls.newInstance();
System.out.println(obj.getClass().getName());
📌 区别:
-
Person.class
→ 编译时确定类型。 -
p.getClass()
→ 运行时获取对象的类型。 -
Class.forName()
→ 运行时动态加载类(支持全限定名)。
📌 2️⃣ 获取类的信息
获取类名
System.out.println(clazz1.getName()); // 类的全限定名:com.example.Person
System.out.println(clazz1.getSimpleName()); // 类名:Person
获取字段
Field[] fields = clazz1.getDeclaredFields();
for (Field field : fields) {System.out.println("字段名称:" + field.getName());System.out.println("字段类型:" + field.getType());
}
获取方法
Method[] methods = clazz1.getDeclaredMethods();
for (Method method : methods) {System.out.println("方法名:" + method.getName());System.out.println("返回类型:" + method.getReturnType());System.out.println("参数类型:" + Arrays.toString(method.getParameterTypes()));
}
获取构造器
Constructor<?>[] constructors = clazz1.getConstructors();
for (Constructor<?> constructor : constructors) {System.out.println("构造器参数:" + Arrays.toString(constructor.getParameterTypes()));
}
📌 3️⃣ 使用反射创建对象
调用无参构造器
Class<?> clazz = Class.forName("com.example.Person");
Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println(obj);
调用带参构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("Alice", 25);
System.out.println(obj);
📌 4️⃣ 动态访问属性
修改 public
字段
Field field = clazz.getField("name");
field.set(obj, "Bob"); // 修改字段值
System.out.println(field.get(obj)); // 获取字段值
修改 private
字段
Field field = clazz.getDeclaredField("age");
field.setAccessible(true); // 暴力破解私有访问权限
field.set(obj, 30);
System.out.println(field.get(obj));
📌 5️⃣ 动态调用方法
调用 public
方法
Method method = clazz.getMethod("sayHello");
method.invoke(obj); // 调用方法
调用 private
方法
Method method = clazz.getDeclaredMethod("secretMethod");
method.setAccessible(true); // 暴力破解访问权限
method.invoke(obj);
📌 6️⃣ 判断注解
检测类上是否存在注解
if (clazz.isAnnotationPresent(MyAnnotation.class)) {System.out.println("类上存在 MyAnnotation 注解");
}
获取字段上的注解
Field field = clazz.getDeclaredField("name");
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
if (annotation != null) {System.out.println("注解值:" + annotation.value());
}
✅ 反射的常见应用场景
🚀 1️⃣ 动态加载类(避免硬编码)
在开发中,有时类名、方法名在编译时无法确定,可以通过反射在运行时加载:
// 动态加载类
String className = "com.example.Person";
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println(obj);
📌 应用场景
- JDBC 动态加载数据库驱动:
Class.forName("com.mysql.cj.jdbc.Driver");
🚀 2️⃣ 框架底层实现
Spring 中的依赖注入
// 通过反射动态创建对象并注入属性
Object bean = clazz.getDeclaredConstructor().newInstance();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(bean, "Spring Injected Name");
System.out.println(field.get(bean));
MyBatis 映射数据库字段
// 动态映射数据库字段
String columnName = "username";
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj, resultSet.getString(columnName));
Java 的动态代理(Dynamic Proxy)机制
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class<?>[] { MyInterface.class },handler
);
JUnit 和 TestNG 等测试框架
反射允许框架扫描类,查找带有特定注解(如 @Test)的方法,并在运行时调用它们。
Method testMethod = testClass.getMethod("testSomething");
testMethod.invoke(testInstance);
✅ 反射的优缺点
优点 | 缺点 |
---|---|
动态加载类,灵活性强 | 性能损耗较大 |
访问私有字段和方法 | 不安全,可能破坏封装 |
框架底层常用 | 代码可读性较差 |
动态生成和调用方法 | 易出错,难以调试 |
✅ 反射的优化建议
-
缓存反射对象
-
反射操作比直接调用慢,频繁使用反射时,建议将
Class
、Field
、Method
等缓存起来。 -
例如:
-
Map<String, Method> methodCache = new HashMap<>();
Method method = clazz.getMethod("getName");
methodCache.put("getName", method);
-
避免频繁使用反射
-
尽量在启动时完成反射初始化,运行时减少反射调用。
-
例如:Spring 将 Bean 实例化和依赖注入在启动时完成。
-
反射符合面向对象吗
✅ 一定程度上是支持 OOP 的
-
反射提供了一种灵活的手段,实现框架化、解耦(如 Spring、JDBC、MyBatis 都 heavily 用反射)
-
让程序具备一定的“自描述、自适应能力”,这在某种程度上是“高阶封装”
❌ 但它也打破了封装性
-
能访问私有字段、方法,违背了封装和信息隐藏
-
比如你不希望外部改你类的私有属性,但反射能做到
我认为反射本身并不是违反面向对象规范的,它是 Java 提供的一种底层机制,目的是为了增强语言的灵活性和扩展性。但它确实可能被滥用,从而打破封装、破坏安全性,所以在使用时要权衡设计目标和风险。在一些框架、IOC 场景中,反射是非常重要和合理的工具。
比如我们在用 Spring 的时候,反射帮助我们动态注入 Bean、实现 AOP,本质是为了更好地解耦和扩展,这是服务于面向对象设计目标的,所以合理使用反射,是在 OOP 的范围内的。
🚀 总结
-
反射是 Java 元编程的重要特性,可以在运行时操作类和对象。
-
在框架(如 Spring、MyBatis、Hibernate)中广泛应用。
-
虽然反射灵活,但性能较低,频繁使用时需注意优化。
-
常见操作包括:动态创建对象、访问私有字段、调用方法、处理注解等。