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