set() 会失败是因为 jvm 默认禁止对 private 字段写入,必须先调用 setaccessible(true) 绕过访问检查;否则抛 illegalaccessexception,且 java 9+ 模块化下还需 --add-opens 等额外配置。

能改,但必须先绕过访问控制,否则 set() 会直接抛 IllegalAccessException。
为什么 set() 会失败:private 字段默认不可写
Java 反射对 private 成员默认是“只读不写”的。哪怕你用 getDeclaredField("xxx") 正确拿到了 Field 对象,调用 field.set(obj, value) 时仍会触发访问检查,抛出异常。
常见错误现象:
java.lang.IllegalAccessException: Class xxx can not access a member of class yyy with modifiers "private"- 字段明明存在、类型也匹配,
set()就是不生效,也不报错(比如用了setInt()但目标是Integer包装类)
根本原因:JVM 的模块化访问检查(尤其是 Java 9+ 的强封装)在运行时拦截了非法访问。
必须调用 setAccessible(true) 才能写入
这是绕过 JVM 访问检查的唯一标准方式。它不是“建议”,而是强制步骤 —— 缺了这句,所有对 private/protected 字段的写操作都会失败。
实操要点:
-
setAccessible(true)必须在set()或get()之前调用,顺序不能反 - 对同一个
Field对象多次调用setAccessible(true)是安全的,不会报错 - 如果字段属于 JDK 内部类(如
java.util.ArrayList.elementData),Java 17+ 默认会拒绝setAccessible(true),需加启动参数--illegal-access=permit或迁移到VarHandle
set() 的参数和类型匹配细节
Field.set(Object obj, Object value) 看似简单,但类型不匹配会导致 IllegalArgumentException。
关键规则:
- 基本类型字段(
int,boolean等)必须传对应包装类或使用专用方法(如setInt(obj, 42)),不能直接传String或null - 静态字段(
static)的第一个参数传null即可,如field.set(null, "new value") - 泛型擦除后,
Field不保留泛型信息,所以set()不校验泛型约束,但运行时类型仍需匹配(比如字段是List<string></string>,你塞进去一个ArrayList<integer></integer>,编译器不管,但后续用的时候可能ClassCastException)
示例(安全写法):
Field field = obj.getClass().getDeclaredField("count");
field.setAccessible(true);
field.setInt(obj, 100); // ✅ 直接设 int 值
// field.set(obj, 100); // ❌ 对 int 字段传 Integer,可能抛 IllegalArgumentException
修改父类私有字段时要注意继承链
子类无法直接通过 getClass().getDeclaredField("xxx") 拿到父类声明的 private 字段 —— getDeclaredField() 只查本类,不查父类。
正确做法:
- 显式指定父类的
Class对象:Parent.class.getDeclaredField("privateField") - 如果父类是 JDK 类或模块化受限类(如
java.util.HashMap),setAccessible(true)可能被拒绝,此时应优先考虑替代方案(如用put()替代直接改table数组) - 字段名拼写必须完全一致,包括大小写和下划线,且不能依赖 IDE 自动补全 —— 反射不认 getter/setter 名,只认字段名
容易被忽略的一点:反射修改的是运行时对象状态,不影响源码、不触发 setter 逻辑、不更新相关缓存或监听器。如果你改的是某个框架内部的状态(比如 Spring Bean 的 private 字段),后续行为可能变得不可预测 —— 这不是 bug,是反射的固有代价。










