【问题标题】:"Mutable" Java String is acting unpredictable“可变”Java 字符串的行为不可预测
【发布时间】:2016-03-17 20:41:37
【问题描述】:

我正在使用函数更改字符串值(我知道它非常不安全和危险):

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") 创建一个new 实例,但它与"abc" 共享相同的支持数组。
  • 当然是不可预知的。这就是为什么。
  • @LouisWasserman - 好的,但它只有当你无法理解发生了什么时才能预测。它是可重复的,所以它可以被解释。
  • 就目前而言,我认为这个问题不适合 StackOverflow。你撕掉了一个字符串的核心并提出了六个问题。 RealSkeptic 帮您写了六个答案。一些有趣的东西,但格式和呈现方式使其不太可能在未来对任何人有用。

标签: java string


【解决方案1】:

您已经知道字符串被保留在字符串池中的事实。因此,这里还有一些事实供您参考。

  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 的调用。

所以,c 的行为不同于 b,因为 ab 共享存储,但 b 不与 c 共享存储 - 因为 c 派生自不同的常量(现在一个子字符串无论如何都会创建一个新的支持数组)。

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

技巧 3 - 为什么当 a 等于 b 时,在它们之前添加一个空格会使它们不相等?好吧,因为" " + a 是一个常量,并且会提前作为" abc" 进行实习,而" " + b 在编译时不是已知的常量,因此它是在运行时计算的。易于检查是否添加

System.out.println( " " + a == " abc" );

这将打印true - 它们是相同的字符串,并且只有在基于编译器对字符串的不变性和finality 的确定性的信念而预先插入" " + a 时才会发生这种情况。

所以," " + 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 大师。一个,最后一个问题 - 为什么在最后一行 oraz abc ="+(" "+"abc") 当我们删除括号时它再次打印 abc = cba?我需要将其放入() 以将其打印为abc。感谢您的帮助。
  • @Z-DNA 我稍微改变了答案的结尾,因为它并不完全准确 - 只有串联的开头被固定在一起。其余部分使用StringBuilder 附加。
猜你喜欢
  • 1970-01-01
  • 2012-07-10
  • 1970-01-01
  • 2014-09-28
  • 2013-05-08
  • 1970-01-01
  • 2022-06-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多