JDK8下为何反射无法修改String字段?如何修改private final static String
测试环境
- JDK版本:1.8.0_291
目标测试类
package reflectionStackoverflow.test; public class ClassA { private final static StringBuilder nameStringBuilder = new StringBuilder("1"); private final static String nameString = "1"; public static void printNameStringBuild() { System.out.println("nameStringBuilder: " + nameStringBuilder); } public static void printNameString() { System.out.println("nameString: " + nameString); } }
反射测试代码
package reflectionStackoverflow.test; import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class ReflectionTest { @Test public void test() throws NoSuchFieldException, IllegalAccessException, InstantiationException { Class c = ClassA.class; Field nameStringBuilder = c.getDeclaredField("nameStringBuilder"); nameStringBuilder.setAccessible(true); Field nameStringBuilderModifiers = nameStringBuilder.getClass().getDeclaredField("modifiers"); nameStringBuilderModifiers.setAccessible(true); nameStringBuilderModifiers.setInt(nameStringBuilder, nameStringBuilder.getModifiers() & ~Modifier.FINAL); nameStringBuilder.set(c, new StringBuilder("2")); nameStringBuilderModifiers.setInt(nameStringBuilder, nameStringBuilder.getModifiers() & ~Modifier.FINAL); Field nameString = c.getDeclaredField("nameString"); nameString.setAccessible(true); Field nameStringModifiers = nameString.getClass().getDeclaredField("modifiers"); nameStringModifiers.setAccessible(true); nameStringModifiers.setInt(nameString, nameString.getModifiers() & ~Modifier.FINAL); nameString.set(c, "2"); nameStringModifiers.setInt(nameString, nameString.getModifiers() & ~Modifier.FINAL); ClassA.printNameStringBuild(); ClassA.printNameString(); } }
测试结果
nameStringBuilder: 2 nameString: 1
初步测试结论
- 通过反射可修改
StringBuilder类型的private final static字段 - 相同反射逻辑无法修改
String类型的private final static字段
问题解答
1. 为什么无法通过反射修改String类型字段?
核心原因是Java编译器的编译期常量内联优化。
按照Java语言规范,同时满足final、static修饰、赋值为编译期可确定值的字段属于编译期常量。你测试代码里的private final static String nameString = "1"正好符合这个特征:字符串字面量"1"在编译阶段就能完全确定值,编译器会对这类常量做常量折叠处理——所有代码中引用这个字段的位置,编译时都会被直接替换成字面量"1",运行时根本不会实际去访问ClassA类中存储的nameString静态字段。
你通过反射修改的是堆中Class对象上nameString字段的存储值,但printNameString()方法的字节码里已经被编译器写死了直接打印"1"的逻辑,自然读不到你反射修改后的值。
而StringBuilder类型字段的初始化是new StringBuilder("1"),属于运行时才会创建的对象,不属于编译期常量,编译器不会做内联替换,所有调用点运行时都会实际读取静态字段存储的对象引用,反射替换引用后就能正常读到新值。
2. 是否存在可行方法,实现对private final static String类型值的修改?
存在可实现的方案,分两类场景处理:
- 如果可以修改目标类源码,最简单的方案是破坏字段的编译期常量属性:把字段的初始化逻辑从直接赋值字面量,改成运行时创建字符串对象,比如写成
private final static String nameString = new String("1");。这种写法下编译器不会对该字段做内联优化,你用和修改StringBuilder完全一致的反射逻辑,去掉字段的final修饰符后重新赋值,就能正常读到修改后的结果。 - 如果无法修改目标类源码,针对已经被内联的常量,可以用两种非正规方案实现修改:
- 借助字节码增强技术(ASM、Javassist、Java Instrumentation代理),在类加载阶段修改目标类的字节码,直接替换常量池中存储的"1"字面量为目标值,这样所有内联了这个常量的代码位置都会读到新值。
- 通过
sun.misc.Unsafe类直接定位静态字段的内存偏移地址,强制修改对应内存位置存储的引用值。这种方式依赖JDK内部非公开API,JDK9及以上版本受模块系统限制默认无法访问,且不同JVM厂商、不同版本的内存布局可能存在差异,稳定性极差,绝对不建议在生产环境使用。
注意:从JDK12开始,反射修改Field对象的
modifiers字段已经被JDK默认禁止,会直接抛出异常。所有绕过Java语言规范的反射修改操作本身就不具备跨版本兼容性,正式业务场景不要使用这类黑科技逻辑。
内容的提问来源于stack exchange,提问作者wo4wangle
相关产品推荐
相关产品推荐

