用 == 比较在 Java 中声明为 final 的字符串

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19418427/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 16:59:36  来源:igfitidea点击:

Comparing strings with == which are declared final in Java

javastringfinal

提问by Tiny

I have a simple question about strings in Java. The following segment of simple code just concatenates two strings and then compares them with ==.

我有一个关于 Java 字符串的简单问题。下面的简单代码段只是连接两个字符串,然后将它们与==.

String str1="str";
String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

The comparison expression concat=="string"returns falseas obvious (I understand the difference between equals()and ==).

比较表达式concat=="string"返回false那么明显(I明白之间的差值equals()==)。



When these two strings are declared finallike so,

当这两个字符串final像这样声明时,

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 finalmake a difference? Does it have to do something with the intern pool or I'm just being misled?

比较表达式concat=="string",在本例中返回true。为什么会final有所作为?它是否必须与实习生池有关,或者我只是被误导了?

采纳答案by Rohit Jain

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不可变的)变量声明为final,并使用编译时常量表达式对其进行初始化时,它也会成为编译时常量表达式,并且它的值由使用它的编译器内联。因此,在您的第二个代码示例中,内联值后,字符串连接由编译器转换为:

String concat = "str" + "ing";  // which then becomes `String concat = "string";`

which when compared to "string"will give you true, because string literals are interned.

与 相比"string"会给你true,因为字符串文字是实习生的

From JLS §4.12.4 - finalVariables:

JLS §4.12.4 -final变量

A variable of primitive type or type String, that is finaland initialized with a compile-time constant expression (§15.28), is called a constant variable.

原始类型或类型的变量String,即final用编译时常量表达式(第 15.28 节)初始化,称为常量变量

Also from JLS §15.28 - Constant Expression:

同样来自JLS §15.28 - 常量表达式:

Compile-time constant expressions of type Stringare always "interned"so as to share unique instances, using the method String#intern().

类型的编译时常量表达式String总是“被嵌入”,以便使用方法共享唯一的实例String#intern()



This is not the case in your first code example, where the Stringvariables 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 Stringobject. You can verify this by comparing byte code of both the codes.

在您的第一个代码示例中,情况并非如此,其中String变量不是final. 因此,它们不是编译时常量表达式。那里的连接操作将延迟到运行时,从而导致创建新String对象。您可以通过比较两个代码的字节码来验证这一点。

The first code example (non-finalversion)is compiled to the following byte code:

第一个代码示例(非final版本)被编译为以下字节码:

  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 strand ingin two separate variables, and using StringBuilderto perform the concatenation operation.

显然,它是将str和存储ing在两个单独的变量中,并StringBuilder用于执行连接操作。

Whereas, your second code example (finalversion)looks like this:

而您的第二个代码示例final版本)如下所示:

  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 stringat compile time, which is loaded by ldcoperation in step 0. Then the second string literal is loaded by ldcoperation in step 7. It doesn't involve creation of any new Stringobject at runtime. The String is already known at compile time, and they are interned.

所以它string在编译时直接内联final变量来创建String ,由ldcstep中的操作加载0。然后通过ldcstep 中的操作加载第二个字符串文字7。它不涉及String在运行时创建任何新对象。String 在编译时就已经知道了,并且它们是实习的。

回答by Pradeep Simha

As per my research, all the final Stringare interned in Java. From one of the blog post:

根据我的研究,所有这些final String都在 Java 中实习。来自其中一篇博文:

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.

因此,如果您确实需要使用 == 或 != 比较两个字符串,请确保在进行比较之前调用 String.intern() 方法。否则,总是更喜欢 String.equals(String) 进行字符串比较。

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 Stringare internally interned.

所以这意味着如果你打电话,String.intern()你可以使用==运算符比较两个字符串。但这里String.intern()没有必要,因为在 Java 中final String是内部实习的。

You can find more information String comparision using == operatorand Javadoc for String.intern()method.

您可以使用 == 运算符和 Javadoc for String.intern()方法找到更多信息字符串比较

Also refer this Stackoverflowpost for more information.

另请参阅此Stackoverflow帖子以获取更多信息。

回答by Pshemo

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 ClassWithTheseMethodsversions you will see

并使用javap -c ClassWithTheseMethods您将看到的版本进行反编译

  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 StringBuilderto concatenate str1and str2so

所以,如果串不是最终的编译器将不得不使用StringBuilder来连接str1str2使

String concat=str1+str2;

will be compiled to

将被编译为

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

which means that concatwill be created at runtime so will not come from String pool.

这意味着concat将在运行时创建,因此不会来自字符串池。



Also if Strings are final then compiler can assume that they will never change so instead of using StringBuilderit can safely concatenate its values so

此外,如果字符串是最终的,那么编译器可以假设它们永远不会改变,因此StringBuilder可以安全地连接它的值而不是使用它

String concat = str1 + str2;

can be changed to

可以改为

String concat = "str" + "ing";

and concatenated into

并连接成

String concat = "string";

which means that concatewill become sting literal which will be interned in string pool and then compared with same string literal from that pool in ifstatement.

这意味着concate它将成为字符串池中的字符串,然后与if语句中该池中的相同字符串进行比较。

回答by Sotirios Delimanolis

Let's see some byte code for the finalexample

让我们看看这个final例子的一些字节码

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 concatdirectly. You can deduce that the compiler is creating (concatenating) the String"string"itself at compilation time.

0:和 处2:String"string"被压入堆栈(来自常量池)并concat直接存储到局部变量中。您可以推断编译器正在编译时创建(连接)String"string"自身。

The non finalbyte code

final字节码

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 Stringconstants, "str"and "ing"which need to be concatenated at runtime with a StringBuilder.

在这里,你有两个String常量,"str""ing"这就需要在运行时用一个级联StringBuilder

回答by pnathan

Stack and string conts pool concept enter image description here

堆栈和字符串 conts 池概念 在此处输入图片说明

回答by Premraj

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.

但是,当您使用 Java 的字符串文字表示法创建时,它会自动调用 intern() 方法将该对象放入字符串池中,前提是该对象尚未出现在池中。

Why does final make a difference?

为什么 final 会有所不同?

Compilerknows the final variable never gonna change, when we add these final variables the output goes to String Pool because of str1 + str2expression 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.

编译器知道最终变量永远不会改变,当我们添加这些最终变量时,输出会进入字符串池,因为str1 + str2表达式输出也永远不会改变,所以最终编译器在输出上述两个最终变量后调用 inter 方法。在非最终变量编译器的情况下,不要调用实习生方法。