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