Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusing example about '&&' and '||' precedence

I was testing the precedence between && and || and I had an example that was confusing. In Java, && has higher operator precedence than the operator ||.

So if we have those 3 expressions:

//expr1  = true , expr2 = false; expr3 = false;

if(expr1 || expr2 && expr3);

It should be evaluated as:

if(expr1 || (expr2 && expr3));

So expr2 && expr3 should be evaluated before expr1. However, this example:

int a1 = 10;
int a2 = 20;
System.out.println(a1 < a2 || ++a1 > a2 && ++a2 < a1);
System.out.println(a1);
System.out.println(a2);

Outputs:

true
10
20

That proves that only a1 < a2 is evaluated. Can you explain why this is the case?

like image 812
AbdelRahmane Avatar asked Oct 16 '16 15:10

AbdelRahmane


2 Answers

The expression is short-circuiting. From the 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.

It sees that the rest of the condition doesn't matter because one of the operands of || is already true (10 < 20). If one of the operands is true, then no matter what the rest of the condition is, it's true.

You may use bitwise & and | to prevent this.


But, the ( expr2 && expr3 ) should be evaluated before expr1, no ?

No. You have to separate concepts of precedence and evaluation order.

  • Precedence: Dictates the parenthesization of an expression, not the order in which an expression is evaluated. For an example:

      true || false && false
    

    Is parenthesized to this because && has higher precedence than ||:

      true || (false && false)
    

    This does not mean that things in parentheses is evaluated first in Java's case. Precedence just clarifies what the operands of an operator are, in this case false and false, where as in this case:

      (true || false) && (false || false)
    

    The operands for && are true and false, not false and false.

  • Evaluation Order: Describes in what order each operand is evaluated and operator is applied and is sometimes language specific. This dictates how an expression is evaluated, unlike precedence.

In this case, your example:

true || false && false

As established earlier, becomes this due to precedence:

true || (false && false)

But Java, unlike C++, JavaScript, or a number of other languages has a strictly left to right evaluation. Per the 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.

So, when you have:

true || (false && false)

Java first evaluates the left operand which turns out to be true. Then the whole condition short circuits. The right operand of || in the parentheses is never evaluated at all. The same goes for your other example:

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

Take another more complex example:

false || false || true && (false || true) || false

Due to precedence, it becomes:

false || false || (true && (false || true)) || false

Then, evaluation begins, left to right:

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

Thus the whole expression evaluates to true. The whole expression was evaluated left to right the whole way through. Precedence only dictated the operands of an operator via parenthesization, not the evaluation order.

Further reading at Eric Lippert's explanatory article on precedence vs associativity vs evaluation order as mentioned by Daniel Pryden, it clears up a lot of the confusion.

The main takeaway is that precedence does not dictate in what an expression is evaluated. It only dictates how an expression should be parenthesized. Evaluation order, on the other hand, tells us exactly how an expression is evaluated, and in Java's case is always left to right.

like image 141
Andrew Li Avatar answered Oct 12 '22 07:10

Andrew Li


The first line prints true because of short-circuiting the || operator.

a1 < a2 is true and so the rest of the boolean expression doesn't need to be evaluated and true is returned.

expr2 should be evaluated before expr1

is incorrect, as operator precedence affects the structure of the expression, rather then the evaluation order (in most cases). If you were to re-order the expression so that it was (expr2 && expr3) || expr1, then yes, expr2 would be evaluated before expr1

like image 6
SamTebbs33 Avatar answered Oct 12 '22 07:10

SamTebbs33