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

分析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 是可以修改的,不仅可以指向常量池,也可以指向堆。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值