4
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 不同。如果这些东西与编译时生成的字符串和运行时生成的字符串有关,那么我如何识别它是编译时字符串还是运行时字符串?

4

7 回答 7

11

发生这种情况的原因是因为 Java 在编译器中进行了优化。当它看到您将文字字符串分配"Hello"给 s1 时,它对 s2 使用相同的“Hello”,因为所有 Java 字符串操作都是非破坏性的(例如,它们返回克隆而不是修改原始字符串),所以这是安全的要做的事。

"Hel" + "lo"vs 也一样"He" + "llo";很聪明地发现它们是同一个东西。

其他的足够复杂,无法优化它们,因此您最终会得到单独的对象。

于 2010-09-26T19:34:55.090 回答
4

== 不检查位模式,它会比较对象的内存地址。只有同一个对象具有相同的内存地址。

于 2010-09-26T19:36:47.767 回答
4

克林特的回答很好,但我会进一步扩展并在编译器级别进行解释。

如您所知,s1最终s2将成为对同一字符串实例的引用,"Hello".

对于s5and s6,编译器会看到常量表达式。也就是说,它看到两个常量(字符串文字)之间的操作。编译器知道如何添加字符串以及结果是什么。由于这些值在编译时立即已知,因此它会为您添加,从而产生文字 string "Hello"。因此,它具有相同的值s1s2因此每个都将引用相同的实例。

s7不能以同样的方式简化。 s7最初从"He"当然开始。这里的区别在于s7 = s7.concat("llo");重新分配s7给函数调用的结果。这不能按原样简化。就 Java 编译器而言,所有函数调用的结果在编译时是未知的。由于它不知道结果值,因此无法简化并保持原样。结果调用返回一个"Hello"字符串的新实例,该实例与编译时实例(s8共享)不同。

s10也不能以同样的方式简化。 s10最初从"He"当然开始。然后是重新分配s10 = s10 + "llo"; 这无法简化。为什么你可能会问?Wells10是一个非最终变量表达式。从技术上讲,编译器不知道它的值,因为它不是常量。如果s10被声明为 a 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
}

你能找出哪些线是真的吗?

真,真,假,假,假,真,假
你可能想知道为什么 3 和 7 不是真的。简短的回答,Java 编译器的编程
不够聪明,无法识别 concat() 调用,因此被视为常规函数调用。

于 2010-09-26T20:11:43.413 回答
0

等于运算符测试引用是否相同(即指向同一个对象),而不是引用的值是否相同。如果您需要测试一个字符串是否等于另一个字符串,您应该使用内置的 .equals 方法。这将进行对象值比较。例如

final String expectedValue = "Foo";
final String actualValue = "F" + "oo";
if (expectedValue.equals(actualValue)) {
  // This will trigger where == would not
}

此外,为了安全起见,如果您确实比较了两个字符串并且其中一个是常量,通常最好在常量上调用 equals,即

String myValue = getMyValue;
boolean theSame = "Some value".equals(myValue);

而不是

String myValue = getMyValue;
boolean theSame = myValue.equals("Some Value");

原因是第二种形式存在空指针异常的风险,可以通过在保证存在的常量字符串上调用 equals() 来避免这种情况。

于 2010-09-26T19:38:07.833 回答
0

您不能对字符串对象做出任何假设。

虚拟机可以努力确保没有两个包含完全相同的字符数组的字符串对象同时存在,而其他虚拟机则允许重复。

于 2010-09-26T19:38:23.493 回答
0

==运算符只检查两个对象是否具有相同的地址(指针)。只有对于不是引用的原始类型(如 int、char 等),它才会比较值。

您需要使用类似的东西s1.equals(s2)来比较两个字符串的内容。

于 2010-09-26T19:39:31.800 回答
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. 
于 2010-09-26T19:59:44.640 回答