san544 san544 - 5 months ago 8
Java Question

Why the output of below class was not as normal?

The output of the below class really shocked me, but am not understanding how it would happen.

public class SampleTest {

public static void main(String[] args) throws SecurityException, NoSuchFieldException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException {

String someString = "IMMUTABLE";
Field field = Class.forName("java.lang.String").getDeclaredField("value");
field.setAccessible(true);
char[] value = (char []) field.get(someString);
String anotherString = "NOTREALLY";
for (int i=0; i<value.length; i++){
char c = anotherString.toCharArray()[i];
value[i]=c;
}

System.out.println(someString); // prints NOTREALLY
System.out.println("IMMUTABLE"); // Why it prints NOTREALLY here..!!!

}

}

Answer

TL;DR: Don't do that. That code is (ab)using reflection to violate the specification. According to the JLS, String values cannot change. That code changes the value of the string by accessing its undocumented private internal structures. Bad idea.

What's going on:

Equivalent string literals in a class are all combined so that at runtime, they all refer to the same string object in memory; they're written to the class's "constant pool." So all occurrences of "IMMUTABLE" in that class actually refer to the same instance of String. That means that in effect, that last line

System.out.println("IMMUTABLE");

...is equivalent to

System.out.println(someString);

...since the literal and the variable refer to the same object once the class is loaded.

Since the code (ab)uses reflection to overwrite the private undocumented value of the string object, naturally you see that updated state in all the places the object is used.

It would also happen if you moved your System.out.println("IMMUTABLE"); line into a separate class and ensured that that class was loaded before your code modified someString. That's because when a class is loaded, the strings in its constants pool are interned so that equivalent string constants in different classes end up referring to the same String object. This isn't something you'd want to rely on, not least because strings can (these days) get chucked out of the intern pool, but in a simple case you'd probably observe it.

So for instance, suppose you had this separate class:

public class Separate {
    public static void show() {
        System.out.println("IMMUTABLE");
    }
}

And you modified your code to use it, but only after modifying someString:

public static void main(String[] args) throws SecurityException, NoSuchFieldException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException {
    String someString = "IMMUTABLE";
    // ...

    System.out.println(someString); // prints NOTREALLY
    Separate.show();
}

You'd get

NOTREALLY
IMMUTABLE

...because by the time Separate is loaded, you've already modified the version of the string that's in the intern pool, so when we load Separate it's not a match and ends up referring to a different String object.

But if you ensure that Separate is loaded before you modify it:

public static void main(String[] args) throws SecurityException, NoSuchFieldException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException {
    Separate.show(); // <================ Note

    String someString = "IMMUTABLE";
    // ...

    System.out.println(someString); // prints NOTREALLY
    Separate.show();
}

you get this bit of fun:

IMMUTABLE
NOTREALLY
NOTREALLY

...because as of when Separate is loaded, its "IMMUTABLE" is equivalent to the one in the intern pool, so Separate uses that String instance — which the code in your main then modifies.