Comparing strings with == which are declared final in Java
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.
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.
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.