3

如何实现 System.out.println("a") 显示 b - 计数寄存器

 1 year ago
source link: https://www.cnblogs.com/CounterX/p/16580484.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

分析String的结构

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

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

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

而实际结果为:

image

这说明我们成功地修改了常量池中字符串"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");
image

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK