Comparing strings with == which are declared final in Java
Asked Answered
D

6

224

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" returns false as obvious (I understand the difference between equals() 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 returns true. Why does final make a difference? Does it have to do something with the intern pool or I'm just being misled?

Diathermic answered 17/10, 2013 at 4:41 Comment(4)
I 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.Overwhelm
This 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.Crease
@Overwhelm 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 by equals, 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 of equals and == reversed only for Strings would be silly. Also, regardless of this, I don't see any advantage anyway in having == do the contents comparison instead of equals.Ugh
@Ugh 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.Overwhelm
L
235

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.

Lepus answered 17/10, 2013 at 4:49 Comment(4)
There is nothing preventing other Java compiler implementation not to intern a final String right?Warehouseman
@Warehouseman the JLS requires that compile-time constant string expressions are interned. Any conforming implementation must do the same thing here.Untidy
Conversely, 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?Graner
@Graner 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.Stepdaughter
W
31

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.

Weihs answered 17/10, 2013 at 4:52 Comment(2)
intern() 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.Gravelly
@Gravelly - Interned strings can be garbage collected. Even interned strings resulting from constant expressions can be garbage collected in some circumstances.Ran
C
21

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.

Clarethaclaretta answered 17/10, 2013 at 4:52 Comment(0)
R
15

Stack and string conts pool concept enter image description here

Rossie answered 17/10, 2013 at 6:33 Comment(2)
What? I don't understand how this has upvotes. Can you clarify your answer?Kulak
I 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.Abattoir
S
3

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.

Skat answered 17/10, 2013 at 5:3 Comment(0)
G
0

Though, when you create using String literal notation of Java, it automatically call intern() method to put that object into String pool, provided it was not present in the pool already.

Why does final make a difference?

Compiler knows the final variable never gonna change, when we add these final variables the output goes to String Pool because of str1 + str2 expression output also never gonna change, So finally compiler calls inter method after output of the above two final variables. In case of non-final variable compiler do not call intern method.

Goosy answered 11/1, 2016 at 7:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.