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


    今天看到一篇文章不用反射,能否交换两个字符串的值. 心想字符串常量在常量池里面,是在就算用了反射也交换不了吧。转念一想,不对,字符串常量虽然本身在常量池里面,但是它依然是个对象,那么 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 是可以修改的,不仅可以指向常量池,也可以指向堆。

  • 相关阅读:
    JavaEE开发之Spring框架整合1
    深度学习——第1章 深度学习的概念及神经网络的工作原理
    线性表操作的实现--顺序表
    在Windows10中打开控制面板的5种方法以及如何设置环境变量
    C# 嵌入式数据库LiteDB
    VUE学习第三篇----VUE实例
    【使用VS开发的第一个QT项目——实现相机功能(包括QT下载、配置、摄像头程序)】
    uniapp 跳转页面 和 记录一些其他的坑
    【java进阶02:抽象类和接口】类与类之间的关系 抽象类与接口的区别
    centos7在线安装rabbitmq及其远程连接
  • 原文地址:https://www.cnblogs.com/CounterX/p/16580484.html