1

I was playing a bit with changing String value (i know that it's extremely unsafe and dangerous) with function:

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();
    }
}

and after while I've discovered that depending on string creation it is acting extremely unpredictable. I've created a small mind-game with that (so many prints was necessary to get wanted effect):

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");
}

But I don't get that play. All string are different object but with the same value.

So why in trick 1 string 'c' acted differently that 'b'?

Ok, I get trick 2. The "abc" is not anymore "abc" but "cba" (but why? I have changed value of String 'a', not value of string pool) so it can't be equal "abc", but how 'c' can be "abc" when I can't even get "abc" calling "abc"??

Why in trick 3 after adding space 'a' and 'b' was not equal anymore and why on earth in 4 'a' and 'c' with spaces was equal?!?!

Trick 5 show us that the value of 'a', 'b', 'c' and "abc" is changing depend on how are we calling it. (oh wait. 'c' is special. The most irrational method of creating string is actually most immune to that black magic).

Please help me understanding what I've actually done and what sort of darkness is function reverse.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Z-DNA
  • 133
  • 1
  • 10
  • Take a look at this answer http://stackoverflow.com/questions/35899879/and-equals-not-working-in-android-studio-java/35899981#35899981 – ΦXocę 웃 Пepeúpa ツ Mar 17 '16 at 20:49
  • 1
    There are too many questions here, and I for one can't be bothered to answer them all. The answer to the first one is easy. `new String("abc")` creates a `new` instance, but it shares *the same* backing array as `"abc"`. – Paul Boddington Mar 17 '16 at 20:49
  • 2
    Of course it's unpredictable. This is _why._ – Louis Wasserman Mar 17 '16 at 21:04
  • @LouisWasserman - Ok, but it _is_ unpredictable only if you can't understand what is going on. It is repeatable so it **can** be explained. – Z-DNA Mar 17 '16 at 21:17
  • As currently presented, I don't think that this question is a good fit for StackOverflow. You've torn out the heart of a String and made half a dozen questions. RealSkeptic has done you the favour of writing half a dozen answers. Some interesting stuff, but the format and presentation makes it unlikely for this to be useful to anyone in the future. – DavidS Mar 17 '16 at 22:09
  • @DavidS - Sorry if you thinking this question is bad. I agree that that function is bad, but we had to write it for the java classes. Function which will change value of String without returning it. And as far as I know the person which demanded that doesn't know what it can really do. So i was trying to figure it our playing with to learn something more about it. I thought that all that "half a dozen questions" are strictly related and can be answered quite together. So please tell he how should i ask this question to be proper? I should ask "half a dozen" separate, nearly the same questions? – Z-DNA Mar 18 '16 at 05:32
  • My opinion is just an opinion. I say this question isn't generally useful and so I downvoted; others can disagree and upvote and things will be fair. I say it's not useful because your confusion stems from just one or two principles, but you have made a long contrived example which creates more questions than necessary. Had you focused on an MCVE you could have answered your questions yourself before getting carried away. – DavidS Mar 18 '16 at 06:07

1 Answers1

2

You already know about the fact that strings are interned in the string pool. So here are a few more facts for you.

  1. This is the source of the constructor for new String("abc").

    String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    

    So this means that your a and b have the same character array behind them. When you change the backing value of a, you change b.

  2. When you change the value of an interned string, of course, the interned string gets changed.

    This really means that every occurrence of "abc" in your code is not really "abc" anymore but "cba". The code says it's "abc", but memory says differently.

  3. However, the compiler calculates constants in advance and interns them separately. This means that if you have a constant such as " " + "abc", it's compiled as " abc" - a different string in the interned pool.

  4. Long string concatenations with + are translated using StringBuilder to avoid creation of several intermediate objects that will get discarded. Each operand of the + becomes a call to append.

So, c behaved differently than b because a shares store wit b, but b doesn't share store with c - because c was derived from a different constant (and nowadays a substring creates a new backing array anyway).

Now, trick 2 returns false for c equaling "abc" because, as we said, the constant itself is not what it was - you changed it.

Trick 3 - why, when a equals b, does adding a space before them make them unequal? Well, because " " + a is a constant, and gets interned in advance as " abc", whereas " " + b is not a constant known at compile time, therefore it gets calculated at runtime. Easy to check if you add

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

This prints true - they are the same string, and this can happen only if " " + a was interened in advance based on the compiler's belief in the immutability of strings and the finality of finals.

So, " " + a is now certain to be " abc". So no wonder it is equal to " " + c. Although c is not a pre-interned constant, it is still "abc" and the concatenation still produces the same result.

Finally, the expression which you printed with different prints still took "abc" alone, so it prints it as "cba" which is its new value. But when you printed it in one big print, some of it is a compiler-time constant expression - specifically, the part in the parentheses:

System.out.println("a = "+a+", b = "+b+", c = "+c+" oraz abc ="+(" "+"abc")+"\n");

gets interned at compile time as " abc" - and you already know that's a separate constant.

Java translates a concatenation of strings into a StringBuilder with several appends. That expression is equivalent to:

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() );

Now, there are two groups of constants that were pre-joined, and one of them is the " " + "abc" that you put in parentheses.

If you remove the parentheses, the space and the "abc" get appended separately, and then "abc" displays as "cba".

You can see this if you use

javap -p -v <class file>
RealSkeptic
  • 33,993
  • 7
  • 53
  • 79
  • That is GENIUS!!! Wow. For me you're java master. One, last question - why on last line `oraz abc ="+(" "+"abc")` when we delete parentheses it prints again `abc = cba`? I was needed to put that into `()` to print it as `abc`. Thanks for your help. – Z-DNA Mar 17 '16 at 21:32
  • @Z-DNA I changed the end of my answer a little bit as it was not entirely accurate - only the beginning of the concatenation gets interned together. The rest are appended using a `StringBuilder`. – RealSkeptic Mar 17 '16 at 21:51