添加String Literals和String对象之间的区别
添加String Literal和String Object之间有什么区别?
例如
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
相同的位置?
因为s1 + s2
不是常量表达式 ,因为s1
和s2
不是final
,因此它的结果不是interned,即创建另一个对象来表示它,因此引用比较产生false
。
JLS 3.10.5字符串文字 :
字符串文字 – 或者更一般地说,是作为常量表达式的值的字符串(第15.28节) – 使用String.intern方法“interned”以便共享唯一的实例。
JLS 15.28常量表达式 :
编译时常量表达式是表示基本类型值的表达式或不突然完成的字符串,仅使用以下内容组成:
- …
- 引用常量变量的简单名称(§4.12.4)。
JLS 4.12.4定义了final
变量。
如果将s1
和s2
声明为final
,则s3 == s5
将为true
。
因为你在比较参考文献。 要比较内容,请使用s1.equals(s2)
。
如果你的引用比较是故意的,那么你不清楚为什么你期望编译器/ JVM以不同方式产生实习或不实习的相同字符串。
编辑:我假设您知道您正在比较引用,而不是字符串的内容。 如果不是, s3.equals(s5)
就是你要找的东西(如前所述)。
s3
由编译器优化为"hellohello1"
,它也被重用于s4
。 我很惊讶编译器不够聪明,不能为 s5
做同样的事情。您使用的是哪个JDK版本? 。 此优化仅允许用于常量表达式(请参阅Java语言规范的15.28 )。 换句话说,对非final变量的任何赋值都否定了以后优化的可能性。
这是一个简单类的javap -c -l
的输出,它将你的代码包装成一个main方法(不是任何人都要求它,但我很好奇自己)。 那么让我们看看发生了什么:
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."":(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并在将结果存储在插槽5(
astore 5
)之前加载了插槽1(aload_1
)和插槽2(aload_2
)。
因为编译器优化了String文字的连接。
在实践中,这应该无关紧要(大多数情况下),因为您通常希望使用equals方法比较Strings的相等性,而不是检查对象引用是否相同。
另请注意,您可以使用例如实习生s5实习:
s5 = s5.intern();
但这很少需要。