记住,
a += x
实际上意味着
a = a + x
。需要理解的关键点是
加法从左到右进行计算--也就是说,在
x
之前会先计算
a + x
中的
a
。
那么我们来看看
b = (a += (a += a))
的含义。首先我们使用规则
a += x
等同于
a = a + x
,然后按照正确的顺序仔细评估表达式。
b = (a = a + (a = a + a))
,因为a += x
意味着a = a + x
b = (a = 1 + (a = a + a))
,因为a
当前是1
。记住我们在计算左边的项a
之前计算右边的项(a = a + a)
b = (a = 1 + (a = 1 + a))
,因为a
仍然是1
b = (a = 1 + (a = 1 + 1))
,因为a
仍然是1
b = (a = 1 + (a = 2))
,因为1 + 1
是2
b = (a = 1 + 2)
,因为a
现在是2
b = (a = 3)
,因为1 + 2
是3
b = 3
,因为a
现在是3
这样我们就得到了a = 3
和b = 3
,如上所述。
现在让我们用另一个表达式b = (a += a) + (a += a)
来尝试一下:
b = (a = a + a) + (a = a + a)
b = (a = 1 + 1) + (a = a + a)
,请记住我们在评估右侧项之前评估左侧项
b = (a = 2) + (a = a + a)
b = 2 + (a = a + a)
,此时a
为2。开始评估右侧项
b = 2 + (a = 2 + 2)
b = 2 + (a = 4)
b = 2 + 4
,此时a
为4
b = 6
这样我们就得到了a = 4
和b = 6
。可以通过在Java / JavaScript中打印出a
和b
来验证这一点(两者在这里具有相同的行为)。
将这些表达式视为解析树可能有助于理解。当我们评估a + (b + c)
时,左侧的a
在右侧的(b + c)
之前被评估。这在树结构中得到编码:
+
/ \
a +
/ \
b c
请注意,我们不再有任何括号——运算顺序被编码到树结构中。当我们评估树中的节点时,按照
固定的顺序(即对于
+
从左到右)处理节点的子级。例如,当我们处理根节点
+
时,我们在评估右子树
(b + c)
之前评估左子树
a
,无论右子树是否被括在括号中(因为括号甚至不存在于解析树中)。
因此,Java/JavaScript并不总是首先评估“最嵌套的括号”,与你可能在算术规则中学到的规则相反。
请参阅
Java Language Specification:
15.7.评估顺序
Java编程语言保证操作数按特定的
评估顺序从左到右出现。
...
15.7.1.先评估左手操作数
二元运算符的左手操作数在任何右手操作数的部分被评估之前似乎已经完全被评估。
如果运算符是复合赋值运算符(§15.26.2),则左手操作数的评估包括记住左手操作数表示的变量并获取和保存该变量的值以用于隐含的二元操作。
类似于您的问题的更多示例可以在JLS的链接部分中找到。
Example 15.7.1-1. Left-Hand Operand Is Evaluated First
In the following program, the * operator has a left-hand operand that
contains an assignment to a variable and a right-hand operand that
contains a reference to the same variable. The value produced by the
reference will reflect the fact that the assignment occurred first.
class Test1 {
public static void main(String[] args) {
int i = 2;
int j = (i=3) * i;
System.out.println(j);
}
}
This program produces the output:
9
It is not permitted for evaluation of the * operator to produce 6
instead of 9.