当运算符优先级说不应该时,为啥短路评估会起作用?
Posted
技术标签:
【中文标题】当运算符优先级说不应该时,为啥短路评估会起作用?【英文标题】:Why does short-circuit evaluation work when operator precedence says it shouldn't?当运算符优先级说不应该时,为什么短路评估会起作用? 【发布时间】:2018-03-12 09:05:08 【问题描述】:在javascript 和Java 中,等于运算符(==
或===
)的优先级高于或运算符(||
)。然而两种语言(JS、Java)都支持在if
语句中进行短路:
当我们有if(true || anything())
时,anything()
不会被评估。
您还可以使用以下表达式:true || foo == getValue())
- 例如在 console.log(...);
等输出语句或赋值中。
现在,根据运算符优先级,不应该发生短路,因为在优先级方面===
= ==
> ||
。 (换句话说,应该首先进行比较,应该调用getValue()
,因为相等性检查的优先级高于OR比较。)但确实如此。 getValue()
未被调用(可以通过将输出语句放入其主体中轻松检查)。
为什么(当运算符优先级表明不应该短路时,短路会起作用)? 还是我把事情搞糊涂了?
【问题讨论】:
相关:What are the rules for evaluation order in Java? 你的问题有矛盾。你说“......应该首先进行比较...... getValue() 应该被调用......但它确实如此。”所以你的问题的一部分暗示getValue()
没有被调用,但是这个语句说是 is 被调用。是哪个?
@Chloe:他的 “但确实如此” 指的是 “不应该发生短路”,这与他对 "getValue()
应该被称为..." 并且他观察到 "getValue()
不被称为"。
【参考方案1】:
还是我把事情搞糊涂了?
你是。我认为将优先级视为分组比排序要简单得多。它会影响评估的顺序,但只是因为它会改变分组。
我不确定 Javascript,但在 Java 中,操作数总是按从左到右的顺序计算。 ==
的优先级高于 ||
的事实只是意味着
true || foo == getValue()
被评估为
true || (foo == getValue())
而不是
(true || foo) == getValue()
如果您只是以这种方式考虑优先级,然后考虑评估始终是从左到右的(例如,||
的左操作数总是在右操作数之前评估)那么一切都很简单 - 并且getValue()
由于短路而从未被评估。
要从等式中消除短路,请考虑以下示例:
A + B * C
... 其中A
、B
和C
可以只是变量,也可以是其他表达式,例如方法调用。在 Java 中,这保证被评估为:
A
(并记住它以备后用)
评估B
评估C
将B
和C
的评估结果相乘
将计算A
的结果与乘法的结果相加
请注意,即使*
的优先级高于+
,A
仍会在B
或C
之前进行评估。如果您想要考虑排序方面的优先级,请注意乘法仍然在加法之前发生 - 但它仍然满足从左到右的评估顺序。
【讨论】:
虽然我完全短路了,但我仍然不明白为什么它符合规则。本质上,我们有operand1 operatorA operand2 operatorB operand3
与operatorA
的优先级低于operatorB
的优先级,因此运算符优先级应该导致o2 oB o3
首先被评估。相反(由于短路)解释器看到operatorA
是一个或,因此只评估operand1
。计算结果为true
,因此没有其他内容得到评估。尽管运算符优先级说明它应该是相反的; o2 oB o3
应该首先运行。
@Christian:“所以运算符优先级应该导致首先评估 o2 oB o3” - 不,它确实没有。运算符优先级意味着它被分组,使得 operatorA 的操作数是“operand1”和“计算 operatorB 的结果”。但是operand1 仍然首先被评估。这就是我所说的更多关于分组的意思。
事实上,从左到右的规则只适用于相同顺序的操作数:1 + 2 + 3 => 3 + 3 => 6
而1 + 2 * 3 => 1 + 6 => 7
,不是3 * 3 => 9
,对吧?如果是这样,我们又回到了第一格,因为 ||
的顺序比 ==
低,但首先计算(即我们得到 9,而不是 6)...
@Christian:不,那是你想的关联性。请参阅docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.7:“在评估右侧操作数的任何部分之前,二元运算符的左侧操作数似乎已被完全评估。”因此,||
的左侧操作数在评估右侧操作数之前进行评估。
@Christian:您可能会发现我不久前写的关于该主题的这篇博文很有用:codeblog.jonskeet.uk/2015/04/21/precedence-ordering-or-grouping【参考方案2】:
根据语言规范,https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.24
在运行时,首先计算左侧操作数表达式;如果结果为布尔类型,则进行拆箱转换(第 5.1.8 节)。
如果结果值为真,则条件或表达式的值为真,并且不计算右侧操作数表达式。
所以如果你有a || b==c
,它不会被解释为(a || b) == c
,因为||
的优先级较低,正如你在教程中发现的那样。相反,它被解释为a || (b==c)
。现在因为a
是||
的左侧,所以首先计算它。
【讨论】:
我发现a || (b==c)
并没有那么令人困惑,因为括号在两种语言中的优先级更高,对于 Java,请参阅 docs.oracle.com/javaee/6/tutorial/doc/bnaik.html【参考方案3】:
在这种情况下没有运算符优先级。您所质疑的就像在f(callback)
语句中的callback
函数甚至在f
之前被评估。这不可能发生。
另一方面,在 JS 中,||
是少数几个可以在展示中看到懒惰的地方之一。 ==
操作数(就像在全功能语言中一样认为它是一个中缀函数)有两个参数,左边的一个首先被评估。如果它解析为true
,则第二个参数甚至不会被评估。
【讨论】:
以上是关于当运算符优先级说不应该时,为啥短路评估会起作用?的主要内容,如果未能解决你的问题,请参考以下文章