如何实现 System.out.println("a") 显示 b
今天看到一篇文章不用反射,能否交换两个字符串的值. 心想字符串常量在常量池里面,是在就算用了反射也交换不了吧。转念一想,不对,字符串常量虽然本身在常量池里面,但是它依然是个对象,那么 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的更多相关文章
- IDEA设置syso快捷键输出System.out.println();
用Eclipse时间长了, 就习惯之前的快捷键! 当然, IDEA不愧是Java开发的”利器”! 写起代码就是一个字 – “爽”! 建议大家可以去尝试一下! 当然, 在IDEA中输出System.ou ...
- System.out.println与System.err.println的区别(输出顺序!!!)
System.out.println与System.err.println的区别(输出顺序!!!) 分类:java (208) (0) System.out.println与System.err.p ...
- 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 ...
- System.out.println()和System.err.println()
在一次笔试中遇到了一个System.err.println()的输出,之前没有见过,回来查一查,自己还是见识太短,来补充一下. 首先看一看jdk中 来一个简单的实验 第一次显示 第二次显示 1. 发现 ...
- 【转】Java基础:System.out.println与System.err.println的区别
同时使用了System.out.println与System.err.println()打印输入内容,结果看到的内容和预想的不一样,顺序与预料的不同并不是因为err和out的区别导致,而是因为他们是两 ...
- 关于随机数、方法重载和System.out.println()的认识
(1)使用纯随机数发生器编写一个指定数目内数字的程序(类真随机数) 源代码: package Demo1; public class trueRandom { long Multiplier = 45 ...
- System.err.println()
err是运行期异常和错误反馈的输出流的方向 System.err.println只能在屏幕上实现打印,即使你重定向了也一样 用err打印出的 字符串,再eclipse的console会显示成红色 标准 ...
- Hadoop2.9下运行JAR包时System.out.println的输出日志
根据博文——Hadoop日志存放路径详解中所述,Container日志包含ApplicationMaster日志和普通Task日志(关于其他类型的日志的详细说明请参考该博文,本文不再赘述) 所以可知, ...
- System.out.println 的多线程并发问题
假设println函数的參数为常量则不会出现线程并发问题,可是假设參数为表达式形式.则JVM在运行println函数的时候会分为几步来运行,从而造成并发问题. 例如以下样例所看到的: package ...
随机推荐
- 树莓派开发笔记(十五):树莓派4B+从源码编译安装mysql数据库
前言 树莓派使用数据库时,优先选择sqlite数据库,但是sqlite是文件数据库同时仅针对于单用户的情况,考虑到多用户的情况,在树莓派上部署安装mysql服务,通过读写锁事务等使用,可以实现多进 ...
- 接口开发-restful
数据库表设计 1 --员工表 2 create table Employee 3 ( 4 id NUMBER primary key, 5 employeeID NUMBER not null, 6 ...
- Leetcode----<Diving Board LCCI>
题解如下: public class DivingBoardLCCI { /** * 暴力解法,遍历每一种可能性 时间复杂度:O(2*N) * @param shorter * @param long ...
- jenkins结合ansible发布
1. yum安装的jenkins修改配置文件调用ansible vim /etc/sysconfig/jenkins 2. 安装Jenkins Ansible插件 Manage Jenkins---& ...
- lvm逻辑卷创建及使用
创建逻辑卷 pvcreate /dev/md0 pvs 查看创建的pv组 pvdisplay /dev/md0 查看磁盘详细信息 添加vg组: 创建vg组: vgcreate vg1 /dev/md0 ...
- python小题目练习(13)
题目:封装用户的上网行为 实现代码: """Author:mllContent:封装用户的上网行为Date:2020-01-19"""def ...
- Linux for CentOS 下的 nginx 绿色安装-超省心安装
1.我这里是nginx-1.13.0-1.x86_64 .rpm(点击下载)版本的. 2.安装nginx的相应环境.有些环境可能不必须,但是安装了,确保以防万一,多多益善 yum install gd ...
- Timer和ScheduledThreadPoolExecutor的区别
Timer 基于单线程.系统时间实现的延时.定期任务执行类.具体可以看下面红色标注的代码. public class Timer { /** * The timer task queue. This ...
- Codeforces Round #804 (Div. 2)
题目链接 A The Third Three Number Problem 题意 给你一个n,让你求满足的a,b,c. 如果不存在则输出-1. 思路 显然任意a,b,c是不可能得到奇数. 只考虑偶数 ...
- ConcurrentHashMap树化链表treeifyBin
private final void treeifyBin(Node<K,V>[] tab, int index) { Node<K,V> b; int n, sc; if ( ...