Java反射完全指南:从入门到精通
🎯 适合人群:Java小白到进阶开发者
🧭 学习目标:全面理解 Class、反射获取类信息/字段/方法/构造、类加载器、常见异常与最佳实践
⏱️ 阅读时长:约30-40分钟
🧠 为什么要学反射?
反射(Reflection)让我们在“运行时”动态地检查和操作类的结构:查询类名、父类、接口,读取/设置字段值,调用方法,构造对象。它是很多框架(Spring、ORM、序列化、依赖注入)背后的基石。
记住两件事:
- 反射是“动态”的(运行时),能做编译期做不到的事;
- 功能强、开销也更高,用在“需要动态性”的地方(如配置驱动、框架内部)。
🔤 反射与 Class:拿到“类的镜子”
获取 Class 的三种方式:
// 1) 通过类名 .class(编译期已知类型)
Class<Student> c1 = Student.class;
// 2) 通过对象实例 .getClass()
Student s = new Student();
Class<?> c2 = s.getClass();
// 3) 通过全限定名 Class.forName(运行时按名称加载)
Class<?> c3 = Class.forName("chapter10.Student");
区别:
Class.forName会触发类加载(可能执行静态代码块),而.class/getClass()通常不会立即初始化。
🧱 读取类的基本信息(基于 chapter10 示例)
示例参考 Java01_Reflect.java:
User user = new Child();
Class<? extends User> aClass = user.getClass();
System.out.println(aClass.getName()); // 包+类名
System.out.println(aClass.getSimpleName()); // 简单类名
System.out.println(aClass.getPackageName()); // 包名Class<?> superClass = aClass.getSuperclass(); // 父类
Class<?>[] interfaces = aClass.getInterfaces(); // 接口数组int modifiers = aClass.getModifiers(); // 修饰符位掩码
boolean isPrivate = java.lang.reflect.Modifier.isPrivate(modifiers);
注意:示例中出现的 aClass.getField("xxxxx") 或 getDeclaredMethod("xxxx") 只是占位写法,真实运行会 NoSuchFieldException/NoSuchMethodException。下文给出正确示例。
🧩 字段 / 方法 / 构造 的获取与使用
字段(Field):
// 仅 public 字段(含父类)
Field[] publicFields = aClass.getFields();
// 所有声明的字段(不含父类)
Field[] allFields = aClass.getDeclaredFields();// 单个字段
Field f = aClass.getDeclaredField("account"); // 可访问私有字段
f.setAccessible(true); // 关闭权限检查(有风险,需受信任环境)
Object value = f.get(obj);
f.set(obj, "admin");
方法(Method):
// 仅 public 方法(含父类)
Method[] publicMethods = aClass.getMethods();
// 所有声明的方法(不含父类)
Method[] declaredMethods = aClass.getDeclaredMethods();// 单个无参方法
Method m1 = aClass.getMethod("login");
Object r1 = m1.invoke(obj);// 单个有参方法
Method m2 = aClass.getMethod("setAge", int.class);
m2.invoke(obj, 18);
构造(Constructor):
// 仅 public 构造器
Constructor<?>[] publicCtors = aClass.getConstructors();
// 所有声明的构造器
Constructor<?>[] allCtors = aClass.getDeclaredConstructors();// 无参构造
Constructor<?> ctor0 = aClass.getDeclaredConstructor();
ctor0.setAccessible(true);
Object obj = ctor0.newInstance();// 有参构造
Constructor<?> ctor = aClass.getDeclaredConstructor(String.class, int.class);
Object obj2 = ctor.newInstance("Tom", 18);
🧪 实战:登录小练习(修正版)
参考 Java03_Reflect_Test.java,原代码通过反射设值并调用 login():
Class<?> empClass = Emp.class;
Constructor<?> ctor = empClass.getDeclaredConstructor();
Object emp = ctor.newInstance();Field account = empClass.getField("account"); // 这里字段是 public,getField 可用
Field password = empClass.getField("password");
account.set(emp, "admin");
password.set(emp, "admin");Method login = empClass.getMethod("login");
Object result = login.invoke(emp);
System.out.println(result); // true / false
如果字段是私有:
Field account = empClass.getDeclaredField("account");
account.setAccessible(true);
account.set(emp, "admin");
🧰 类加载器(ClassLoader)与“谁来加载类”
参考 Java02_Reflect_ClassLoader.java:
Class<Student> studentClass = Student.class;
ClassLoader cl = studentClass.getClassLoader();
System.out.println(cl); // AppClassLoader(应用类加载器)
System.out.println(cl.getParent()); // PlatformClassLoader(平台类加载器)
System.out.println(cl.getParent().getParent()); // 可能为 null(引导类加载器用 C/C++ 实现,不是 Java 对象)Class<String> stringClass = String.class;
System.out.println(stringClass.getClassLoader()); // 一般是 null,表示由引导类加载器加载
类加载器常见“三层”模型:
- BootstrapClassLoader(引导/启动类加载器):加载核心类库(
java.*),在 Java 代码中表现为null。 - PlatformClassLoader(平台类加载器):加载 Java 平台模块。
- AppClassLoader(应用类加载器):加载应用 classpath 下的类(我们写的类)。
口诀:核心类库(null)→ 平台 → 应用。输出的第三级通常是 null,不是异常。
🚨 常见异常与避坑(基于 Java04_Reflect_Exception.java)
原文件中有一段:
Class<?> empClass = Class.forName("chapter10.Emp");
但同文件只定义了 class Emp4,这会导致 ClassNotFoundException。应改为:
Class<?> empClass = Class.forName("chapter10.Emp4");
同时,注释中的异常解释更正:
ClassNotFoundException:类找不到(Class.forName 指向的类名不对 / 未在类路径)。NoSuchMethodException:找不到指定方法。IllegalArgumentException:传入方法/构造的参数不合法。NoSuchFieldException:找不到指定字段(原注释“没有顺序的异常”应为“没有字段”)。IllegalAccessException:访问受限(未setAccessible(true)等)。InvocationTargetException:反射调用方法时,方法内部抛出了异常,会被包裹到此异常中。
完整可运行修正版片段:
Class<?> empClass = Class.forName("chapter10.Emp4");
Constructor<?> ctor = empClass.getDeclaredConstructor();
Object emp = ctor.newInstance();Field account = empClass.getField("account");
Field password = empClass.getField("password");
account.set(emp, "admin");
password.set(emp, "admin");Method login = empClass.getMethod("login");
Object result = login.invoke(emp);
System.out.println(result);
🧭 最佳实践速记
- 尽量优先常规调用,反射用于“确有动态需求”时。
- 使用
getDeclaredXxx可获取私有成员,需setAccessible(true),并确保受信任环境。 - 通过
getMethods()/getFields()只能拿到 public(含父类),要拿本类私有成员用getDeclaredXxx。 Class.forName需要全限定名,且可能触发初始化(注意副作用与性能)。- 反射有开销,涉及热点路径时可做缓存(例如缓存
Method/Constructor对象)。
🧪 进阶加餐:动态调用有参方法与私有方法
class Person {private String name = "Tom";private int age = 18;private String secret(String prefix) { return prefix + name + ":" + age; }
}public class InvokeDemo {public static void main(String[] args) throws Exception {Class<?> c = Person.class;Object p = c.getDeclaredConstructor().newInstance();// 访问/修改私有字段Field f = c.getDeclaredField("name");f.setAccessible(true);System.out.println("old: " + f.get(p));f.set(p, "Jerry");// 调用私有有参方法Method m = c.getDeclaredMethod("secret", String.class);m.setAccessible(true);Object ret = m.invoke(p, ">> ");System.out.println(ret); // >> Jerry:18}
}
🎯 面试常见问题与答案
1)getField 与 getDeclaredField 区别?
getField仅返回 public 字段(包括父类);getDeclaredField返回“当前类声明的”字段(可含 private,不含父类)。方法与构造器也同理。
2)Class.forName 与 SomeClass.class 的区别?
Class.forName按名称加载类,通常会触发类初始化;.class是编译期可得的类字面量,不会主动触发初始化(取决于使用方式)。
3)为什么 String.class.getClassLoader() 往往是 null?
- 因为
String属于核心类库,由引导类加载器加载;引导类加载器不是 Java 对象,在 Java 中表现为null。
4)如何通过反射调用私有方法?安全性如何?
- 通过
getDeclaredMethod+setAccessible(true),仅应在受信任环境使用(可能破坏封装,存在安全风险)。
5)反射有什么性能问题?如何缓解?
- 反射绕过了JIT优化的一些路径,方法查找/检查也有成本。可通过缓存
Method/Field/Constructor、减少反射调用频率来优化。
6)InvocationTargetException 何时出现?
- 当被反射调用的方法内部抛出异常时,JVM 会把原始异常包在
InvocationTargetException中抛出;需通过getCause()获取根因。
7)能用反射实例化没有无参构造的类吗?
- 可以:获取有参构造
getDeclaredConstructor(参数类型...),再newInstance(参数...)。必要时setAccessible(true)。
✅ 小结
- 反射是“运行时”查看和操作类结构的能力,是 Java 框架的基石。
- 熟练掌握 Class 的获取、字段/方法/构造的反射用法,了解类加载器分层与常见异常。
- 反射应“按需使用”,注意性能与安全。
如果本文对你有帮助,欢迎点赞、收藏、评论交流!有任何疑问或想看的反射高级案例(例如注解解析、包扫描、类路径发现、动态代理),留言告诉我~
