Comparing strings with == which are declared final in Java
Solution 1
When you declare a String
(which is immutable) variable as final
, and initialize it with a compile-time constant expression, it also becomes a compile-time constant expression, and its value is inlined by the compiler where it is used. So, in your second code example, after inlining the values, the string concatenation is translated by the compiler to:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
which when compared to "string"
will give you true
, because string literals are interned.
From JLS §4.12.4 - final
Variables:
A variable of primitive type or type
String
, that isfinal
and initialized with a compile-time constant expression (§15.28), is called a constant variable.
Also from JLS §15.28 - Constant Expression:
Compile-time constant expressions of type
String
are always "interned" so as to share unique instances, using the methodString#intern()
.
This is not the case in your first code example, where the String
variables are not final
. So, they are not a compile-time constant expressions. The concatenation operation there will be delayed till runtime, thus leading to the creation of a new String
object. You can verify this by comparing byte code of both the codes.
The first code example (non-final
version) is compiled to the following byte code:
Code:
0: ldc #2; //String str
2: astore_1
3: ldc #3; //String ing
5: astore_2
6: new #4; //class java/lang/StringBuilder
9: dup
10: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: ldc #9; //String string
31: if_acmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #10; //Method java/io/PrintStream.println:(Z)V
42: return
Clearly it is storing str
and ing
in two separate variables, and using StringBuilder
to perform the concatenation operation.
Whereas, your second code example (final
version) looks like this:
Code:
0: ldc #2; //String string
2: astore_3
3: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_3
7: ldc #2; //String string
9: if_acmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
20: return
So it directly inlines the final variable to create String string
at compile time, which is loaded by ldc
operation in step 0
. Then the second string literal is loaded by ldc
operation in step 7
. It doesn't involve creation of any new String
object at runtime. The String is already known at compile time, and they are interned.
Solution 2
As per my research, all the final String
are interned in Java. From one of the blog post:
So, if you really need to compare two String using == or != make sure you call String.intern() method before making comparison. Otherwise, always prefer String.equals(String) for String comparison.
So it means if you call String.intern()
you can compare two strings using ==
operator. But here String.intern()
is not necessary because in Java final String
are internally interned.
You can find more information String comparision using == operator and Javadoc for String.intern() method.
Also refer this Stackoverflow post for more information.
Solution 3
If you take a look at this methods
public void noFinal() {
String str1 = "str";
String str2 = "ing";
String concat = str1 + str2;
System.out.println(concat == "string");
}
public void withFinal() {
final String str1 = "str";
final String str2 = "ing";
String concat = str1 + str2;
System.out.println(concat == "string");
}
and its decompiled with javap -c ClassWithTheseMethods
versions you will see
public void noFinal();
Code:
0: ldc #15 // String str
2: astore_1
3: ldc #17 // String ing
5: astore_2
6: new #19 // class java/lang/StringBuilder
9: dup
10: aload_1
11: invokestatic #21 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: aload_2
18: invokevirtual #30 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #34 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
...
and
public void withFinal();
Code:
0: ldc #15 // String str
2: astore_1
3: ldc #17 // String ing
5: astore_2
6: ldc #44 // String string
8: astore_3
...
So if Strings are not final compiler will have to use StringBuilder
to concatenate str1
and str2
so
String concat=str1+str2;
will be compiled to
String concat = new StringBuilder(str1).append(str2).toString();
which means that concat
will be created at runtime so will not come from String pool.
Also if Strings are final then compiler can assume that they will never change so instead of using StringBuilder
it can safely concatenate its values so
String concat = str1 + str2;
can be changed to
String concat = "str" + "ing";
and concatenated into
String concat = "string";
which means that concate
will become sting literal which will be interned in string pool and then compared with same string literal from that pool in if
statement.
Solution 4
Stack and string conts pool concept
Solution 5
Let's see some byte code for the final
example
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: ldc #2 // String string
2: astore_3
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_3
7: ldc #2 // String string
9: if_acmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
20: return
}
At 0:
and 2:
, the String
"string"
is pushed onto the stack (from the constant pool) and stored into the local variable concat
directly. You can deduce that the compiler is creating (concatenating) the String
"string"
itself at compilation time.
The non final
byte code
Compiled from "Main2.java"
public class Main2 {
public Main2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: ldc #2 // String str
2: astore_1
3: ldc #3 // String ing
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: ldc #9 // String string
31: if_acmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
42: return
}
Here you have two String
constants, "str"
and "ing"
which need to be concatenated at runtime with a StringBuilder
.
Related videos on Youtube
Tiny
Just an orphan kid and have no more to say. Three things in general, cannot be avoided (at least I can never) Mother Mother-tongue Mother-land. They are always unique. I'm a family-less boy. My family was hunted leaving me all alone when my house targeted and deliberately set on a fire by a mob during a nonsense communal riot but I was survived by a rescue team with the help of firemen. As a survival, I didn't know whether it was my fortune or misfortune but when I recovered, the rescue team came to my home, one day. One of the members gave me a piece of paper in my hand in which the following text was written. lifeisnowhere. He asked me to read it carefully and I could hardly interpret the text as Life is now here, instead of Life is nowhere. All of them gave me a cute smile and went away and I decided to live peacefully and hopefully on their saying from then onwards and very soon. Because of this tragedy, I'm alone couldn't join a school but a curiosity to learn something made me a self-learner. I'm indeed a self-learner, so I'm likely not able to answer any questions on this site right now. In the field of computer science, my self-study mainly includes, QBASIC, C, C++, C#, VB, Java, JavaScript, PHP and a little about ASP.NET. Oracle, MySQL and MSSQL-Server with DBMS. and other theoretical subjects. I'm currently dealing with - Android and Java EE including Servlet, JSP-JSTL/EL (with Spring and Struts with ORM models JPA/Hibernate) and JSF.
Updated on June 05, 2022Comments
-
Tiny about 2 years
I have a simple question about strings in Java. The following segment of simple code just concatenates two strings and then compares them with
==
.String str1="str"; String str2="ing"; String concat=str1+str2; System.out.println(concat=="string");
The comparison expression
concat=="string"
returnsfalse
as obvious (I understand the difference betweenequals()
and==
).
When these two strings are declared
final
like so,final String str1="str"; final String str2="ing"; String concat=str1+str2; System.out.println(concat=="string");
The comparison expression
concat=="string"
, in this case returnstrue
. Why doesfinal
make a difference? Does it have to do something with the intern pool or I'm just being misled?-
Davio over 10 yearsI always found it silly that equals was the default way of checking for equal contents, instead of having == do that and just use referenceEquals or something similar to check if the pointers are the same.
-
arshajii over 10 yearsThis is not a duplicate of "How do I compare strings in Java?" in any way. The OP understands the difference between
equals()
and==
in the context of strings, and is asking a more meaningful question. -
SantiBailors over 7 years@Davio But how would that work when the class is not
String
? I think it's very logical, not silly, to have the contents comparison done byequals
, a method we can override to tell when we consider two objects equal, and to have the identity comparison done by==
. If the contents comparison was done by==
we couldn't override that to define what we mean by "equal contents", and having the meaning ofequals
and==
reversed only forString
s would be silly. Also, regardless of this, I don't see any advantage anyway in having==
do the contents comparison instead ofequals
. -
Davio over 7 years@SantiBailors you are correct that this is just how it works in Java, I've also used C# where == is overloaded for content equality. An added bonus of using == is that it's null-safe: (null == "something") returns false. If you use equals for 2 objects, you have to be aware if either can be null or you risk a NullPointerException being thrown.
-
-
Ajeesh over 10 yearsintern() strings are not garbage collected and it's stored in permgen space which is low so you will be get into trouble like out of memory error if not used properly.
-
Alvin over 10 yearsThere is nothing preventing other Java compiler implementation not to intern a final String right?
-
Tavian Barnes over 10 years@Alvin the JLS requires that compile-time constant string expressions are interned. Any conforming implementation must do the same thing here.
-
Cᴏʀʏ over 10 yearsWhat? I don't understand how this has upvotes. Can you clarify your answer?
-
viki.omega9 over 10 yearsI think the intended answer is that because str1+str2 is not optimized to an interned string, comparison with a string from the string pool will lead to a false condition.
-
Stephen C about 7 years@Ajeesh - Interned strings can be garbage collected. Even interned strings resulting from constant expressions can be garbage collected in some circumstances.
-
phant0m over 5 yearsConversely, does the JLS mandate that a compiler must not optimize the concatenation in the first, non-final version? Is the compiler forbidden from producing code, that would have the comparison evaluate to
true
? -
Holger over 4 years@phant0m taking the current wording of the specification, “The
String
object is newly created (§12.5) unless the expression is a constant expression (§15.28).” literally, applying an optimization in the non-final version is not allowed, as a “newly created” string must have a different object identity. I don’t know whether this is intentional. After all, the current compilation strategy is to delegate to a runtime facility which does not document such restrictions.