在Java中的string连接的情况下“==”
String a = "devender"; String b = "devender"; String c = "dev"; String d = "dev" + "ender"; String e = c + "ender"; System.out.println(a == b); //case 1: o/p true System.out.println(a == d); //case 2: o/p true System.out.println(a == e); //case 3: o/p false
a和b都指向string常量池中相同的string。 在情况1中如此
String d = "dev" + "ender";
应该在内部使用像 –
String d = new StringBuilder().append("dev").append("ender").toString();
一个 & d如何指向相同的引用&而不是& e ?
四件事情正在发生:
-
(你清楚地知道这一点,但对于潜伏者)
==
testing,看看variables指向相同的String
对象 ,而不是等价的string。 所以,即使x
是"foo"
,y
也是"foo"
,x == y
可能是true或false,取决于x
和y
是指向相同的String
对象还是不同的对象。 这就是为什么我们使用equals
而不是==
来比较string的等价性。 以下所有内容仅仅是为了解释为什么==
有时候是正确的,不build议使用==
来比较string。 🙂 -
等价的string常量(编译器知道的string是根据JLS中的各种规则的常量)被编译器引用到同一个string中(也列在类的“常量池”中 )。 这就是为什么
a == b
是真的。 -
当这个类被加载时,它的每个string常量都会被自动实现 – JVM的string池被检查一个等价的string,如果find了,就使用这个
String
对象(如果没有的话,新的常量的新的String
对象被添加到游泳池)。 所以,即使x
是一个在Foo
类中初始化的string常量,而y
是一个在Bar
类中初始化的string常量,它们也将互为==
。以上第2点和第3点部分由JLS§3.10.5覆盖。 (关于类常量池的一点是一个实现细节,因此与之前的JVM规范有联系,JLS只是说实习。)
-
如果编译器处理常量值,则编译器会执行string连接
String d = "dev" + "ender";
被编译到
String d = "devender";
而
"devender"
是一个string常量,编译器和JVM将上面的2和3点应用到。 例如,没有使用StringBuilder
,连接发生在编译时 ,而不是运行时。 这包含在JLS§15.28 – 常量expression式中 。 所以,a == d
的原因与a == b
是同一个原因:它们引用相同的常量string,所以编译器确保它们引用类的常量池中的相同string。当任何操作数不是一个常量时,编译器不能这样做,所以它不能这样做:
String e = c + "ender";
即使代码分析可以很容易地显示出
c
的价值肯定会是"dev"
,因此e
一定是"devender"
。 规范只是让编译器用常数值连接,具体来说。 因此,由于编译器不能这样做,它会输出您引用的StringBuilder
代码,并且该工作在运行时完成,从而创build一个新的String
对象。 该string不会自动被拦截,所以e
最后引用了一个不同的String
对象,所以a == e
是false。请注意, 正如维诺德所说 ,如果你宣布
c
为final
:final String c = "dev";
那么这将是一个常量variables (是的,他们真的被称为),所以§15.28将适用,编译器将转向
String e = c + "ender";
成
String e = "devender";
和
a == e
也是如此。
只是重申:没有哪一个意味着我们应该使用==
比较string的等价性。 :-)这就是equals
目的。
编译器在底层做了很多优化。
String d = "dev" + "ender";
这里编译器会在程序编译时用"devender"
代替"dev" + "ender"
"devender"
。 如果要添加2个文字(这适用于基元和string),编译器会执行此优化。
Java代码:
String d = "dev" + "ender";
字节码:
0: ldc #16 // String devender
来一个特例:
final String c = "dev"; // mark this as final String e = c + "ender";
使c
最终将使string编译时间不变 。 编译器会认识到c
的值不能改变,因此在编译时会用c
的值“dev”replacec
所有出现,因此在编译时本身就会解决。
d
和e
之间的区别在于,当连接string文字时 ,连接在编译时执行。 Java编译器以与"devender"
expression式相同的方式处理"dev" + "ender"
"devender"
expression式,在编译时产生相同的文字。 由于所有的String
都被禁止了,因此"dev" + "ender"
的结果d
也会以a
和b
的"devender"
引用同一个对象。
e
的expression式,即c + "ender"
,在运行时被评估。 即使它产生相同的string,编译器也不会使用这个事实。 这就是为什么生成不同的String
对象,导致==
比较失败。
正如你在内部所说的,最后的连接是通过类似的方式完成的
String e = new StringBuilder().append(c).append("ender").toString();
StringBuilder
的toString()
的实现会创build一个新的String 。 这是实施。
public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
使用==
比较string而不是.equals()
仅在两个string相同时才返回true
。 在这种情况下, 它们不一样,因为第二个string被创build为一个 String
types的新对象。
其他连接由编译器直接执行,所以不会创build新的string。
"dev" + "ender"
是一个编译时可评估的常量expression式:这两个参数都是string文字。 因此这个expression是"devender"
。
对c + "ender"
不能这样说的:某些情况下(例如某个代码运行在不同的线程上)可能导致c
被设置为一个不同的值。 合格的c
作为final
消除这种可能性,在这种情况下, e
也将同一个对象称为a
。
所以a
, b
和d
都是指同一个对象。
String d =“dev”+“ender”; 常数+常数,“d”仍然是一个常数(同一个),所以(a == d)是真的;
String e = c +“ender”; variables+常量,结果'e'是一个variables,它将在内部使用StringBuilder,并创build一个新的引用。
请记住,Java拥有一个在程序中find的所有string文字的池,用于匹配等目的,所以上面的任何不同的string连接将导致相同的对象,相同的string文字。 你可以看看这个有用的文章更多。
另一方面,一个String
对象和一个文本(case c + "ender"
)的连接将导致在运行时创build一个StringBuilder
对象,与在池中find的文字不同。