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

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

如何实现 System.out.println("a") 显示 b的更多相关文章

  1. IDEA设置syso快捷键输出System.out.println();

    用Eclipse时间长了, 就习惯之前的快捷键! 当然, IDEA不愧是Java开发的”利器”! 写起代码就是一个字 – “爽”! 建议大家可以去尝试一下! 当然, 在IDEA中输出System.ou ...

  2. System.out.println与System.err.println的区别(输出顺序!!!)

    System.out.println与System.err.println的区别(输出顺序!!!) 分类:java (208)  (0) System.out.println与System.err.p ...

  3. java 标准输出与标准错误 out与 err 区别 用法 联系 java中的out与err区别 System.out和System.err的区别 System.out.println和System.err.println的区别 Java重定向System.out和System.err

    本文关键词: java 标准输出与标准错误    out与 err 区别 用法 联系  java中的out与err区别  System.out和System.err的区别 System.out.pri ...

  4. System.out.println()和System.err.println()

    在一次笔试中遇到了一个System.err.println()的输出,之前没有见过,回来查一查,自己还是见识太短,来补充一下. 首先看一看jdk中 来一个简单的实验 第一次显示 第二次显示 1. 发现 ...

  5. 【转】Java基础:System.out.println与System.err.println的区别

    同时使用了System.out.println与System.err.println()打印输入内容,结果看到的内容和预想的不一样,顺序与预料的不同并不是因为err和out的区别导致,而是因为他们是两 ...

  6. 关于随机数、方法重载和System.out.println()的认识

    (1)使用纯随机数发生器编写一个指定数目内数字的程序(类真随机数) 源代码: package Demo1; public class trueRandom { long Multiplier = 45 ...

  7. System.err.println()

    err是运行期异常和错误反馈的输出流的方向 System.err.println只能在屏幕上实现打印,即使你重定向了也一样 用err打印出的 字符串,再eclipse的console会显示成红色 标准 ...

  8. Hadoop2.9下运行JAR包时System.out.println的输出日志

    根据博文——Hadoop日志存放路径详解中所述,Container日志包含ApplicationMaster日志和普通Task日志(关于其他类型的日志的详细说明请参考该博文,本文不再赘述) 所以可知, ...

  9. System.out.println 的多线程并发问题

    假设println函数的參数为常量则不会出现线程并发问题,可是假设參数为表达式形式.则JVM在运行println函数的时候会分为几步来运行,从而造成并发问题. 例如以下样例所看到的: package ...

随机推荐

  1. curl-URL请求

    模拟HTTP请求,通过访问URL获取HTTP响应. 语法 curl [选项] 访问URL 选项 --connect-timeout SECONDS 设置最大请求时间. -C, --continue-a ...

  2. [漏洞复现] [Vulhub靶机] OpenSSL Heartbleed Vulnerability (CVE-2014-0160)

    免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责. 0x00 背景知识 传输层安全协议SSL 安全套接字协议SSL(Secure Sockets Layer),及其继任者传输层安 ...

  3. Elasticsearch(es)介绍与安装

    ### RabbitMQ从入门到集群架构: https://zhuanlan.zhihu.com/p/375157411 可靠性高 ### Kafka从入门到精通: https://zhuanlan. ...

  4. Web 前端实战(三):雷达图

    前言 在<Canvas 线性图形(五):多边形>实现了绘制多边形的函数.本篇文章将记录如何绘制雷达图.最终实现的效果是这样的: 绘制雷达图 雷达图里外层 如动图中所示,雷达图从里到外一共有 ...

  5. React简单教程-4-事件和hook

    前言 在上一章 React 简单教程-3-样式 中我们建立了一个子组件,并稍微美化了一下.在另一篇文章 React 简单教程-3.1-样式之使用 tailwindcss 章我们使用了 tailwind ...

  6. 1. Docker的中央仓库安装设置及镜像的操作

    具体也可参考:https://developer.aliyun.com/mirror/docker-ce?spm=a2c6h.13651102.0.0.3e221b11G7cfhr https://d ...

  7. GDKOI 2021 Day1 TG 。。。

    看着一群群比 LHF , HQX 还强的大佬涌进了机房,本蒟蒻表示慌得一批 T1 讲题人说最简单的签到题本蒟蒻表示... \(Update\) 用 ds , dt 两个变量记录点 i 连向 s 或 t ...

  8. 27.MySQL 索引、事务与存储引擎

    MySQL 索引.事务与存储引擎 目录 MySQL 索引.事务与存储引擎 MySQL 索引 索引的概念 索引的作用及副作用 索引的作用 索引的副作用 创建索引的原则依据 索引的分类和创建 普通索引 唯 ...

  9. RPA应用场景-对公账户开户资质审查

    场景概述 对公账户开户资质审查 所涉系统名称 人民银行账户管理系统 人工操作(时间/次) 0.5小时 所涉人工数量 132 操作频率 不定时 场景流程 1.机器人自动登录人民银行账户管理系统 2.查询 ...

  10. jenkins配置自动执行sql脚本

    shell脚本: bigsql="select big_version,small_version from d0mstore.db_current_version order by big ...