我有以下代码。我理解java字符串不变性和字符串常量池的概念。我不明白为什么在下面的程序中'name1 == name2‘结果为假,而'name2 == name3’结果为真。字符串变量name1、name2和name3如何放在字符串常量池中?
public class Test {
public static void main(String[] args) {
final String firstName = "John";
String lastName = "Smith";
String name1 = firstName + lastName;
String name2 = firstName + "Smith";
String name3 = "John" + "Smith";
System.out.println(name1 == name2);
System.out.println(name2 == name3);
}
}
Output:
false
true
发布于 2020-12-13 23:31:49
答案相当简单:作为一种捷径,java将某些概念视为所谓的“编译时间常数”(CTC)。这个想法是在编译级完全内联一个变量(这是非常特别的;通常javac
基本上只是使用非常简单和容易理解的转换将你的java源文件转化成一个类文件,并且fancypants优化在运行时在hotspot期间发生)。
例如,如果您这样做:
保存到UserOfBatch.java
class BatchOConstants {
public static final int HELLO = 5;
}
public class UserOfBatch {
public static void main(String[] args) {
System.out.println(BatchOConstants.HELLO);
}
}
在命令行上运行:
> javac UserOfBatch.java
> java UserOfBatch
5
> javap -c UserOfBatch # javap prints bytecode
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iconst_5
4: invokevirtual #15 // Method java/io/PrintStream.println:(I)V
7: return
看看上面的第三行。iconst_5
。那5个?它是硬编码的!!
不再保留对BatchOConstants
的引用。让我们来测试一下:
在命令行上:
> rm BatchOConstants.class
> java UserOfBatch
5
哇。代码运行,尽管它缺少应该提供5的类文件,从而证明它是由编译器本身而不是运行时“硬编码”的。
另一种“观察”任何值的CTC的方法是annoparams。注释参数必须通过硬编码到类文件中,所以不能传递表达式。给定:
public @interface Foo {
long value();
我不能写:@Foo(System.currentTimeMillis())
,因为System.cTM
显然不是一个编译时间常数。但是我可以编写@Foo(SomeClass.SOME_STATIC_FINAL_LONG_FIELD)
,假设分配给S_S_F_L_F的值是一个编译时间常数。如果不是,@Foo(...)
代码将无法编译。如果是,它将进行编译: CTC-ness现在确定您的代码是否编译。
对于编译器何时被允许解释为“编译时间常量”并进行内联狂欢,有特定的规则。例如,null
永远不是内联常量。过于简单化,但是:
对于字段,字段必须是静态的和最终的,并且具有原语或字符串类型,在现场初始化(在同一时间内,而不是在静态块中的后面),使用常量表达式,即不为
不幸的是,局域的CTC-ness更难直接观察。不过,您编写的代码是间接观察它的好方法。你已经用你的指纹证明了firstName
是CTC而lastName
不是。
但我们可以观察到一些精选的东西。因此,让我们将您的代码进行编译,并将其放在javap中,以见证结果。Via Jonas Konrad's online javap tool,让我们分析一下:
public Main() {
final String a = "hello";
String b = "world";
String c = a + "!";
String d = b + "!";
System.out.println(c == "hello!");
System.out.println(d == "world!");
}
相关部分:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: ldc #7 // String hello
6: astore_1
start local 1 // java.lang.String a
7: ldc #9 // String world
9: astore_2
start local 2 // java.lang.String b
10: ldc #11 // String hello!
12: astore_3
start local 3 // java.lang.String c
13: aload_2
14: invokedynamic #13, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
19: astore 4
start local 4 // java.lang.String d
注意“start local 2”(即c;javap从0开始计数)显示了如何将hello!
作为一个完整的常量加载,但“start local 3”(即d)显示了加载2个常量并调用makeConcat将它们绑定在一起。
发布于 2020-12-13 23:41:32
在编译代码后运行javap -c Test
,
你会看到的
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String Smith
2: astore_2
3: aload_2
4: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
9: astore_3
10: ldc #4 // String JohnSmith
12: astore 4
14: ldc #4 // String JohnSmith
16: astore 5
18: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_3
22: aload 4
24: if_acmpne 31
27: iconst_1
28: goto 32
31: iconst_0
32: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
35: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload 4
40: aload 5
42: if_acmpne 49
45: iconst_1
46: goto 50
49: iconst_0
50: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
53: return
}
如你所见
4: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
在public static void main(java.lang.String[]);
中,这是运行firstname + lastname
时实际编译的字节代码。因此,生成的字符串将不会具有与常量池中的"JohnSmith“相同的哈希码。
where as on
10: ldc #4 // String JohnSmith
和
14: ldc #4 // String JohnSmith
这是编译器在执行firstname + "Smith"
和"John" + "Smith"
时生成的字节码,这意味着两者实际上都是从常量池中读取的。
这就是为什么当你使用==
比较name1和name2时,它会返回false
。因为name2
和name3
引用常量池中的相同字符串。如果与==
比较,则返回true
。这就是为什么将2字符串与==
进行比较并不是一个好主意。进行字符串比较时请使用String.equals()
。
因为两者都
发布于 2020-12-13 23:30:45
让我们用final
查看字节码
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #7 // String Smith
2: astore_1
3: aload_1
4: invokedynamic #9, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
9: astore_2
10: ldc #13 // String JohnSmith
12: astore_3
13: ldc #13 // String JohnSmith
15: astore 4
17: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_2
21: aload_3
22: if_acmpne 29
25: iconst_1
26: goto 30
29: iconst_0
30: invokevirtual #21 // Method java/io/PrintStream.println:(Z)V
33: getstatic #15 // 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 #21 // Method java/io/PrintStream.println:(Z)V
50: return
}
和不带final
的字节码
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #7 // String John
2: astore_1
3: ldc #9 // String Smith
5: astore_2
6: aload_1
7: aload_2
8: invokedynamic #11, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
13: astore_3
14: aload_1
15: invokedynamic #15, 0 // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
20: astore 4
22: ldc #18 // String JohnSmith
24: astore 5
26: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_3
30: aload 4
32: if_acmpne 39
35: iconst_1
36: goto 40
39: iconst_0
40: invokevirtual #26 // Method java/io/PrintStream.println:(Z)V
43: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload 4
48: aload 5
50: if_acmpne 57
53: iconst_1
54: goto 58
57: iconst_0
58: invokevirtual #26 // Method java/io/PrintStream.println:(Z)V
61: return
}
正如您所看到的,对于final
,Java将+
的左右两边都识别为常量,因此它在编译时将连接替换为常量字符串"JohnSmith"
。对makeConcatWithConstants
(连接字符串)的惟一调用是针对firstName + lastName
的,因为lastName
不是最终的。
在第二个示例中,有两个对makeConcatWithConstants
的调用,一个用于firstName + lastName
,另一个用于firstName + "Smith"
,因为Java不会将firstName
识别为常量。
这就是在您的示例中name1 == name2
是false
的原因:name2
是字符串池中的常量"JohnSmith"
,而name1
是在运行时动态计算的。但是,name2
和name3
都是字符串池中的常量,这就是name2 == name3
为true
的原因。
https://stackoverflow.com/questions/65276561
复制相似问题