字符串字面量和字符串对象相加的区别

3

什么是字符串文字和字符串对象的区别?

举个例子:

    String s1 ="hello";
    String s2 ="hello1";
    String s3 ="hello" + "hello1";
    String s4 ="hellohello1";
    String s5 = s1 + s2;

    System.out.println(s3 == s4); // returns true
    System.out.println(s3 == s5); // return false
    System.out.println(s4 == s5); // return false

为什么s3/s4指向的位置与s5不同?

1
这种效果是由字符串池引起的,并在此博客中进行了深入解释。 - Björn Pollex
4个回答

3
因为你正在比较引用。如果要比较内容,请使用s1.equals(s2)
如果你有意比较引用,那么不清楚你为什么期望编译器/JVM将以不同方式产生的相同字符串联接或不联接。

3
因为s1 + s2不是一个常量表达式,因为s1s2不是final,因此它的结果不会被整合(即不会与字符串池中的对象共享),而是创建了另一个对象来表示它,所以引用比较产生了falseJLS 3.10.5 String Literals

字符串字面量或更一般地说, 值为常量表达式(§15.28)的字符串,将被“内插”以共享唯一实例,使用方法String.intern。

JLS 15.28 Constant Expression

编译时常量表达式是指代原始类型或String的值,不会突然停止并且仅由以下组成:

  • ...
  • 指称常量变量 (§4.12.4) 的简单名称。
JLS 4.12.4定义了final变量。
如果您将s1s2声明为final,则s3 == s5将为true

好的,我明白了你的意思。这是否意味着 s5 = s1 + s2 与 s5 = new String(s1 + s2) 相似? - Kapil
+1 完美游戏 :) @Kapil,这和 s5 = new StringBuilder(s1).append(s2).toString(); 是一样的。 - sfussenegger

1

编辑:我假设您知道您正在比较引用,而不是字符串的内容。如果不是这样,s3.equals(s5)就是您要找的(如已经提到的)。

s3被编译器优化为"hellohello1",这也被重复使用于s4我很惊讶编译器没有聪明到对s5做同样的事情。您使用的JDK版本是哪个?。这种优化仅允许针对常量表达式(请参见Java语言规范的15.28)。换句话说,对非final变量的任何赋值都会否定后续优化的可能性。

这是一个简单类的输出,它将您的代码包装到一个主方法中(虽然没有人要求,但我自己很好奇)。那么让我们看看发生了什么:

public static void main(java.lang.String[]);
  Code:
    0:  ldc #16; //String hello
    2:  astore_1
    3:  ldc #18; //String hello1
    5:  astore_2
    6:  ldc #20; //String hellohello1
    8:  astore_3
    9:  ldc #20; //String hellohello1
    11: astore  4
    13: new #22; //class java/lang/StringBuilder
    16: dup
    17: aload_1
    18: invokestatic    #24; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
    21: invokespecial   #30; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
    24: aload_2
    25: invokevirtual   #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    28: invokevirtual   #37; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    31: astore  5
    33: getstatic   #41; //Field java/lang/System.out:Ljava/io/PrintStream;
    36: aload_3
    37: aload   4
    39: if_acmpne   46
    42: iconst_1
    43: goto    47
    46: iconst_0
    47: invokevirtual   #47; //Method java/io/PrintStream.println:(Z)V
    50: getstatic   #41; //Field java/lang/System.out:Ljava/io/PrintStream;
    53: aload_3
    54: aload   5
    56: if_acmpne   63
    59: iconst_1
    60: goto    64
    63: iconst_0
    64: invokevirtual   #47; //Method java/io/PrintStream.println:(Z)V
    67: getstatic   #41; //Field java/lang/System.out:Ljava/io/PrintStream;
    70: aload   4
    72: aload   5
    74: if_acmpne   81
    77: iconst_1
    78: goto    82
    81: iconst_0
    82: invokevirtual   #47; //Method java/io/PrintStream.println:(Z)V
    85: return

LocalVariableTable: 
  Start  Length  Slot  Name   Signature
   0     86      0     args   [Ljava/lang/String;
   3     83      1     s1     Ljava/lang/String;
   6     80      2     s2     Ljava/lang/String;
   9     77      3     s3     Ljava/lang/String;
  13     73      4     s4     Ljava/lang/String;
  33     53      5     s5     Ljava/lang/String;


}

我对阅读字节码不是很有经验,但我会尝试一下 :)

  • 以 # 开头的数字(例如 #16)是对常量池的引用。内容始终作为注释添加到此行
  • ldc #16 后跟 astore_1 表示“加载常量 #16 并将其存储在槽 1 中”。正如您所看到的,在开始时对于槽 1 - 4 进行了 4 次操作,这转换为 s1、s2、s3 和 s4(请参见 LocalVariableTable)。
  • 对于 s5,不详细介绍,显然涉及 StringBuilder 和加载槽 1 (aload_1) 和槽 2 (aload_2),然后将结果存储在槽 5 (astore 5) 中。

3
由于s1s2不是编译时常量表达式,编译器不能对此进行优化。如果它们是static final字段,则可以(并且必须)进行该优化。 - Joachim Sauer
@Joachim 感谢您提醒我。听说过一段时间,但显然我完全忘记了 :) - sfussenegger

0

因为编译器会优化字符串文字的连接。

实际上,这通常不重要(大部分情况下),因为您通常希望使用equals方法比较字符串是否相等,而不是检查对象引用是否相同。

还请注意,您可以使用例如以下方式对s5进行国际化:

s5 = s5.intern();

虽然这很少需要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接