串用==比较了该声明最后在Java中串用==比较了该声明最后在Java中(Comparing str

2019-05-08 20:04发布

我对Java中的字符串一个简单的问题。 简单的代码下面段只是连接两个字符串,然后将它们与比较==

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

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

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


当这两个字符串宣布final像这样,

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

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

比较表达式concat=="string" ,在这种情况下,返回true 。 为什么final有所作为? 是否有做实习生池的东西或我只是被误导?

Answer 1:

当声明一个String (这是不可变的 )变量作为final ,用一个编译时间常量表达式进行初始化,也变得一个编译时间常数表达,并且它的值是由在那里使用的编译器内联。 所以,在你的第二个代码示例,内联值后,字符串连接是由编译器来编译:

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

这相比于当"string"会给你true ,因为字面字符串被拘留

从JLS§4.12.4 - final变量 :

原始类型或类型的变量String ,即final ,用一个编译时间常量表达式(§15.28)初始化,被称为恒定变量

此外,从JLS§15.28 -常量表达式:

编译时类型的常量表达式String总是“实习”,以便共享独特实例,使用该方法String#intern()


这是不是在你的第一个代码示例,其中的情况下String变量不是final 。 所以,他们不是一个编译时间常数表达式。 结合动作也将被推迟,直到运行,从而导致建立一个新的String对象。 您可以通过比较两者的代码的字节代码验证这一点。

第一个代码示例(非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

显然,正存储string在两个独立的变量,并使用StringBuilder执行级联操作。

然而,你的第二个代码示例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

因此,它直接内联最终变量来创建字符串string在编译时,其通过加载ldc操作在步骤0 。 然后字面第二串由加载ldc操作在步骤7 。 它不涉及任何新创建String在运行时对象。 该字符串已经知道在编译的时候,他们拘留。



Answer 2:

按我的研究,所有的final String被拘留在Java中。 从博客文章之一:

所以,如果你真的需要比较使用==或两个字符串!=确保你做比较之前调用中的String.intern()方法。 否则,总是喜欢String.equals(String)以字符串比较。

因此,这意味着,如果你调用String.intern()您可以用比较两个字符串==操作符。 但这里String.intern()是没有必要的,因为在Java中final String在内部实习。

你可以找到更多信息使用==操作符字符串比较和Javadoc文档中的String.intern()方法。

也可参考此#1后以获取更多信息。



Answer 3:

如果你看看这个方法

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");
}

和它的反编译带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;
      ...

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

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

String concat=str1+str2;

将被编译成

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

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


此外,如果字符串是最后则编译器可以假设,他们将永远不会改变的,从而代替使用StringBuilder它可以安全地串联其值,以便

String concat = str1 + str2;

可改为

String concat = "str" + "ing";

并串连成

String concat = "string";

这意味着concate将成为字面蜇将在字符串池被拘禁,然后用相同的字符串从池文字相比if声明。



Answer 4:

堆栈和字符串conts池概念



Answer 5:

让我们来看看一些字节码的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
}

0:2:中, String "string"被压入堆栈(从常量池中)并存储到局部变量concat直接。 可以推断,编译器创建(串联)的String "string"在编译时间本身。

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
}

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



Answer 6:

不过,当您创建使用Java的字符串文字符号,它会自动调用实习生()方法把这个对象转换成String池,只要它是不存在的游泳池了。

为什么最终有所作为?

编译器知道最后的变量永远不会改变,当我们添加这些最终变量输出为字符串池,因为str1 + str2表达产量也永远不会改变,因此,最后的编译器上面的两个最终变量的输出后调用间方法。 在非final的变量编译器的情况下,不叫实习生方法。



文章来源: Comparing strings with == which are declared final in Java