我正试图找到 this question 的第三种解决方案.
我不明白为什么不打印 false
。
public class MyClass {
public MyClass() {
try {
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set("true", f.get("false"));
} catch (Exception e) {
}
}
public static void main(String[] args) {
MyClass m = new MyClass();
System.out.println(m.equals(m));
}
}
当然,由于字符串驻留,被修改的 "true"
实例与 PrintStream
的 print
方法中使用的实例完全相同?
public void print(boolean b) {
write(b ? "true" : "false");
}
我错过了什么?
编辑
@yshavit 的一个有趣观点是,如果你添加这条线
System.out.println(true);
在try
之前,输出是
true
false
最佳答案
这可以说是 HotSpot JVM 错误。
问题出在字符串文字驻留机制。
-
java.lang.String
字符串文字的实例是在常量池解析期间延迟创建的。 - 最初,字符串文字在常量池中表示为
CONSTANT_String_info
。指向CONSTANT_Utf8_info
的结构. - 每个类都有自己的常量池。即
MyClass
和PrintStream
有自己的一双CONSTANT_String_info
/CONSTANT_Utf8_info
文字 'true' 的 cpool 条目。 - 何时
CONSTANT_String_info
第一次访问,JVM发起解析过程。字符串实习是这个过程的一部分。 - 为了找到与驻留文字的匹配项,JVM 比较
CONSTANT_Utf8_info
的内容包含StringTable
中字符串实例的内容. - ^^^ 这就是问题所在。来自 cpool 的原始 UTF 数据与 Java
char[]
进行比较用户可以通过反射欺骗的数组内容。
那么,您的测试中发生了什么?
-
f.set("true", f.get("false"))
在MyClass
中启动文字 'true' 的解析. - JVM 在
StringTable
中没有发现任何实例匹配序列 'true',并创建一个新的java.lang.String
, 它存储在StringTable
中. -
value
来自StringTable
的那个字符串通过反射替换。 -
System.out.println(true)
在PrintStream
中启动文字 'true' 的解析类。 - JVM 将 UTF 序列 'true' 与来自
StringTable
的字符串进行比较,但找不到匹配项,因为该字符串已经具有 'false' 值。 'true' 的另一个字符串被创建并放置在StringTable
中.
为什么我认为这是一个错误?
JLS §3.10.5和 JVMS §5.1要求包含相同字符序列的字符串文字必须指向 java.lang.String
的相同实例.
但是,在以下代码中,解析具有相同字符序列的两个字符串文字会导致不同实例。
public class Test {
static class Inner {
static String trueLiteral = "true";
}
public static void main(String[] args) throws Exception {
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set("true", f.get("false"));
if ("true" == Inner.trueLiteral) {
System.out.println("OK");
} else {
System.out.println("BUG!");
}
}
}
JVM 的一个可能修复方法是在 StringTable
中存储指向原始 UTF 序列的指针。连同 java.lang.String
对象,以便实习进程不会将 cpool 数据(用户无法访问)与 value
进行比较数组(可通过反射访问)。
关于java - 字符串文字、实习和反射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36366692/