用==比较在Java中声明为最终的string
我有一个关于Java中string的简单问题。 以下简单代码段连接两个string,然后将它们与==
进行比较。
String str1="str"; String str2="ing"; String concat=str1+str2; System.out.println(concat=="string");
比较expression式concat=="string"
返回false
(我理解equals()
和==
)之间的区别。
当这两个string被声明为final
时候,
final String str1="str"; final String str2="ing"; String concat=str1+str2; System.out.println(concat=="string");
比较expression式concat=="string"
,在这种情况下返回true
。 为什么final
有所作为? 是否需要对实习生池做些什么,或者我只是被误导了?
当您将一个String
( 不可变 )variables声明为final
,并用一个编译时常量expression式初始化它时,它也会变成一个编译时常量expression式,并且它的值由编译器在内部使用。 因此,在第二个代码示例中,在内联值之后,编译器将string连接转换为:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
当与"string"
相比时,会给你true
,因为string文字是interned 。
从JLS§4.12.4 – final
variables :
原始types或types为
String
variables是final
并且使用编译时常量expression式(第15.28节)进行初始化,该variables称为常量variables 。
同样来自JLS§15.28 – 常量expression式:
编译时间常量expression式的
String
总是“interned” ,以共享唯一的实例,使用方法String#intern()
。
在您的第一个代码示例中, String
variables不是final
情况并非如此。 所以,它们不是一个编译时常量expression式。 这里的连接操作将被延迟到运行时,从而导致创build一个新的String
对象。 您可以通过比较两个代码的字节代码来validation。
第一个代码示例(非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
存储在两个单独的variables中,并使用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
所以它直接内联最后一个variables,以便在编译时创buildString string
,该string
在步骤0
由ldc
操作加载。 然后在步骤7
通过ldc
操作加载第二个string文字。 它不涉及在运行时创build任何新的String
对象。 string在编译时已经知道了,并且被实现了。
根据我的研究,所有final String
都是用Java编写的。 从其中一篇博客文章:
所以,如果你真的需要比较两个string使用==或!=请确保在进行比较之前调用String.intern()方法。 否则,总是比较喜欢使用String.equals(String)进行string比较。
所以这意味着如果你调用String.intern()
你可以使用==
运算符比较两个string。 但是在这里String.intern()
并不是必须的,因为在Java中final String
是在内部实现的。
你可以find更多的信息string比较使用==运算符和Javadoc String.intern()方法。
另请参阅此Stackoverflowpost了解更多信息。
如果你看看这个方法
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 ...
所以如果string不是最终的,编译器将不得不使用StringBuilder
来连接str1
和str2
String concat=str1+str2;
将编译到
String concat = new StringBuilder(str1).append(str2).toString();
这意味着concat
将在运行时创build,所以不会来自String池。
另外,如果string是最终的,那么编译器可以假定它们永远不会改变,所以不是使用StringBuilder
而是可以安全地连接它的值
String concat = str1 + str2;
可以改成
String concat = "str" + "ing";
并串联而成
String concat = "string";
这意味着concate
将成为sting文字,它将被放入string池中,然后在if
语句中与该池中的相同string文字进行比较。
堆栈和stringconts池的概念
让我们看看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"
被压入堆栈(从常量池)并直接存储到本地variablesconcat
。 您可以推断编译器在编译时正在创build(连接) 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文字符号创build时,它会自动调用intern()方法将该对象放入string池中,只要该对象不在池中。
为什么最后会有所作为?
编译器知道最终variables永远不会改变,当我们添加这些最终variables的输出到string池,因为str1 + str2
expression式输出也永远不会改变,所以最后编译器调用后,上述两个最终variables的输出中间方法。 在非最终variables编译器的情况下不要调用实习方法。