I have written a same program in two different way and both are giving me different output. i am not able to understand why. please correct me. In first program i am getting this output
Original: Umesh
Changed: Xmesh
And in second program i am getting this output.
Original: Umesh
Changed: Umesh
Program-1
import java.lang.reflect.Field;
public class SomeClass {
public static void main(final String[] args) throws Throwable {
final String s = "Umesh";
changeString(s);
}
// We need a method so the compiler won't inline "s":
static void changeString(final String s) throws Throwable {
System.out.println("Original: " + s);
final Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
final char[] value = (char[]) field.get(s);
value[0] = 'X';
System.out.println("Changed: " + s);
}
}
Program-2
import java.lang.reflect.Field;
public class SomeClass {
public static void main(final String[] args) throws Throwable {
final String s = "Umesh";
System.out.println("Original: " + s);
final Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
final char[] value = (char[]) field.get(s);
value[0] = 'X';
System.out.println("Changed: " + s);
}
}
I'll just start by saying you really, really shouldn't be mucking about with strings like that. :-)
In your first example you aren't "re-referencing" anything (that is, you're not changing what string s
refers to), what you're doing is modifying the string it refers to. Even though officially strings are immutable, you're using reflection as a backdoor to modify the undocumented internals of the String
implementation in Oracle's JDK (other JDKs may be implemented differently, making that code fail). But the s
reference is unchanged. Even with reflection, you can't change the value of a final
local variable. (You could change a final
field via reflection, but doing so would open you up to the same sort of inconsistencies you're seeing in this example.)
What's happening in your second example is that since s
is a final
variable you give a literal value to within main
, as far as the compiler is concerned, it's a compile-time constant, since String
is officially immutable. The compiler is (very) aware of strings and does a fair bit of optimization around them, such as turning "a" + "b" + "c"
into simply "abc"
. So later, when it sees "Original: " + s
, it can happily just substitute "Original: Umesh"
for that. And again at the end, when it sees "Changed: " + s
it can replace that with "Changed: Umesh"
. It ends up being exactly as though you'd actually written System.out.println("Original: Umesh");
and System.out.println("Changed: Umesh");
in the source code.
The compiler couldn't do that in the first example because s
is an argument to the function, rather than a final
declared right there in main
.
You can see the difference in the bytecode. Compile each of them, then disassemble them via javac -p SomeClass
. Here's what I get (I called them Example1
and Example2
):
$ javap -c Example1 Compiled from "Example1.java" public class Example1 { public Example1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]) throws java.lang.Throwable; Code: 0: ldc #2 // String Umesh 2: invokestatic #3 // Method changeString:(Ljava/lang/String;)V 5: return static void changeString(java.lang.String) throws java.lang.Throwable; Code: 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #5 // class java/lang/StringBuilder 6: dup 7: invokespecial #6 // Method java/lang/StringBuilder."":()V 10: ldc #7 // String Original: 12: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 25: ldc #11 // class java/lang/String 27: ldc #12 // String value 29: invokevirtual #13 // Method java/lang/Class.getDeclaredField:(Ljava/lang/String;)Ljava/lang/reflect/Field; 32: astore_1 33: aload_1 34: iconst_1 35: invokevirtual #14 // Method java/lang/reflect/Field.setAccessible:(Z)V 38: aload_1 39: aload_0 40: invokevirtual #15 // Method java/lang/reflect/Field.get:(Ljava/lang/Object;)Ljava/lang/Object; 43: checkcast #16 // class "[C" 46: checkcast #16 // class "[C" 49: astore_2 50: aload_2 51: iconst_0 52: bipush 88 54: castore 55: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 58: new #5 // class java/lang/StringBuilder 61: dup 62: invokespecial #6 // Method java/lang/StringBuilder."":()V 65: ldc #17 // String Changed: 67: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 70: aload_0 71: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 74: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 77: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 80: return }
and
$ javap -c Example2 Compiled from "Example2.java" public class Example2 { public Example2(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]) throws java.lang.Throwable; Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Original: Umesh 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: ldc #5 // class java/lang/String 10: ldc #6 // String value 12: invokevirtual #7 // Method java/lang/Class.getDeclaredField:(Ljava/lang/String;)Ljava/lang/reflect/Field; 15: astore_2 16: aload_2 17: iconst_1 18: invokevirtual #8 // Method java/lang/reflect/Field.setAccessible:(Z)V 21: aload_2 22: ldc #9 // String Umesh 24: invokevirtual #10 // Method java/lang/reflect/Field.get:(Ljava/lang/Object;)Ljava/lang/Object; 27: checkcast #11 // class "[C" 30: checkcast #11 // class "[C" 33: astore_3 34: aload_3 35: iconst_0 36: bipush 88 38: castore 39: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 42: ldc #12 // String Changed: Umesh 44: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 47: return }
Notice how we don't see any string concatenation (StringBuilder
usage) in the second example, the compiler combined the static strings at the compilation stage.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With