Reputation: 1445
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?
Upvotes: 6
Views: 206
Reputation: 7394
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 ]
Upvotes: 6
Reputation: 424983
Let's break it down.
String s3 = new String("Java");
s3
is always a new Object
; it is never obtained from the intern pool.
However, the String constant "Java"
that is passed to its constructor is from the intern pool (as per the JLS).
String s2 = s1.intern();
As per the javadoc, if the String isn't already in the pool, intern()
adds it to the pool and returns it, otherwise it returns the String that was already there.
String s1 = "Ja".concat("va");
This creates a new Object "Java"
without involving the pool at all.
Thus, if the string constant '"Java"
is encountered before calling s1.intern()
, then the string constant will be returned. Otherwise, s1
will be returned.
Upvotes: 0
Reputation: 7290
In App1, the concatenated s1
is the first "Java" String to get interned, in App2 it's the explicit literal "Java" that gets interned. Let's have a detailed look:
Let's assume there's no String "Java" in the pool before running main()
, [which might not be true for every JRE version].
String s1 = "Ja".concat("va");
Now there's a String "Java", not in the pool.
String s2 = s1.intern();
Now, "Java" is interned, in your case simply by registering your s1
instance as part of the pool. So, s1
and s2
are identical.
The line
String s3 = new String("Java");
changes the initial state. You have a String literal here "Java" that becomes part of your class file and thus gets interned into the pool when your App2 class is loaded.
String s1 = "Ja".concat("va");
This still gives a fresh "Java" String outside of the pool, as in App1.
String s2 = s1.intern();
This finds a "Java" String already present in the pool (the String literal "Java" from loading the s3 assignment statement), which is a different instance. Hence s1 and s2 are different now, s1 the concatenated string, s2 the interned literal.
The same will happen if anywhere in your App2 class there is a String literal or constant expression "Java" that occupies the "Java" place in the pool before you intern your concatenated string.
The String being "Java" makes the case tricky, as it's somewhat likely that some JRE classes loaded before your App class might already contain a literal "Java", explaining why the behaviour is different with different JREs. Try it with something like "hmkjlfhvjkh".
Upvotes: 0
Reputation: 10184
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.
Upvotes: 2
Reputation: 27971
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
.
Upvotes: 2
Reputation: 15684
To quote the documentation for intern (emphasis mine):
When the
intern
method is invoked, if the pool already contains a string equal to thisString
object as determined by theequals(Object)
method, then the string from the pool is returned. Otherwise, thisString
object is added to the pool and a reference to thisString
object is returned.
Upvotes: -2