public class Demo {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
System.out.println("s1 == s2 " + (s1 == s2));
String s5 = "Hel" + "lo";
String s6 = "He" + "llo";
System.out.println("s5 == s6 " + (s5 == s6));
String s7 = "He";
String s8 = "Hello";
s7 = s7.concat("llo");
System.out.println("s7 == s8 " + (s7 == s8));
String s10 = "He";
s10 = s10 + "llo";
System.out.println("s1 == s10 "+(s1 == s10));
}
}
在前面的代码中,s7 == s8和s1 == s10给出错误。有人可以解释一下,在s7 = s7.concat(“llo”)中实际发生了什么;并且s10 = s10 +“llo”;我理解==运算符检查引用和equal()检查对象的内容。但我需要知道为什么s7和s10参考变量位模式与s8和s1不同。如果这些事情与编译时生成的字符串和运行时生成的字符串有关,那么如何识别它是编译时还是运行时字符串?
答案 0 :(得分:11)
之所以发生这种情况,是因为Java在编译器中进行了优化。当它看到您将文字字符串"Hello"
分配给s1时,它对s2使用相同的“Hello”,因为所有Java String操作都是非破坏性的(例如,它们返回克隆而不是修改原始字符串) ,这样做是安全的。
同样适用于"Hel" + "lo"
vs "He" + "llo"
;它足够巧妙地弄清楚它们是一样的。
其他内容非常复杂,无法对其进行优化,因此您最终会得到单独的对象。
答案 1 :(得分:4)
==不检查位模式,它会比较对象的内存地址。只有同一个对象具有相同的内存地址。
答案 2 :(得分:4)
克林特的答案很好,但我会进一步扩展并在编译器层面进行解释。
如您所知,s1
和s2
将最终成为对同一字符串实例"Hello"
的引用。
对于s5
和s6
,编译器会看到常量表达式。也就是说,它看到两个常量(字符串文字)之间的操作。编译器知道如何添加字符串以及结果是什么。由于这些值在编译时立即得知,因此它会为您添加,从而产生文字字符串"Hello"
。因此,它具有与s1
和s2
相同的值,因此每个都将引用相同的实例。
s7
不能以同样的方式简化。 s7
最初以"He"
开头。这里的区别在于s7 = s7.concat("llo");
将s7
重新分配给函数调用的结果。这不能简化。就Java编译器而言,所有函数调用的结果在编译时都是未知的。由于它不知道结果值,因此不能简化并保持不变。结果调用返回"Hello"
字符串的新实例,该实例与编译时实例(s8
共享)的实例不同。
s10
也不能以同样的方式简化。 s10
最初以"He"
开头。然后重新分配s10 = s10 + "llo";
这无法简化。你可能会问为什么?好s10
是一个非最终变量表达式。从技术上讲,编译器不知道它的值,因为它不是常量。如果s10
被声明为final String
,那么这可以是常量折叠(当分配给不同的变量时)。
请考虑此版本的测试代码:
public static void main(String[] args)
{
String s1 = "Hello";
String s2 = "Hello";
System.out.println("1: s1 == s2 " + (s1 == s2)); // 1
String s3 = "Hel" + "lo";
String s4 = "Hel".concat("lo");
System.out.println("2: s1 == s3 " + (s1 == s3)); // 2
System.out.println("3: s1 == s4 " + (s1 == s4)); // 3
String he = "He";
String s5 = he + "llo";
String s6 = he.concat("llo");
System.out.println("4: s1 == s5 " + (s1 == s5)); // 4
System.out.println("5: s1 == s6 " + (s1 == s6)); // 5
final String fhe = "He";
String s7 = fhe + "llo";
String s8 = fhe.concat("llo");
System.out.println("6: s1 == s7 " + (s1 == s7)); // 6
System.out.println("7: s1 == s8 " + (s1 == s8)); // 7
}
你能算出哪一行是真的吗?
true,true,false,false,false,true,false
您可能想知道为什么3和7不是真的。简而言之,Java编译器未编程
足够智能识别concat()调用,因此被视为常规函数调用。
答案 3 :(得分:0)
equals运算符测试引用是否相同(即指向同一对象),而不是引用的值是否相同。如果您需要测试一个字符串是否等于另一个字符串,则应使用内置的.equals方法。这将进行对象值比较。 e.g。
final String expectedValue = "Foo";
final String actualValue = "F" + "oo";
if (expectedValue.equals(actualValue)) {
// This will trigger where == would not
}
另外为了安全起见,如果你比较两个字符串而一个是常量,通常最好在常量上调用等号,即
String myValue = getMyValue;
boolean theSame = "Some value".equals(myValue);
而不是
String myValue = getMyValue;
boolean theSame = myValue.equals("Some Value");
原因是第二种形式存在空指针异常的风险,可以通过在保证存在的常量字符串上调用equals()来避免这种情况。
答案 4 :(得分:0)
你不能对字符串对象做任何假设。
VM可以努力确保不会同时存在包含完全相同的char数组的两个字符串对象,而其他VM将允许重复。
答案 5 :(得分:0)
==
运算符仅检查两个对象是否具有相同的地址(指针)。仅适用于不是引用的原始类型(如int,char等),它会比较该值。
您需要使用s1.equals(s2)
之类的内容来比较两个字符串的内容。
答案 6 :(得分:0)
在您提供的示例中,这是正在发生的事情:
String s7 = “He”; //s7 is an object referencing a part of memory holding “He”
String s8 = “Hello”; //s8 is an object referencing a part of memory holding “Hello”
s7 = s7.concat(“llo”); //s7.concat(“llo”) created a new object in memory that contains “Hello” and s7 now references this now object
(s7==s8) //checks if the equality of the object reference and this is false since they reference different memory addresses.
(s7.equals(s8)) //this will compare s7 and s8 however way the String class compares two String objects.