我正在用函数改变字符串值(我知道这是非常不安全和危险的):
public static void reverse(String s) {
try {
Field val = String.class.getDeclaredField("value");
val.setAccessible(true);
char[] value = (char[]) val.get(s);
char[] inverse = s.toCharArray();
for (int i = 0; i < s.length(); i++)
value[i] = inverse[s.length()-i-1];
}
catch (Exception e) {
e.printStackTrace();
}
}
之后我发现,根据字符串的创建,它的行为极其不可预测。我用它创建了一个小型智力游戏(需要很多打印才能达到想要的效果):
public static void main(String[] args) {
final String a = "abc";
final String b = new String("abc");
final String c = "abcd".substring(0, 3);
System.out.println("Let's start!");
System.out.print("a - ");
System.out.println(a);
System.out.print("b - ");
System.out.println(b);
System.out.print("c - ");
System.out.println(c);
System.out.print("Are they all equals? - ");
System.out.println(a.equals(b) && a.equals(c) && b.equals(c));
System.out.print("But they are different objects, right? - ");
System.out.println(!(a == b || b == c || a == c));
System.out.println("Let's reverse only 'a'. But all are final and String is not mutable, so what can go wrong?");
reverse(a);
System.out.println("Done. What we've got here?");
// trick 1
System.out.print("a = ");
System.out.print(a);
System.out.println(" - ok, 'a' is reversed. A bit strange, but it works. Super method");
System.out.print("b = ");
System.out.print(b);
System.out.println(" - wait... We haven't touched this");
System.out.print("c = ");
System.out.print(c);
System.out.println(" - this is untouched, wierd, huh? We've just reversed 'a' so 'b' and 'c' should act the same.");
// trick 2
System.out.println("\nOk, so 'c' should equals \"abc\", right?\n");
System.out.println("\"abc\".equals(c)? = "+"abc".equals(c));
System.out.println("...\n");
System.out.print("Do you remeber, that");
System.out.print(" a = ");
System.out.print(a);
System.out.print(" oraz b = ");
System.out.print(b);
System.out.println(" ?\n");
// trick 3
System.out.println("So let's check that");
System.out.print("a.equals(b) = ");
System.out.println(a.equals(b)+"\n");
System.out.println("Ok, we had expected that.\n");
System.out.println("But what do you think the result of (\" \"+a).equals(\" \"+b) will be?\n");
System.out.print("(\" \"+a).equals(\" \"+b) = ");
System.out.println((" "+a).equals(" "+b)+"\n");
System.out.print("And do you remeber, that");
System.out.print(" a = ");
System.out.print(a);
System.out.print(" ,a c = ");
System.out.print(c);
System.out.println(" ?\n");
// trick 4
System.out.println("So let's check if they are different:");
System.out.print("a.equals(c) = ");
System.out.println(a.equals(c));
System.out.println("So they are different... but are they really different?\n");
System.out.print("(\" \"+a).equals(\" \"+c) = ");
System.out.println((" "+a).equals(" "+c));
System.out.println("Booo!!! You could choose the blue pill!\n");
System.out.println("Our actors were: ");
System.out.print("a = ");
System.out.print(a);
System.out.print(", b = ");
System.out.print(b);
System.out.print(", c = ");
System.out.print(c);
System.out.print(" oraz abc = ");
System.out.println("abc");
System.out.print("\n");
// trick 5
System.out.println("Or in other words");
System.out.println("a = "+a+", b = "+b+", c = "+c+" oraz abc ="+(" "+"abc")+"\n");
System.out.println("But do you remember what we were revering? Was is rally b?");
System.out.println("Have a nice day. Z-DNA");
}
但我不明白那场比赛。所有字符串都是不同的对象,但具有相同的值。
那么为什么在技巧 1 中,字符串“c”的行为与“b”不同?
好的,我明白了技巧 2。“abc”不再是“abc”而是“cba”(但为什么?我更改了字符串“a”的值,而不是字符串池的值),所以它不能是等于“abc”,但是当我什至无法让“abc”调用“abc”时,“c”怎么可能是“abc”?
为什么在技巧 3 中添加空格后“a”和“b”不再相等,而为什么在 4 个带空格的“a”和“c”中相等?!?!
技巧 5 告诉我们,“a”、“b”、“c”和“abc”的值正在变化,具体取决于我们如何调用它。 (哦等等。“c”是特殊的。创建字符串的最不合理的方法实际上最不受黑魔法的影响)。
请帮助我理解我实际上做了什么以及函数反转是什么样的黑暗。
最佳答案
您已经知道字符串被保存在字符串池中这一事实。这里还有一些事实供您引用。
这是
new String("abc")
构造函数的源代码。String(String original) { this.value = original.value; this.hash = original.hash; }
所以这意味着您的
a
和b
后面有相同的字符数组。当您更改a
的支持值时,您也会更改b
。当您更改驻留字符串的值时,驻留字符串当然也会更改。
这实际上意味着代码中每次出现的
“abc”
不再是真正的“abc”,而是“cba”。代码说它是“abc”,但内存的说法不同。但是,编译器会提前计算常量并分别对它们进行实习。这意味着,如果您有一个常量,例如
""+ "abc"
,它会被编译为"abc"
- 暂留池中的不同字符串。使用
+
的长字符串连接使用StringBuilder
进行转换,以避免创建多个将被丢弃的中间对象。+
的每个操作数都成为对append
的调用。
因此,c
的行为与 b
不同,因为 a
与 b
共享存储,但 b
不与 c
共享存储 - 因为 c
是从不同的常量派生的(现在子字符串无论如何都会创建一个新的支持数组)。
现在,对于 c
等于 "abc"
的情况,技巧 2 返回 false
,因为正如我们所说,常量本身不是原来的样子- 你改变了它。
技巧 3 - 为什么当 a
等于 b
时,在它们之前添加空格会使它们不相等?嗯,因为 ""+ a
是一个常量,并且提前被实习为 "abc"
,而 ""+ b
不是 a常量在编译时已知,因此它是在运行时计算的。很容易检查是否添加
System.out.println( " " + a == " abc" );
这会打印 true
- 它们是相同的字符串,并且只有在基于编译器对以下内容的不变性的信念而提前插入 ""+ a
时才会发生这种情况字符串和 final 的最终结果。
因此,""+ a
现在肯定是"abc"
。所以难怪它等于""+ c
。虽然 c
不是预驻留常量,但它仍然是 "abc"
并且串联仍然产生相同的结果。
最后,您用不同的打印方式打印的表达式仍然单独使用 "abc"
,因此它将其打印为 "cba"
这是它的新值。但是当你用一个大字体打印它时,其中一些是编译器时常量表达式 - 具体来说,括号中的部分:
System.out.println("a = "+a+", b = "+b+", c = "+c+" oraz abc ="+(" "+"abc")+"\n");
在编译时被保留为“abc”
- 并且您已经知道这是一个单独的常量。
Java 将字符串串联转换为带有多个附加的 StringBuilder
。该表达式相当于:
StringBuilder sb = new StringBuilder();
sb.append( "a = abc b=" )
.append( b )
.append( ", c = " )
.append( c )
.append( " oraz abc =" )
.append( " abc" )
.append( "\n" );
System.out.println( sb.toString() );
现在,有两组已预先连接的常量,其中一组是放在括号中的 ""+ "abc"
。
如果删除括号,空格和 "abc"
将分别附加,然后 "abc"
显示为 "cba"
.
如果你使用你可以看到这个
javap -p -v <class file>
关于java - "Mutable"Java 字符串行为不可预测,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36071290/