“Mutable”Java String行为不可预测

时间:2016-03-17 20:41:37

标签: java string

我正在尝试使用函数改变String值(我知道它非常不安全和危险):

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”?

为什么在添加空格'a'和'b'之后的技巧3不再相等,为什么4'a'和'c'与空格的地球相同?!?! < / p>

特技5告诉我们'a','b','c'和“abc”的值正在改变取决于我们如何调用它。 (哦等等。'c'很特别。创建字符串的最不合理的方法实际上最不受黑魔法的影响)。

请帮助我理解我实际做了什么以及什么样的黑暗功能逆转。

1 个答案:

答案 0 :(得分:2)

您已经知道字符串在字符串池中被中断的事实。所以这里还有一些事实。

  1. 这是new String("abc")

    的构造函数的来源
    String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    

    这意味着您的ab背后有相同的字符数组。更改a的支持值后,您需要更改b

  2. 当您更改实习字符串的值时,当然会更改实习字符串。

    这实际上意味着代码中"abc"的每次出现都不再是“abc”而是“cba”。代码说它是“abc”,但内存说的不同。

  3. 但是,编译器会提前计算常量并分别对它们进行实例化。这意味着,如果您有" " + "abc"之类的常量,则将其编译为" abc" - 实习池中的其他字符串。

  4. 使用+转换与StringBuilder的长字符串连接,以避免创建将被丢弃的多个中间对象。 +的每个操作数都会调用append

  5. 因此,c的行为与b的行为不同,因为a分享商店b,但b不与[{1}}共享商店 - 因为c是从不同的常量派生的(现在,子串创建了一个新的后备数组)。

    现在,技巧2返回c false等于c因为,正如我们所说,常数本身不是它 - 你改变了它。

    技巧3 - 当"abc"等于a时,为什么在它们之前添加空格使它们不相等?好吧,因为b是一个常量,并且事先得到" " + a,而" abc"不是编译时已知的常量,因此它会在运行时计算出来。如果你添加

    ,很容易检查
    " " + b

    这打印System.out.println( " " + a == " abc" ); - 它们是相同的字符串,只有在true根据编译器对字符串的不变性和终结的终结性的信念提前交互时才会发生这种情况。

    因此," " + a现在肯定是" " + a。所以难怪它等于" abc"。虽然" " + c不是预先实现的常量,但它仍然是c,并且连接仍会产生相同的结果。

    最后,您使用不同打印件打印的表达式仍然单独使用"abc",因此它将其打印为"abc",这是它的新值。但是当你用一个大字体打印它时,其中一些是编译时间常量表达式 - 具体来说,括号中的部分:

    "cba"

    在编译时以System.out.println("a = "+a+", b = "+b+", c = "+c+" oraz abc ="+(" "+"abc")+"\n"); 进行实习 - 你已经知道这是一个单独的常量。

    Java将字符串串联转换为带有多个附加项的" abc"。该表达式相当于:

    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"