ref的字符串赋值。在此语句之后将对象字符串写入内存会发生吗?
为什么是这样?
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
我在模拟面试中被问到这个问题,但我仍然不明白。
回答
我认为答案在Java 语言规范中,它描述了第 15.7 点中的评估顺序。通常这是从左到右完成的。在您的情况下,评估将是
- 加载 str1(来自
str1.equals)->"one" - 加载 str2(来自
str1 = str2)->"two" - 将值存储到 str1 (from
str1 = str2) -> str1 现在是"two" - 完整表达式的值
str1 = str2是"two",但请记住,对于此语句,str1 已在第一步中加载。它不会再次加载。 "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
这不会做的是在存储到 str1 后再次加载它的值。
语言规范有更多关于边缘情况及其处理方式的示例(例如“如果函数的参数之一是引发异常的函数会发生什么情况)。
- 那很有意思。一般来说(不是在这种情况下,因为 `String` 是最终的)这两个方法调用可以运行完全不同的方法——如果 `str2` 是可分配给 `str1` 的子类的实例,并且子类覆盖了 `equals`。
回答
Java 规范15.12.4。方法调用的运行时评估管理这一点。它指出; “在运行时,方法调用需要五个步骤。首先,可以计算目标引用。其次,评估参数表达式。......”
所以我们首先计算目标,然后评估参数。
您示例中的目标表达式是str1,它在评估参数之前将评估为特定的字符串对象。所以我们调用Equals那个对象的方法。在这一点上,我们不再关心变量 str1,并且在评估参数时更改此变量指向的对象这一事实无关紧要。
回答
在我的第一个答案中,我说这些行中的每一行都可以分解为 3 个单独的语句:赋值、equals、println。然而,这产生了不同的字节码,因此,我的说法是错误的。
这是内部发生的事情,使用 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
这里值得注意的部分是aload_1in 偏移量 9 存储str1了堆栈上的初始值。偏移量 10、11 和 12 然后执行重新分配,但堆栈中的值保持不变,因此仍包含旧引用。
这就是 str1.equals(str1=str2) 为 false 的原因。
我想这是为什么应该避免内联分配的主要例子。