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 is final
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 method String#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 pieces of code.
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.
equals()
and==
in the context of strings, and is asking a more meaningful question. – CreaseString
? 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
. – Ugh