我对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
有所作为? 是否有做实习生池的东西或我只是被误导?
当声明一个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
显然,正存储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
在运行时对象。 该字符串已经知道在编译的时候,他们拘留。
按我的研究,所有的final String
被拘留在Java中。 从博客文章之一:
所以,如果你真的需要比较使用==或两个字符串!=确保你做比较之前调用中的String.intern()方法。 否则,总是喜欢String.equals(String)以字符串比较。
因此,这意味着,如果你调用String.intern()
您可以用比较两个字符串==
操作符。 但这里String.intern()
是没有必要的,因为在Java中final String
在内部实习。
你可以找到更多信息使用==操作符字符串比较和Javadoc文档中的String.intern()方法。
也可参考此#1后以获取更多信息。
如果你看看这个方法
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
声明。
让我们来看看一些字节码的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
。
不过,当您创建使用Java的字符串文字符号,它会自动调用实习生()方法把这个对象转换成String池,只要它是不存在的游泳池了。
为什么最终有所作为?
编译器知道最后的变量永远不会改变,当我们添加这些最终变量输出为字符串池,因为str1 + str2
表达产量也永远不会改变,因此,最后的编译器上面的两个最终变量的输出后调用间方法。 在非final的变量编译器的情况下,不叫实习生方法。