关于“&&”和“||”的混淆示例优先权
Confusing example about '&&' and '||' precedence
我正在测试 &&
和 ||
之间的优先级,我有一个令人困惑的例子。在 Java 中,&&
的运算符优先级高于运算符 ||
.
所以如果我们有这 3 个表达式:
//expr1 = true , expr2 = false; expr3 = false;
if(expr1 || expr2 && expr3);
应评价为:
if(expr1 || (expr2 && expr3));
所以 expr2 && expr3
应该在 expr1
之前计算。然而,这个例子:
int a1 = 10;
int a2 = 20;
System.out.println(a1 < a2 || ++a1 > a2 && ++a2 < a1);
System.out.println(a1);
System.out.println(a2);
输出:
true
10
20
证明只计算了a1 < a2
。
你能解释一下为什么会这样吗?
第一行打印 true
因为短路了 ||
运算符。
a1 < a2
为真,因此不需要计算布尔表达式的其余部分并返回 true
。
expr2 should be evaluated before expr1
是不正确的,因为运算符优先级会影响表达式的结构,而不是计算顺序(在大多数情况下)。如果您要重新排序表达式使其成为 (expr2 && expr3) || expr1
,那么是的,expr2
将在 expr1
之前计算
表达式为short-circuiting。来自 link:
when the first argument of the AND function evaluates to false, the overall value must be false; and when the first argument of the OR function evaluates to true, the overall value must be true.
它看到条件的其余部分无关紧要,因为 ||
的操作数之一已经为真(10 < 20)。如果其中一个操作数为真,那么无论其余条件是什么,它都为真。
您可以使用按位 &
和 |
来防止这种情况。
But, the ( expr2 && expr3 ) should be evaluated before expr1, no ?
没有。您必须将 precedence 和 evaluation order.
的概念分开
优先顺序:指示表达式的括号,而不是表达式求值的顺序。例如:
true || false && false
被括号括起来是因为 &&
的优先级高于 ||
:
true || (false && false)
这并不意味着在 Java 的情况下首先评估括号中的内容。优先级只是阐明了运算符的操作数是什么,在本例中为 false
和 false
,而在 中,本例中为 :
(true || false) && (false || false)
&&
的操作数是true
和false
,而不是false
和false
。
求值顺序:描述每个操作数的求值顺序和应用运算符的顺序,有时是特定于语言的。与优先级不同,这决定了表达式的计算方式。
在这种情况下,您的示例:
true || false && false
如前所述,由于优先级而变为:
true || (false && false)
但 Java 与 C++ 不同,JavaScript 或许多其他语言具有严格的从左到右的计算。根据 Java Language Specification:
15.7. Evaluation Order
The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.
15.7.1. Evaluate Left-Hand Operand First
The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.
所以,当你有:
true || (false && false)
Java 首先计算左操作数,结果是true
。然后整个状态短路。括号中 ||
的右操作数根本不会被计算。您的另一个示例也是如此:
a1 < a2 || (++a1 > a2 && ++a2 < a1)
^^^^^^^^^^^^^^^^^^^^^^^^
Step 0, precedence and parenthesization
a1 < a2 || (++a1 > a2 && ++a2 < a1)
^^^^^^^
Step 1, left operand evaluated, variables resolved to values 10 and 20, condition is true
true || (++a1 > a2 && ++a2 < a1)
^^^^
Step 2, short circuits, left operand is not evaluated
再举一个更复杂的例子:
false || false || true && (false || true) || false
由于优先级,它变成:
false || false || (true && (false || true)) || false
然后,评估开始,从左到右:
false || false || (true && (false || true)) || false
^^^^^^^^^^^^^^
Step 1, false || false, does not short circuit, right operand is evaluated, is false
false || (true && (false || true)) || false
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Step 2, false || (true && (false || true)), does not short circuit, right operand is evaluated
Step 2A, (true && (false || true)), does not short circuit, right operand is evaluated
Step 2B, (false || true), does not short circuit, right operand is evaluated, is true
Step 2C, (true && true), does not short circuit, right operand is evaluated, is true
Step 2D, false || true, does not short circuit, right operand is evaluated, is true
true || false
^^^^
Step 3, true || false short circuits, right operand is not evaluated, is true
因此整个表达式的计算结果为 true
。整个表达式从左到右进行评估。优先级仅通过括号指定运算符的操作数,而不是评估顺序。
进一步阅读 Eric Lippert's explanatory article on precedence vs associativity vs evaluation order as mentioned by ,它消除了很多困惑。
主要的收获是 precedence 没有规定表达式的计算内容。它只规定了表达式应该如何括起来。另一方面,求值顺序告诉我们表达式是如何求值的,在Java的情况下总是向右。
即使您使用
中的显式括号
if (expr1 || (expr2 && expr3))
expr1
首先计算,因为运算符的操作数是从左到右计算的。由于 ||
是短路运算符,因此仅当 expr1
为 false
.
时才会评估第二个操作数(在您的情况下为 (expr2 && expr3)
)
当您删除括号时,运算符优先级仅在 expr1
为 false 时才起作用。在这种情况下,&&
操作数将在 ||
运算符之前计算,其值将是 ||
运算符的第二个操作数。
优先级:
boolean result = a || b && c
为了根据优先规则获得正确的值,编译器必须在逻辑上将其计算为:
boolean x = b && c
boolean result = a || x
这说明了您的观点,即必须先计算 b && c
,然后才能计算 result
。但是,在布尔代数中,可以编写其结果 不依赖于所有操作数 的表达式。这一事实被用来启用编译器优化,称为 short circuiting.
短路:
对于任何布尔表达式:
a || b
如果 a
的计算结果为 true
,则结果不依赖于 b
。当 a
是 true
时,b
的计算结果是真还是假并不重要。因此,编译器执行:
a || b && c
好像是这样写的:
boolean result = a;
if (!a) result = b && c;
请注意,以这种方式执行,仍然遵守优先规则。表达式未计算为 (a || b) && c
。由于在 a
为真的情况下表达式的总体结果不依赖于 (b && c)
,因此根本不会计算 b && c
。
这是一件好事。当一个操作数的正确性取决于另一个操作数的正确性时,短路允许您编写正确的程序。例如:
if (myString == null || !myString.isEmpty() && myString != "break") return;
如果没有短路评估,布尔表达式可能会抛出 NullPointerException
。然而,由于短路评估,这个表达式,如所写,永远不会抛出 NullPointerException
.
短路也可以用作性能优化。如果评估一个操作数非常昂贵,那么先评估另一个操作数可以节省评估其值不影响整个表达式最终结果的操作数所需的执行时间。
我正在测试 &&
和 ||
之间的优先级,我有一个令人困惑的例子。在 Java 中,&&
的运算符优先级高于运算符 ||
.
所以如果我们有这 3 个表达式:
//expr1 = true , expr2 = false; expr3 = false;
if(expr1 || expr2 && expr3);
应评价为:
if(expr1 || (expr2 && expr3));
所以 expr2 && expr3
应该在 expr1
之前计算。然而,这个例子:
int a1 = 10;
int a2 = 20;
System.out.println(a1 < a2 || ++a1 > a2 && ++a2 < a1);
System.out.println(a1);
System.out.println(a2);
输出:
true
10
20
证明只计算了a1 < a2
。
你能解释一下为什么会这样吗?
第一行打印 true
因为短路了 ||
运算符。
a1 < a2
为真,因此不需要计算布尔表达式的其余部分并返回 true
。
expr2 should be evaluated before expr1
是不正确的,因为运算符优先级会影响表达式的结构,而不是计算顺序(在大多数情况下)。如果您要重新排序表达式使其成为 (expr2 && expr3) || expr1
,那么是的,expr2
将在 expr1
表达式为short-circuiting。来自 link:
when the first argument of the AND function evaluates to false, the overall value must be false; and when the first argument of the OR function evaluates to true, the overall value must be true.
它看到条件的其余部分无关紧要,因为 ||
的操作数之一已经为真(10 < 20)。如果其中一个操作数为真,那么无论其余条件是什么,它都为真。
您可以使用按位 &
和 |
来防止这种情况。
But, the ( expr2 && expr3 ) should be evaluated before expr1, no ?
没有。您必须将 precedence 和 evaluation order.
的概念分开优先顺序:指示表达式的括号,而不是表达式求值的顺序。例如:
true || false && false
被括号括起来是因为
&&
的优先级高于||
:true || (false && false)
这并不意味着在 Java 的情况下首先评估括号中的内容。优先级只是阐明了运算符的操作数是什么,在本例中为
false
和false
,而在 中,本例中为 :(true || false) && (false || false)
&&
的操作数是true
和false
,而不是false
和false
。求值顺序:描述每个操作数的求值顺序和应用运算符的顺序,有时是特定于语言的。与优先级不同,这决定了表达式的计算方式。
在这种情况下,您的示例:
true || false && false
如前所述,由于优先级而变为:
true || (false && false)
但 Java 与 C++ 不同,JavaScript 或许多其他语言具有严格的从左到右的计算。根据 Java Language Specification:
15.7. Evaluation Order
The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.
15.7.1. Evaluate Left-Hand Operand First
The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.
所以,当你有:
true || (false && false)
Java 首先计算左操作数,结果是true
。然后整个状态短路。括号中 ||
的右操作数根本不会被计算。您的另一个示例也是如此:
a1 < a2 || (++a1 > a2 && ++a2 < a1)
^^^^^^^^^^^^^^^^^^^^^^^^
Step 0, precedence and parenthesization
a1 < a2 || (++a1 > a2 && ++a2 < a1)
^^^^^^^
Step 1, left operand evaluated, variables resolved to values 10 and 20, condition is true
true || (++a1 > a2 && ++a2 < a1)
^^^^
Step 2, short circuits, left operand is not evaluated
再举一个更复杂的例子:
false || false || true && (false || true) || false
由于优先级,它变成:
false || false || (true && (false || true)) || false
然后,评估开始,从左到右:
false || false || (true && (false || true)) || false
^^^^^^^^^^^^^^
Step 1, false || false, does not short circuit, right operand is evaluated, is false
false || (true && (false || true)) || false
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Step 2, false || (true && (false || true)), does not short circuit, right operand is evaluated
Step 2A, (true && (false || true)), does not short circuit, right operand is evaluated
Step 2B, (false || true), does not short circuit, right operand is evaluated, is true
Step 2C, (true && true), does not short circuit, right operand is evaluated, is true
Step 2D, false || true, does not short circuit, right operand is evaluated, is true
true || false
^^^^
Step 3, true || false short circuits, right operand is not evaluated, is true
因此整个表达式的计算结果为 true
。整个表达式从左到右进行评估。优先级仅通过括号指定运算符的操作数,而不是评估顺序。
进一步阅读 Eric Lippert's explanatory article on precedence vs associativity vs evaluation order as mentioned by
主要的收获是 precedence 没有规定表达式的计算内容。它只规定了表达式应该如何括起来。另一方面,求值顺序告诉我们表达式是如何求值的,在Java的情况下总是向右。
即使您使用
中的显式括号if (expr1 || (expr2 && expr3))
expr1
首先计算,因为运算符的操作数是从左到右计算的。由于 ||
是短路运算符,因此仅当 expr1
为 false
.
(expr2 && expr3)
)
当您删除括号时,运算符优先级仅在 expr1
为 false 时才起作用。在这种情况下,&&
操作数将在 ||
运算符之前计算,其值将是 ||
运算符的第二个操作数。
优先级:
boolean result = a || b && c
为了根据优先规则获得正确的值,编译器必须在逻辑上将其计算为:
boolean x = b && c
boolean result = a || x
这说明了您的观点,即必须先计算 b && c
,然后才能计算 result
。但是,在布尔代数中,可以编写其结果 不依赖于所有操作数 的表达式。这一事实被用来启用编译器优化,称为 short circuiting.
短路:
对于任何布尔表达式:
a || b
如果 a
的计算结果为 true
,则结果不依赖于 b
。当 a
是 true
时,b
的计算结果是真还是假并不重要。因此,编译器执行:
a || b && c
好像是这样写的:
boolean result = a;
if (!a) result = b && c;
请注意,以这种方式执行,仍然遵守优先规则。表达式未计算为 (a || b) && c
。由于在 a
为真的情况下表达式的总体结果不依赖于 (b && c)
,因此根本不会计算 b && c
。
这是一件好事。当一个操作数的正确性取决于另一个操作数的正确性时,短路允许您编写正确的程序。例如:
if (myString == null || !myString.isEmpty() && myString != "break") return;
如果没有短路评估,布尔表达式可能会抛出 NullPointerException
。然而,由于短路评估,这个表达式,如所写,永远不会抛出 NullPointerException
.
短路也可以用作性能优化。如果评估一个操作数非常昂贵,那么先评估另一个操作数可以节省评估其值不影响整个表达式最终结果的操作数所需的执行时间。