Comparing Strings with == Which Are Declared Final in Java

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 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 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.

Java: final string comparison does not work

String command = scanner.nextLine();
if (command.equals(SHOW) {
System.out.println("Print this line.");
}

Use it like this. (To answer your comment)

Comparing string references

In

String s = "e!";
String str7 = "Lov"+ s;

While "e!" is a constant expression, s is not a constant variable (JLS §4.12.4); therefore, "Lov" + s, which references s, cannot be a constant expression (JLS §15.28). In order for a variable like s to be a constant variable, it needs to be both final and initialized from a constant expression.

If you had written

final String s = "e!";
String str7 = "Lov" + s;

then str1 == str7 would have been true.

Difference in the following declarations

str1 + str2 for non-compilation-constant strings will be compiled into

new StringBuilder(str1).append(str2).toString(). This result will not be put, or taken from string pool (where interned strings go).

It is different story in case of "foo"+"bar" where compiler knows which values he works with, so he can concatenate this string once to avoid it at runtime. Such string literal will also be interned.

So String str3 = str1+str2; is same as

String str3 = new StringBuilder(str1).append(str2).toString();

and String str3 = new String(str1+str2); is same as

String str3 = new String(new StringBuilder(str1).append(str2).toString());

Again, strings produced as result of method (like substring, replace, toString) are not interned.

This means you are comparing two different instances (which store same characters) and that is why == returns false.

Why is the String class declared final in Java?

It is very useful to have strings implemented as immutable objects. You should read about immutability to understand more about it.

One advantage of immutable objects is that

You can share duplicates by pointing them to a single instance.

(from here).

If String were not final, you could create a subclass and have two strings that look alike when "seen as Strings", but that are actually different.

String.equals versus ==

Use the string.equals(Object other) function to compare strings, not the == operator.

The function checks the actual contents of the string, the == operator checks whether the references to the objects are equal. Note that string constants are usually "interned" such that two constants with the same value can actually be compared with ==, but it's better not to rely on that.

if (usuario.equals(datos[0])) {
...
}

NB: the compare is done on 'usuario' because that's guaranteed non-null in your code, although you should still check that you've actually got some tokens in the datos array otherwise you'll get an array-out-of-bounds exception.

Confused about comparing string in this example

Since fName is final and initialized with a literal String, it's a constant expression.

So the instruction

String name2 = fName + "Gosling"

is compiled to

String name2 = "James" + "Gosling"

which is compiled to

String name2 = "JamesGosling"

So, in the bytecode, you have the equivalent of

String name2 = "JamesGosling";
String name3 = "JamesGosling";

So both name2 and name3 reference the same literal String, which is interned.

On the other hand, lName is not final, and is thus not a constant expression, and the concatenation happens at runtime rather than at compile time. So the concatenation creates a new, non-interned String.



Related Topics



Leave a reply



Submit