分析String的结构
通过反射可以很轻松地获取所有属性
// 获取所有属性 for (Field field : String.class.getDeclaredFields()) { System.out.println(field); }
方框框起来的 private final byte[] java.lang.String.value
即为需要的对象。
设置可见性
接下来就是常见的反射修改可见性。
Field field = String.class.getDeclaredField("value"); field.setAccessible(true);
然而这一步会报错: java.base does not “opens java.lang“ to unnamed module
,即非法访问警告。
这是因为 JDK 9 开始,除非模块标识为opens去允许反射访问,否则模块不能使用反射去访问非公有的成员/成员方法以及构造方法。解决方案为,设置VM启动参数 --add-opens=java.base/java.lang.invoke=ALL-UNNAMED
参照 非法访问异常 以及 IDEA设置VMoptions
编写显示函数
希望显示比较充分的信息,但这样反复调格式就太麻烦了,所以封装到函数里。由于是采用的 main 入口函数,所以需要写成静态方法。
private static void show(String s, String name, Field field) { StringBuilder sb = new StringBuilder(); try { sb.append("String ").append(name).append("@").append(s.hashCode()).append("{") .append("value@").append(Integer.toHexString(field.get(s).hashCode())).append(" = ").append(s) .append("}"); System.out.println(sb.toString()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }
编写主函数
public static void main(String[] args) { String a = "a"; String b = "b"; String c = "a"; // 获取所有属性 for (Field field : String.class.getDeclaredFields()) { System.out.println(field); } try { Field field = String.class.getDeclaredField("value"); field.setAccessible(true); show(a, "a", field); show(b, "b", field); show(c, "c", field); field.set(a, field.get(b)); show(a, "a", field); show(b, "b", field); show(c, "c", field); } catch (Exception e) { throw new RuntimeException(e); } }
执行效果
String a@97{value@568db2f2 = a} String b@98{value@378bf509 = b} String c@97{value@568db2f2 = a} String b@97{value@378bf509 = b} String b@98{value@378bf509 = b} String c@97{value@378bf509 = b}
其中前三行是执行前,后三行是执行后。
值得注意的是,第四行原本是希望显示为:
String a@97{value@378bf509 = b}
而实际结果为:
这说明我们成功地修改了常量池中字符串 "a"
的值,使其值为 private final byte[] value = {'b'}
这也就有了题目,在main函数的最后补充以下代码:
System.out.println("\"a\"现在的值为:"); System.out.println("a"); field.set(a, new byte[] {65, 66, 67}); System.out.println("\"a\"现在的值为:"); System.out.println("a");
结果为:
可见 private final byte[] value
是可以修改的,不仅可以指向常量池,也可以指向堆。