有四件事情在发生:
(你清楚地知道这一点,但对于潜伏者)==
测试,看看变量是指向同一个String
对象,而不是等效的字符串.所以,即使x
是"foo"
和y
也"foo"
,x == y
可能是真的还是假的,这取决于是否x
和y
指向同一个String
对象或不同的.这就是为什么我们使用equals
而不是==
比较字符串的等价性.以下所有内容只是为了解释为什么==
有时候是真的,这不是==
用来比较字符串的建议.:-)
在同一个类中,等效字符串常量(编译器知道的字符串是根据JLS中的各种规则的常量)被编译器引用相同的字符串(它也在类的"常量池"中列出它们).这就是为什么a == b
是真的.
当类被加载,它的每一个字符串常量自动扣留 - JVM的字符串池检查等效字符串,如果找到一个,即String
使用对象(如果没有,新String
的恒新对象添加到池).因此即使x
是在类中初始化的字符串常量Foo
并且y
是在类中初始化的字符串常量Bar
,它们也将是==
彼此.
上述第2点和第3点部分由JLS§3.10.5涵盖.(关于类常量池的一点是一个实现细节,因此之前是JVM规范的链接; JLS只是谈到了实习.)
如果编译器处理常量值,则编译器会进行字符串连接
String d = "dev" + "ender";
被编译为
String d = "devender";
并且"devender"
是一个字符串常量,编译器和JVM将上面的点2和3应用于.例如,没有StringBuilder
使用,连接发生在编译时,而不是运行时.这在JLS§15.28 - 常量表达式中有所介绍.所以a == d
是真正出于同样的原因a == b
是正确的:它们指向同一个常量字符串,所以编译器确保他们指的在类的常量池相同的字符串.
当任何操作数不是常量时,编译器不能这样做,所以它不能用:
String e = c + "ender";
...即使代码分析可以很容易地表明它的价值c
肯定会是"dev"
,因此e
肯定会"devender"
.该规范仅使编译器使用常量值进行串联.因此,由于编译器无法执行此操作,因此它会输出StringBuilder
您引用的代码,并且该工作在运行时完成,从而创建一个新String
对象.该字符串不会自动实现,因此e
最终会引用不同的String
对象a
,因此a == e
也是错误的.
需要注意的是为维诺德说,如果声明c
为final
:
final String c = "dev";
然后它将是一个常量变量(是的,它们真的被称为),因此§15.28将适用并且编译器将转向
String e = c + "ender";
成
String e = "devender";
而且a == e
也是如此.
重申一下:没有一个意味着我们应该==
用来比较字符串的等价性.:-)这equals
就是为了什么.
有四件事情在发生:
(你清楚地知道这一点,但对于潜伏者)==
测试,看看变量是指向同一个String
对象,而不是等效的字符串.所以,即使x
是"foo"
和y
也"foo"
,x == y
可能是真的还是假的,这取决于是否x
和y
指向同一个String
对象或不同的.这就是为什么我们使用equals
而不是==
比较字符串的等价性.以下所有内容只是为了解释为什么==
有时候是真的,这不是==
用来比较字符串的建议.:-)
在同一个类中,等效字符串常量(编译器知道的字符串是根据JLS中的各种规则的常量)被编译器引用相同的字符串(它也在类的"常量池"中列出它们).这就是为什么a == b
是真的.
当类被加载,它的每一个字符串常量自动扣留 - JVM的字符串池检查等效字符串,如果找到一个,即String
使用对象(如果没有,新String
的恒新对象添加到池).因此即使x
是在类中初始化的字符串常量Foo
并且y
是在类中初始化的字符串常量Bar
,它们也将是==
彼此.
上述第2点和第3点部分由JLS§3.10.5涵盖.(关于类常量池的一点是一个实现细节,因此之前是JVM规范的链接; JLS只是谈到了实习.)
如果编译器处理常量值,则编译器会进行字符串连接
String d = "dev" + "ender";
被编译为
String d = "devender";
并且"devender"
是一个字符串常量,编译器和JVM将上面的点2和3应用于.例如,没有StringBuilder
使用,连接发生在编译时,而不是运行时.这在JLS§15.28 - 常量表达式中有所介绍.所以a == d
是真正出于同样的原因a == b
是正确的:它们指向同一个常量字符串,所以编译器确保他们指的在类的常量池相同的字符串.
当任何操作数不是常量时,编译器不能这样做,所以它不能用:
String e = c + "ender";
...即使代码分析可以很容易地表明它的价值c
肯定会是"dev"
,因此e
肯定会"devender"
.该规范仅使编译器使用常量值进行串联.因此,由于编译器无法执行此操作,因此它会输出StringBuilder
您引用的代码,并且该工作在运行时完成,从而创建一个新String
对象.该字符串不会自动实现,因此e
最终会引用不同的String
对象a
,因此a == e
也是错误的.
需要注意的是为维诺德说,如果声明c
为final
:
final String c = "dev";
然后它将是一个常量变量(是的,它们真的被称为),因此§15.28将适用并且编译器将转向
String e = c + "ender";
成
String e = "devender";
而且a == e
也是如此.
重申一下:没有一个意味着我们应该==
用来比较字符串的等价性.:-)这equals
就是为了什么.
编译器在幕后进行了大量优化.
String d = "dev" + "ender";
在这里,编译器将取代"dev" + "ender"
与"devender"
被编译的程序时.如果要添加2个文字(这适用于基元和字符串),编译器会执行此优化.
Java代码:
String d = "dev" + "ender";
字节代码:
0: ldc #16 // String devender
来到一个特例:
final String c = "dev"; // mark this as final String e = c + "ender";
使c
final成为String的编译时常量.编译器将意识到值c
不能改变,因此在编译时将c
用值"dev" 替换所有出现的值,因此e
将在编译期间自行解决.
正如你在内部所说,最后的连接是针对类似的东西完成的
String e = new StringBuilder().append(c).append("ender").toString();
实施toString()
的StringBuilder
创建一个新字符串.这是实施.
public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
仅当两个字符串相同时才使用==
而不是.equals()
返回true
来比较字符串.在这种情况下,它们不相同,因为第二个字符串是作为类型的新对象创建的String
.
其他连接由编译器直接执行,因此不会创建新的String.
"dev" + "ender"
是一个编译时可计算的常量表达式:两个参数都是字符串文字.因此,表达是"devender"
.
同样不能说c + "ender"
:某些情况(某些代码在不同的线程上运行)可能导致c
设置为不同的值.符合条件c
是为了final
避免这种可能性,在这种情况下e
也会引用相同的对象a
.
所以a
,b
和d
所有引用同一个对象.
d
和之间的区别在于e
,当您连接字符串文字时,连接是在编译时执行的.Java编译器"dev" + "ender"
以与"devender"
表达式相同的方式处理表达式,在编译时生成相同的文字.由于所有String
文字得到实习,d
,这是一个结果"dev" + "ender"
,也结束了引用相同的对象a
和b
的"devender"
.
表达式for e
,c + "ender"
是在运行时计算的.即使它产生相同的字符串,编译器也不会使用这个事实.这就是产生不同String
对象的原因,导致比较失败==
.
字符串d ="dev"+"ender"; 常数+常数,'d'仍然是常数(同一个),所以(a == d)为真;
字符串e = c +"ender"; 变量+常量,结果'e'是一个变量,它将在内部使用StringBuilder,并创建一个新的引用.