反射入门零基础
反射入门零基础
反射基础概念
-
什么是反射?
-
在传统(正向)编程中,你明确知道要使用的类(如
String,ArrayList)及其方法(如length(),add()),并在编译时直接编写代码创建对象和调用方法。 -
反射是一种在程序运行时(Runtime) 动态操作程序自身的能力。它允许你的代码:
-
获取任意一个类的完整结构信息(类名、父类、接口、字段、方法、构造器、注解等)。
-
动态创建该类的对象实例。
-
动态访问和修改对象的字段值(即使是
private的!需谨慎)。 -
动态调用对象的方法(即使是
private的!需谨慎)。 -
处理数组和枚举。
-
在运行时分析类的结构。
-
-
-
为什么需要反射?
-
框架开发的基石: (核心应用)
-
依赖注入 (DI/IoC - Spring): 容器读取配置(XML/注解),在运行时动态创建所需对象 (
Class.newInstance(),Constructor.newInstance()),并通过反射 (Field.set(),Method.invoke()) 将依赖对象注入。 -
对象关系映射 (ORM - Hibernate, MyBatis): 根据实体类定义,在运行时通过反射读取字段信息 (
Field),动态生成 SQL 进行数据库记录与 Java 对象的相互转换。 -
单元测试 (JUnit): 框架通过反射查找带有
@Test,@Before等注解的方法 (Method),并在运行时动态调用它们 (Method.invoke())。
-
-
动态代理 (AOP - Spring AOP): 创建实现接口的代理对象。调用代理方法时,动态转发给
InvocationHandler,其内部使用反射 (Method.invoke()) 调用目标方法,并可在前后插入通用逻辑(日志、事务)。 -
开发工具 (IDE): 代码自动补全、类型检查、调试器查看对象状态等功能依赖反射分析类信息。
-
插件/扩展机制: 应用在运行时加载外部 JAR,通过反射识别并实例化实现了特定接口或继承了基类的插件类 (
Class.forName(),Class.newInstance(),Constructor.newInstance())。 -
序列化/反序列化 (Jackson, Gson): 库使用反射 (
Class,Field) 分析对象结构,将其转换为 JSON/XML 或反之。 -
访问非公开成员 (谨慎!): 突破封装访问
private字段/方法 (测试私有方法、修改三方库行为)。通过setAccessible(true)实现,但破坏封装性。
-
反射核心类(java.lang.reflect包)
-
基石:
Class类-
代表什么? 代表一个类或接口在运行时的类型信息。是反射操作的入口点。
-
获取
Class对象的 3 种主要方式:-
对象.getClass(): 通过已有的对象实例获取。String str = "Hello"; Class<?> clazz1 = str.getClass(); // clazz1 代表 java.lang.String -
类名.class: 使用类的字面常量。最常用、直接,不会初始化类。Class<String> clazz2 = String.class; // 明确类型 Class<?> clazz3 = int.class; // 基本数据类型也可以 Class<?> clazz4 = void.class; // void 类型 -
Class.forName("完整类名"): 根据字符串形式的全限定类名(包名.类名)动态加载类并返回Class对象。会触发类的初始化。Class<?> clazz5 = Class.forName("java.lang.String"); // 加载 String 类 Class<?> clazz6 = Class.forName("com.mysql.jdbc.Driver"); // 动态加载数据库驱动
-
-
Class对象的核心功能:-
获取类信息:
-
String getName():全限定类名 (e.g.,"java.lang.String") -
String getSimpleName():简单类名 (e.g.,"String") -
String getCanonicalName():规范名称 (通常同全限定名) -
Class<? super T> getSuperclass():获取直接父类Class对象 -
Class<?>[] getInterfaces():获取实现的接口Class数组 -
int getModifiers():获取修饰符 (需配合Modifier解析) -
Package getPackage():获取包信息 -
boolean isInterface()/isArray()/isPrimitive()/isEnum()/isAnnotation():判断类型 -
boolean isAssignableFrom(Class<?> cls):判断cls是否是该类/接口的子类/实现类
-
-
获取成员信息:
-
Field[] getFields():获取所有 public 字段(包括继承的)。 -
Field getField(String name):根据名字获取指定的 public 字段(包括继承的)。 -
Field[] getDeclaredFields():获取本类声明的所有字段(包括 private, protected, package-private),不包括继承的。 -
Field getDeclaredField(String name):根据名字获取本类声明的指定字段(任何访问权限)。 -
Method[] getMethods():获取所有 public 方法(包括继承的)。 -
Method getMethod(String name, Class<?>... parameterTypes):根据方法名和参数类型列表获取指定的 public 方法(包括继承的)。 -
Method[] getDeclaredMethods():获取本类声明的所有方法(任何访问权限),不包括继承的。 -
Method getDeclaredMethod(String name, Class<?>... parameterTypes):根据方法名和参数类型列表获取本类声明的指定方法(任何访问权限)。 -
Constructor<?>[] getConstructors():获取所有 public 构造器。 -
Constructor<T> getConstructor(Class<?>... parameterTypes):根据参数类型列表获取指定的 public 构造器。 -
Constructor<?>[] getDeclaredConstructors():获取本类声明的所有构造器(任何访问权限)。 -
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):根据参数类型列表获取本类声明的指定构造器(任何访问权限)。 -
Annotation[] getAnnotations():获取类上的所有注解。 -
<A extends Annotation> A getAnnotation(Class<A> annotationClass):获取指定类型的注解。
-
-
创建实例 (过时方式):
-
T newInstance():调用类的无参 public 构造器创建新实例。已过时(Java 9+),推荐使用Constructor.newInstance()。
-
-
其他:
-
ClassLoader getClassLoader():获取加载该类的类加载器。 -
TypeVariable<Class<T>>[] getTypeParameters():获取泛型类型参数(如果类是泛型类)。
-
-
-
-
操作字段:
Field类-
代表什么? 代表类的一个字段(成员变量)。
-
获取
Field对象: 通过Class对象的getField(s)/getDeclaredField(s)方法获得(见 3.3.2)。 -
Field对象的核心功能:-
获取字段信息:
-
String getName():字段名 -
Class<?> getType():字段的声明类型 (Class 对象) -
int getModifiers():修饰符 (需配合Modifier解析) -
Annotation[] getAnnotations()/<A extends Annotation> A getAnnotation(Class<A> annotationClass):获取字段上的注解
-
-
访问字段值:
-
Object get(Object obj):获取指定对象obj上此字段的值。如果是静态字段,obj可为null。 -
void set(Object obj, Object value):将指定对象obj上此字段的值设置为value。如果是静态字段,obj可为null。 -
基本数据类型快捷方法:
getXxx(Object obj)/setXxx(Object obj, Xxx value)(e.g.,getInt,setBoolean,getDouble,setChar) 避免装箱拆箱。
-
-
访问私有字段 (关键步骤):
-
默认无法访问非
public字段。 -
void setAccessible(boolean flag):将此对象的accessible标志设置为指示的布尔值。true表示反射对象在使用时应该取消 Java 语言访问检查。 -
重要:
field.setAccessible(true);必须在调用field.get(obj)或field.set(obj, value)之前执行。 -
警告: 这破坏了封装性,应谨慎使用,并注意安全管理器可能禁止此操作。
-
-
-
-
操作方法:
Method类-
代表什么? 代表类的一个方法。
-
获取
Method对象: 通过Class对象的getMethod(s)/getDeclaredMethod(s)方法获得(见 3.3.2)。必须指定参数类型列表 (Class<?>... parameterTypes) 来区分重载方法。// 假设类有方法: public void setName(String name) 和 public void setName(String firstName, String lastName) Method setName1 = clazz.getMethod("setName", String.class); Method setName2 = clazz.getMethod("setName", String.class, String.class); -
Method对象的核心功能:-
获取方法信息:
-
String getName():方法名 -
Class<?> getReturnType():返回值类型 (Class 对象) -
Class<?>[] getParameterTypes():参数类型数组 (Class 对象数组) -
int getModifiers():修饰符 (需配合Modifier解析) -
Class<?>[] getExceptionTypes():声明抛出的异常类型数组 (Class 对象数组) -
Annotation[] getAnnotations()/getDeclaredAnnotations()/<A extends Annotation> A getAnnotation(Class<A> annotationClass):获取方法上的注解 -
boolean isVarArgs():是否声明为可变参数 (e.g.,String...)
-
-
调用方法:
-
Object invoke(Object obj, Object... args):在指定对象obj上调用此方法,并传入参数args。-
obj:调用方法的对象实例。如果是 静态方法,obj必须为null。 -
args:传递给方法的参数值。参数个数和类型必须与方法签名匹配。基本数据类型会自动装箱。 -
返回值: 方法的返回值。如果方法返回
void,则为null。基本类型返回值会被包装成对应包装类。
-
-
示例:
Object result = method.invoke(targetObject, arg1, arg2, arg3); // 调用实例方法 Object staticResult = staticMethod.invoke(null, arg1); // 调用静态方法
-
-
访问私有方法:
-
同样需要
method.setAccessible(true);才能调用private方法。
-
-
-
-
操作构造器:
Constructor<T>类-
代表什么? 代表类的一个构造方法,用于创建对象实例。
-
获取
Constructor对象: 通过Class对象的getConstructor(s)/getDeclaredConstructor(s)方法获得(见 3.3.2)。必须指定参数类型列表 (Class<?>... parameterTypes) 来区分重载构造器。// 假设类有无参构造器和 public Person(String name, int age) 构造器 Constructor<?> noArgCons = clazz.getDeclaredConstructor(); // 无参 Constructor<?> paramCons = clazz.getDeclaredConstructor(String.class, int.class); // 有参 -
Constructor对象的核心功能:-
获取构造器信息:
-
Class<?>[] getParameterTypes():参数类型数组 -
int getModifiers():修饰符 -
Class<?>[] getExceptionTypes():声明抛出的异常类型数组 -
Annotation[] getAnnotations()/getDeclaredAnnotations()/<A extends Annotation> A getAnnotation(Class<A> annotationClass):获取构造器上的注解 -
boolean isVarArgs():是否可变参数
-
-
创建对象实例 (推荐方式):
-
T newInstance(Object... initargs):使用此构造器创建并初始化类的新实例。参数initargs是传递给构造器的实际参数值。 -
这是替代
Class.newInstance()的现代方式,支持有参构造器。 -
示例:
Object obj1 = noArgCons.newInstance(); // 调用无参构造器 Object obj2 = paramCons.newInstance("Alice", 30); // 调用有参构造器
-
-
访问私有构造器:
-
同样需要
constructor.setAccessible(true);才能调用private构造器。
-
-
-
-
解析修饰符:
Modifier工具类-
作用: 解析
Class.getModifiers(),Field.getModifiers(),Method.getModifiers(),Constructor.getModifiers()返回的int值,判断具体修饰符。 -
常用静态方法: 都是
static boolean isXxx(int mod)-
isPublic(mod)/isPrivate(mod)/isProtected(mod) -
isStatic(mod)/isFinal(mod)/isAbstract(mod) -
isSynchronized(mod)/isNative(mod)/isStrict(mod)/isTransient(mod)/isVolatile(mod) -
isInterface(mod)/isAnnotation(mod)/isEnum(mod)
-
-
使用示例:
int mods = someField.getModifiers(); System.out.println("字段修饰符:"); if (Modifier.isPublic(mods)) System.out.println(" public"); if (Modifier.isPrivate(mods)) System.out.println(" private"); if (Modifier.isStatic(mods)) System.out.println(" static"); if (Modifier.isFinal(mods)) System.out.println(" final");
-
关键概念区分:getXxx vs getDeclaredXxx
-
getXxx()(不带 Declared):-
目标:获取 public 成员。
-
范围:包括本类及其所有父类/实现的接口中定义的 public 成员。
-
示例:
getFields(),getMethods(),getConstructors(),getField(name),getMethod(name, paramTypes),getConstructor(paramTypes) -
无法获取非 public 成员。
-
-
getDeclaredXxx():-
目标:获取本类直接声明的所有成员。
-
范围:仅限本类中声明的成员(字段、方法、构造器),不考虑访问权限(public, protected, package-private, private 都能获取)。
-
不包括从父类或接口继承来的任何成员。
-
示例:
getDeclaredFields(),getDeclaredMethods(),getDeclaredConstructors(),getDeclaredField(name),getDeclaredMethod(name, paramTypes),getDeclaredConstructor(paramTypes)
-
综合示例 (带详细注释)
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class DetailedReflectionExample {
public static void main(String[] args) throws Exception {
// ===== 1. 获取 Class 对象 =====
// 方式1: Class.forName (触发初始化)
Class<?> personClass = Class.forName("com.example.Person");
System.out.println("1. 获取Class对象: " + personClass.getName());
// ===== 2. 使用 Class 对象获取信息 =====
// 2.1 类基本信息
System.out.println("\n2.1 类基本信息:");
System.out.println(" 简单类名: " + personClass.getSimpleName());
System.out.println(" 父类: " + (personClass.getSuperclass() != null ? personClass.getSuperclass().getName() : "无"));
System.out.println(" 是接口? " + personClass.isInterface());
// 2.2 获取所有 public 字段 (包括继承的)
System.out.println("\n2.2 Public 字段:");
Field[] publicFields = personClass.getFields();
for (Field field : publicFields) {
System.out.println(" - " + field.getName() + " : " + field.getType().getSimpleName());
}
// 2.3 获取所有本类声明的字段 (包括 private)
System.out.println("\n2.3 Declared 字段:");
Field[] declaredFields = personClass.getDeclaredFields();
for (Field field : declaredFields) {
System.out.print(" - " + field.getName() + " : " + field.getType().getSimpleName());
// 解析修饰符
int mods = field.getModifiers();
System.out.print(" | 修饰符: ");
if (Modifier.isPublic(mods)) System.out.print("public ");
if (Modifier.isPrivate(mods)) System.out.print("private ");
if (Modifier.isProtected(mods)) System.out.print("protected ");
if (Modifier.isStatic(mods)) System.out.print("static ");
if (Modifier.isFinal(mods)) System.out.print("final ");
System.out.println();
}
// ===== 3. 创建对象实例 =====
System.out.println("\n3. 创建对象实例:");
// 3.1 获取无参构造器 (假设存在)
Constructor<?> noArgConstructor = personClass.getDeclaredConstructor();
// 3.2 使用构造器创建对象
Object personObj = noArgConstructor.newInstance();
System.out.println(" 对象创建成功: " + personObj); // 会调用对象的 toString()
// ===== 4. 操作字段 =====
System.out.println("\n4. 操作字段:");
// 4.1 获取 public 字段 "publicField" (假设存在)
Field publicField = personClass.getField("publicField");
// 4.2 获取字段值
Object publicFieldValue = publicField.get(personObj);
System.out.println(" 4.2 publicField 原始值: " + publicFieldValue);
// 4.3 设置字段值
publicField.set(personObj, "新的Public值");
System.out.println(" 4.3 publicField 新值: " + publicField.get(personObj));
// 4.4 获取 private 字段 "name" (假设存在)
Field privateNameField = personClass.getDeclaredField("name");
// 4.5 关键!设置可访问性 (突破 private)
privateNameField.setAccessible(true);
// 4.6 设置 private 字段值
privateNameField.set(personObj, "张三");
System.out.println(" 4.6 设置 private name 为: " + privateNameField.get(personObj));
// ===== 5. 操作方法 =====
System.out.println("\n5. 操作方法:");
// 5.1 获取 public 方法 "setName(String)" (假设存在)
Method setNameMethod = personClass.getMethod("setName", String.class);
// 5.2 调用方法 (修改 name)
setNameMethod.invoke(personObj, "李四");
System.out.println(" 5.2 调用 setName('李四') 后, name: " + privateNameField.get(personObj)); // 验证
// 5.3 获取 public 方法 "getName()" (假设存在)
Method getNameMethod = personClass.getMethod("getName");
// 5.4 调用方法获取返回值
Object nameValue = getNameMethod.invoke(personObj);
System.out.println(" 5.4 调用 getName(): " + nameValue);
// 5.5 获取 private 方法 "privateMethod()" (假设存在)
Method privateMethod = personClass.getDeclaredMethod("privateMethod");
// 5.6 设置可访问性
privateMethod.setAccessible(true);
// 5.7 调用 private 方法
System.out.print(" 5.7 调用 privateMethod(): ");
privateMethod.invoke(personObj);
}
// 假设的 Person 类 (放在 com.example 包下)
static class Person { // 这里用内部类简化示例,实际应在单独文件
public String publicField = "原始Public值";
private String name;
private int age;
public Person() {
System.out.println(" (内部) Person 无参构造器被调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println(" (内部) setName 被调用,name=" + name);
}
private void privateMethod() {
System.out.println(" (内部) 私有方法 privateMethod 被调用!");
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", publicField='" + publicField + "'}";
}
}
}
反射的优缺点 (深入分析)
-
优点:
-
极致灵活性: 程序行为可在运行时动态决定,无需编译时硬编码类名、方法名。这是框架和可扩展系统的灵魂。
-
框架核心: 支撑了 Spring (DI, AOP), Hibernate/MyBatis (ORM), JUnit (测试), Tomcat (Servlet 容器) 等几乎所有主流 Java 框架/容器。
-
通用能力: 可编写代码分析器、调试器、序列化库、动态代理生成器等通用工具。
-
突破限制 (双刃剑): 在特定场景下(如测试、深度集成),可以访问和操作非公开成员。
-
-
缺点:
-
性能开销:
-
反射操作(尤其是
Method.invoke())比直接方法调用慢得多(JIT 优化受限)。 -
原因:动态解析类型、方法查找、访问检查、参数装箱/拆箱、异常包装等。
-
重要: 在性能敏感的代码(高频循环、核心算法)中避免使用反射。
-
-
破坏封装性:
-
setAccessible(true)允许绕过private,protected等访问控制,违反了面向对象的基本原则。 -
可能导致预期外的行为、安全漏洞(访问敏感数据)、破坏类的内部不变性。
-
最佳实践: 除非绝对必要(如框架内部、特定测试),否则不要使用。使用时务必明确风险。
-
-
代码复杂度:
-
反射代码通常冗长、可读性差(大量字符串、异常处理)。
-
调试困难:错误常在运行时才暴露(如
NoSuchMethodException,IllegalAccessException),堆栈信息可能不如直接调用清晰。
-
-
安全隐患:
-
可能被恶意代码利用来访问内部 API、修改 final 字段、调用私有方法等。
-
安全管理器 (
SecurityManager) 可以限制反射操作(但现代 Java 中SecurityManager已被废弃)。
-
-
绕过泛型检查:
-
泛型的类型参数信息在编译后会被擦除(Type Erasure)。
-
反射在运行时操作,无法获取具体的泛型类型参数(
List<String>在运行时只是List)。 -
使用
Field.get()/Method.invoke()时编译器无法进行泛型类型安全检查,可能导致ClassCastException。
-
-
最佳实践与学习建议
-
最佳实践:
-
优先直接编码: 如果编译时类型已知,绝不使用反射。直接用
new,.操作符。 -
限制使用范围: 仅在真正需要运行时动态性的地方使用(框架、插件、通用工具、特定测试)。
-
缓存反射元数据: 避免重复查找
Class,Method,Field,Constructor对象。在初始化阶段获取并缓存它们(如放入Map)。 -
慎用
setAccessible(true): 深刻理解其破坏封装的后果。仅在无其他替代方案且操作对象可控时使用。避免在生产代码中随意使用。 -
严格异常处理: 反射 API 抛出大量检查型异常 (
ClassNotFoundException,NoSuchMethodException,IllegalAccessException,InvocationTargetException,InstantiationException)。必须用try-catch或throws妥善处理。InvocationTargetException包裹了目标方法真正抛出的异常,需用getCause()获取。 -
考虑替代方案:
-
对象创建:依赖注入框架 (Spring, Guice)。
-
动态代理:标准接口 +
java.lang.reflect.Proxy或 CGLIB/Byte Buddy (Spring AOP)。 -
测试私有成员:优先重构使方法可测,或使用 Mockito/PowerMock (后者也基于反射)。
-
配置驱动:使用 Properties, YAML, JSON 配合工厂模式。
-
-
-
学习路径建议:
-
打牢基础: 精通 Java OOP(类、对象、继承、接口、访问控制、异常)。
-
掌握
Class: 理解其意义,熟练三种获取方式。 -
逐个击破: 分别深入学习
Field(获取/设值,setAccessible),Method(获取/调用, 参数类型),Constructor(获取/创建实例)。 -
理解
Modifier: 学会解析修饰符常量。 -
区分
Declared: 牢记getXxx和getDeclaredXxx的本质区别。 -
动手实践: 编写大量小例子,操作一个简单类(如上面的
Person),尝试各种操作。 -
阅读源码 (进阶): 选读 Spring Core (Bean 创建、依赖注入) 或 JUnit (Runner, 发现测试方法) 的部分源码,观察反射的实际应用。
-
关注性能与安全: 时刻警惕反射的代价和风险。
-
总结
反射是 Java 赋予开发者的一项强大而危险的能力。它如同程序世界的“内窥镜”和“手术刀”,让你能在运行时洞察和修改类的内部结构。这份详细指南从零基础出发,通过清晰的序号结构,系统讲解了反射的核心概念(Class, Field, Method, Constructor, Modifier)、获取方式、核心 API 用法、关键区别 (getXxx vs getDeclaredXxx)、典型应用场景、重要优缺点以及最佳实践。记住:反射是框架开发者的利器,而非日常业务编码的常规工具。 在绝大多数情况下,清晰、直接、编译时确定的代码才是首选。
更多推荐



所有评论(0)