ref 的字符串赋值。在此语句之后将对象字符串写入内存会发生吗?

Man*_*ann 26 java string

为什么是这样?

String str1 = "one";
String str2 = "two";
System.out.println(str1.equals(str1 = str2)); // false, doesn't assignment of ref. to string object memory location happens after???
System.out.println(str1.equals(str1 = str2)); // true, same statement
Run Code Online (Sandbox Code Playgroud)

我在模拟面试中被问到这个问题,但我仍然不明白。

Bor*_*ris 30

我认为答案在Java 语言规范中,它描述了第 15.7 点中的评估顺序。通常这是从左到右完成的。在您的情况下,评估将是

  1. 加载 str1(来自str1.equals)->"one"
  2. 加载 str2(来自str1 = str2)->"two"
  3. 将值存储到 str1 (from str1 = str2) -> str1 现在是"two"
  4. 完整表达式的值str1 = str2"two",但请记住,对于此语句,str1 已在第一步中加载。它不会再次加载。
  5. "one"使用参数"two"-> false调用 equals on (已加载,见上文)

您还可以查看反编译的 Java 代码(javap -c ClassName在您的类路径上运行),它也会向您显示以下顺序:

       0: ldc           #7                  // String one
       2: astore_1
       3: ldc           #9                  // String two
       5: astore_2
    [...]
       9: aload_1           // <-- loads the value of str1
      10: aload_2           // <-- loads the value of str2
      11: dup
      12: astore_1          // <-- stores to str1
      13: invokevirtual #17 // <-- invokes equals
Run Code Online (Sandbox Code Playgroud)

这不会做的是在存储到 str1 后再次加载它的值。

语言规范有更多关于边缘情况及其处理方式的示例(例如“如果函数的参数之一是引发异常的函数会发生什么情况)。

  • 那很有意思。一般来说(不是在这种情况下,因为 `String` 是最终的)这两个方法调用可以运行完全不同的方法——如果 `str2` 是可分配给 `str1` 的子类的实例,并且子类覆盖了 `equals`。 (3认同)

Tae*_*myr 9

Java 规范15.12.4。方法调用的运行时评估管理这一点。它指出; “在运行时,方法调用需要五个步骤。首先,可以计算目标引用。其次,评估参数表达式。......”

所以我们首先计算目标,然后评估参数。

您示例中的目标表达式是str1,它在评估参数之前将评估为特定的字符串对象。所以我们调用Equals那个对象的方法。在这一点上,我们不再关心变量 str1,并且在评估参数时更改此变量指向的对象这一事实无关紧要。


f1s*_*1sh 8

在我的第一个答案中,我说这些行中的每一行都可以分解为 3 个单独的语句:赋值、equalsprintln。然而,这产生了不同的字节码,因此,我的说法是错误的。

这是内部发生的事情,使用 OP 代码段的第 3 行生成的字节码进行演示:

   0: ldc           #2                  // String one
   2: astore_1
   3: ldc           #3                  // String two
   5: astore_2
   6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
   9: aload_1
  10: aload_2
  11: dup
  12: astore_1
  13: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  16: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V
Run Code Online (Sandbox Code Playgroud)

这里值得注意的部分是aload_1in 偏移量 9 存储str1了堆栈上的初始值。偏移量 10、11 和 12 然后执行重新分配,但堆栈中的值保持不变,因此仍包含旧引用。

这就是 str1.equals(str1=str2) 为 false 的原因。

我想这是为什么应该避免内联分配的主要例子。


sin*_*air 1

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        ....
 }
Run Code Online (Sandbox Code Playgroud)

简短回答:在第一个语句中this保留“one”,因此对 的“旧版本”进行操作str1,即使对象同时发生变化。