关于Java中的字符串,我有一个简单的问题。下面的简单代码段只是将两个字符串连接起来,然后将它们与==
进行比较。
String str1="str";
String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
比较表达式concat=="string"
返回明显的false
(我理解equals()
和==
之间的区别)。
当这两个字符串声明为final
时,
final String str1="str";
final String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
比较表达式concat=="string"
,在本例中返回true
。为什么final
会起作用呢?它一定要对实习生泳池做些什么吗?还是我只是被误导了?
发布于 2013-10-17 04:49:10
当您将一个String
(它是不可变的)变量声明为final
,并使用编译时常量表达式初始化它时,它也成为一个编译时常量表达式,它的值由使用它的编译器内联。因此,在第二个代码示例中,在插入值后,编译器将字符串连接转换为:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
与"string"
相比,这将为您提供true
,因为字符串文本是内部的。
来自变量
原语类型或类型
String
的变量,即final
,由编译时常量表达式初始化(§15.28),称为常量变量。
也来自JLS§15.28 -常量表达式:
String
类型的编译时常量表达式总是“内部”的,以便使用String#intern()
方法共享唯一的实例。
在您的第一个代码示例中,情况并非如此,其中String
变量不是final
。因此,它们不是编译时常量表达式.那里的连接操作将延迟到运行时,从而导致创建一个新的String
对象。您可以通过比较这两个代码的字节代码来验证这一点。
第一个代码示例(non-**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
显然,它将str
和ing
存储在两个独立的变量中,并使用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
对象。字符串在编译时就已经知道,并且它们是内部的。
发布于 2013-10-17 04:52:23
根据我的研究,所有的final String
都是用Java实现的。其中一篇博文中写道:
因此,如果确实需要使用==或!=比较两个字符串,请确保在进行比较之前调用String.intern()方法。否则,总是更喜欢字符串比较的String.equals( String )。
这意味着,如果调用String.intern()
,可以使用==
运算符比较两个字符串。但是这里没有必要使用String.intern()
,因为final String
在内部是内部的。
您可以找到更多关于使用==算子的字符串比较和String.intern()方法的信息。
有关更多信息,请参阅此堆栈过流帖子。
发布于 2013-10-17 04:52:36
如果你看一下这个方法
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
连接str1
和str2
,所以
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
语句中与来自该池的相同字符串文本进行比较。
https://stackoverflow.com/questions/19418427
复制相似问题