I am struggling with the following riddle of my coworker:
public class App1 {
public static void main(String[] args) {
String s1 = "Ja".concat("va"); // seems to be interned?!
String s2 = s1.intern();
System.out.println(s1 == s2); // true
}
}
This outputs true. I am little bit surprised because it looks like s1 is interned. But this is no constant expression, isn't it?
But then I am even more surprised why the following prints false.
public class App2 {
public static void main(String[] args) {
String s1 = "Ja".concat("va"); // seems not to be interned?!
String s3 = new String("Java"); // this changes output
String s2 = s1.intern();
System.out.println(s1 == s2); // false
}
}
Why does the introduction of s3 change the output?
Here are the rules governing Java String objects wrt to String pool:
Let's go over your example,
String s1 = "Ja".concat("va");
If you look at the concat operation in String source, you will notice it calls new operator at the end.
new String(buf, true)
Therefore, s1 is not added to the string pool.
Now, let's look at the line where intern is called,
String s2 = s1.intern();
Here, the intern method on s1 returns the object from the String pool (created if it didn't exist). So, s2 contains the object from the String pool.
Meanwhile, s1 still contains the old object and not the one in the pool. Therefore,
(s1 == s2)
is always going to return false.
Modified Behavior in Java 1.8.0_92-b14
The behavior in Java 8 has changed. The Java compiler is performing optimization. If the intern method is called immediately after concat, Java 8 optimizes and creates the string object in the String Pool and ignores the earlier behavior of new which we have witnessed in the earlier versions of Java. Please check the optimization in the opcodes of the decompiled code (checkOne is App1 and checkTwo is App2),
public static void checkOne();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=0
0: ldc #2 // String Ja
2: ldc #3 // String va
4: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
7: astore_0
8: aload_0
9: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String;
12: astore_1
13: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
16: aload_0
17: aload_1
18: if_acmpne 25
21: iconst_1
22: goto 26
25: iconst_0
26: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
29: return
LineNumberTable:
line 6: 0
line 7: 8
line 9: 13
line 10: 29
LocalVariableTable:
Start Length Slot Name Signature
8 22 0 s1 Ljava/lang/String;
13 17 1 s2 Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 25
locals = [ class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
public static void checkTwo();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=0
0: ldc #2 // String Ja
2: ldc #3 // String va
4: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
7: astore_0
8: new #8 // class java/lang/String
11: dup
12: ldc #9 // String Java
14: invokespecial #10 // Method java/lang/String."":(Ljava/lang/String;)V
17: astore_1
18: aload_0
19: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String;
22: astore_2
23: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_0
27: aload_2
28: if_acmpne 35
31: iconst_1
32: goto 36
35: iconst_0
36: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
39: return
LineNumberTable:
line 13: 0
line 14: 8
line 15: 18
line 17: 23
line 18: 39
LocalVariableTable:
Start Length Slot Name Signature
8 32 0 s1 Ljava/lang/String;
18 22 1 s3 Ljava/lang/String;
23 17 2 s2 Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 35
locals = [ class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
The documentation for the intern method says that it is guaranteed to return a string from the pool of unique strings. It doesn't guarantee that it is the exact same string that you call intern on.
When you do String s3 = new String("Java"), you are actually interning the string literal "Java" that you pass to the constructor. This makes s1.intern() return that string instead of s1.
I am on JDK 1.8.0_144. I ran your program and it prints 'false' in both the scenarios you have described. It makes complete sense. Here's why.
When you execute the statement "Ja".concat("va");, it returns a new String object. Here's the return statement from the java.lang.String#concat method:
return new String(buf, true);
Since the String returned is created using new keyword, the string is not added to the String pool. (Remember, only String literals and String resulting from constant expressions are added to the pool; the strings created using new are not).
When you create s2 as an intern of s1, it is the first time the string "Java" is added to the pool. So at that point s1 and s2 are different objects. The string s1 sits on perm gen area while s2 is in the String pool in the main part of the heap. Hence, they are not equal memory-wise. So it rightly prints false.
The introduction of the line String s3 = new String("Java"); has nothing to do with this behavior.
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