如何实现 System.out.println("a") 显示 b

今天看到一篇文章不用反射,能否交换两个字符串的值. 心想字符串常量在常量池里面,是在就算用了反射也交换不了吧。转念一想,不对,字符串常量虽然本身在常量池里面,但是它依然是个对象,那么 private final 类型的属性仅仅表示它是一个指向常量池的引用,而并非不可修改。完全可以让它指向另一个常量。

分析String的结构

通过反射可以很轻松地获取所有属性

// 获取所有属性 for (Field field : String.class.getDeclaredFields()) { 	System.out.println(field); } 

如何实现 System.out.println("a") 显示 b

方框框起来的 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} 

而实际结果为:

如何实现 System.out.println("a") 显示 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"); 

结果为:

如何实现 System.out.println("a") 显示 b

可见 private final byte[] value 是可以修改的,不仅可以指向常量池,也可以指向堆。

发表评论

评论已关闭。

相关文章