2011-04-13 109 views
2

添加字符串文字和字符串对象有什么区别?添加字符串文字和字符串对象的区别

例如

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

此效应是由字符串interning引起的,在[本博客](http://javatechniques.com/blog/string-equality-and-interning/)中有深入解释。 – 2011-04-13 12:36:14

回答

2

因为s1 + s2不是常量表达式,由于s1s2final,因此,其结果是没有实习,创建即另一个对象来表示,所以基准比较产生false

JLS 3.10.5 String Literals

字符串文字,或者更一般地,字符串是“实习”,以便共享独特实例常量表达式(§15.28)-are的值,使用该方法的String.intern 。

JLS 15.28 Constant Expression

编译时常量表达式是表示原始类型或字符串的一个值,该值不会突然完成并只使用由以下的表达式:

  • ...
  • 引用常量变量的简单名称(§4.12.4)。

JLS 4.12.4定义final变量。

如果声明s1s2finals3 == s5true

+0

好吧,让你点。这是否意味着s5 = s1 + s2与s5 = new String(s1 + s2)相似; – Kapil 2011-04-13 12:45:15

+0

+1完美游戏:) @Kapil它与's5 = new StringBuilder(s1).append(s2).toString();' – sfussenegger 2011-04-13 13:11:28

3

因为您正在比较参考。要比较内容,请使用s1.equals(s2)

如果您的引用比较是故意的,则不清楚为什么您希望编译器/ JVM实习或不实习以不同方式产生的相同字符串。

1

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

s3已被编译器优化为"hellohello1",它也被s4重复使用。 我很惊讶编译器不够聪明,但是为 s5做同样的事情。您使用的是哪个JDK版本? 。此优化仅适用于常量表达式(请参阅15.28 of Java Language Specification)。换句话说,对非最终变量的任何赋值都会拒绝以后优化的可能性。

这是​​的一个简单类的输出,它将您的代码封装到一个主要方法中(不是任何人要求它,但我很好奇自己)。让我们看看这是怎么回事:

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中”。正如你所看到的,在开始的时候,第一到第四的时隙会被转换为s1,s2,s3和s4(见LocalVariableTable)。
  • 对于s5,没有详细描述,显然在将结果存储在时隙5(astore 5)之前涉及到StringBuilder并且加载了时隙1(aload_1)和时隙2(aload_2)。
+3

因为's1'和's2'不是编译时常量表达式,编译器**不允许**来优化它。如果这些是“静态最终”字段,那么它可以(并且将不得不)进行该优化。 – 2011-04-13 12:36:06

+0

@Joachim谢谢你提醒我。听到这个前一阵子,但显然我完全忘了它:) – sfussenegger 2011-04-13 13:09:51

0

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

在实践中,这应该不是问题(大部分时间),因为您通常要使用equals方法比较字符串是否相等,而不是检查对象引用是否相同。

另外请注意,你可以实习生S5使用例如为:

s5 = s5.intern(); 

这是很少用到,虽然。

相关问题