Execution order of f1() + f2()*f3() expression and operator precedence in JLS
Asked Answered
T

4

11

Given an expression f1() + f2()*f3() with 3 method calls, java evaluates (operands of) addition operation first:

int result = f1() + f2()*f3();

f1 working
f2 working
f3 working

I (wrongly) expected f2() to be called first, then f3(), and finally f1(). Because multiplication shall be evaluated before addition.

So, I don't understand JLS here - what am I missing?

15.7.3. Evaluation Respects Parentheses and Precedence

The Java programming language respects the order of evaluation indicated explicitly by parentheses and implicitly by operator precedence.

How exactly operator precedence is respected in this example?

JLS mentions some exceptions in 15.7.5. Evaluation Order for Other Expressions (method invocation expressions (§15.12.4),method reference expressions (§15.13.3)), but I cannot apply any of those exceptions to my example.

I understand that JLS guarantees that operands of a binary operation are evaluated left-to-right. But in my example in order to understand method invocation sequence it is necessary to understand which operation (and both its operands respectively!) is considered first. Am I wrong here - why?

More examples:

int result = f1() + f2()*f3() + f4() + f5()*f6() + (f7()+f8());

proudly produces:

f1 working
f2 working
f3 working
f4 working
f5 working
f6 working
f7 working
f8 working

Which is 100% left-to-right, totally regardless of operator precedence.

int result = f1() + f2() + f3()*f4();

produces:

f1 working
f2 working
f3 working
f4 working

Here the compiler had two options:

  1. f1()+f2() - addition first
  2. f3()*f4() - multiplication first

But the "evaluate-left-to-right rule" works here as a charm!

This link implies that in such examples method calls are always evaluated left-to-right regardless of any parenthesis and associativity rules.

Torrez answered 14/8, 2019 at 20:14 Comment(3)
It evaluates the functions in sequential order. It applies the mathematical operators according to PEMDAS.Alexei
Step one, figure out method the values in the expression - methods are called LTR. Then figure out the value of the expression - evaluation respects the order of precedence of operators.Gerhardt
You should read 15.7.1 and 15.7.2. 15.7.3 just means that f1()+f1() won't be replaced with 2*f1(), 3.0*x/4.0 won't be replace with (3.0/4.0)*x, etc...Wikiup
T
10

You're missing the text immediately under 15.7:

The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.

The values of the operands of an expression are evaluated before the expression itself. In your specific case, The outermost expression is f1() + [f2() * f3()]. We must first evaluate f1(), then we must evaluate f2() * f3(). Within that, f2() is evaluated first, then f3(), then the multiplication (which returns the value for the operand of the +).

Turgescent answered 14/8, 2019 at 20:18 Comment(3)
Could you explain how to reconcile §15.7.3 with §15.7?Tungstite
Operand evaluation order is clear. The problem is with (binary) operations order (addition operation or multiplication operation shall be first - then operand-evaluation rule makes everything clear).Torrez
@CodeComplete Your "P.S." still misses the point; you may be confused between compile-time code analysis and runtime evaluation. It might be helpful to draw your expression as a tree and then traverse it in depth-first order (where the leaves are the f?() expressions).Turgescent
L
6

JLS §15.17.2. Evaluate Operands before Operation:

The Java programming language guarantees that every operand of an operator (except the conditional operators &&, ||, and ? :) appears to be fully evaluated before any part of
the operation itself is performed.

Thus, f1(), f2() and f3() are evaluated first (left-to-right). Then, operator precedence is applied.

After all, you could observe the execution order in the bytecode, which in my case is:

[...]
INVOKESTATIC Main.f1 ()I
INVOKESTATIC Main.f2 ()I
INVOKESTATIC Main.f3 ()I
IMUL
IADD

Here is how the Expression Tree would look like:

   +
  / \
 /   \
f1    *
     / \
    /   \
   f2   f3

Leaves are evaluated first and the operator precedence is encoded in the tree itself.

Laurilaurianne answered 14/8, 2019 at 20:21 Comment(0)
G
1

Because multiplication shall be evaluated before addition.

As you seem to mean it, that's not a reasonable description of the precedence of multiplication over addition, nor of the effect of section 15.7.3. Precedence says that your expression

f1() + f2()*f3()

is evaluated the same as

f1() + (f2() * f3())

, as opposed to the same as

(f1() + f2()) * f3()

Precedence does not speak to which operand of the + operator is evaluated first, regardless of the nature of the operands. It speaks instead to what each operand is. This does impact order of evaluation, in that it follows from precedence rules that the result of the multiplication in your original expression must be computed before the result of the addition, but that does not speak directly to the question of when the values of two factors are evaluated relative to each other or to other sub-expressions.

Different rules than the one you quoted are responsible for the order in which the two operands of a + or * operator are evaluated -- always the left operand first and the right operand second.

How exactly operator precedence is respected in this example?

The example's output does not convey information about whether precedence -- understood correctly -- is respected. The same output would be observed for both of the two parenthesized alternatives above, and precedence is about which one of those two is equivalent to the original expression.

In an order-of-operations sense, precedence says (only) that the right-hand operand of the addition is f2()*f3(), which means that product must be computed before the overall sum can be. JLS 15.7.3 isn't saying anything more than or different from that. That that sub-expression is a multiplication has no bearing on its order of evaluation relative to the other operand of the addition operation.

I understand that JLS guarantees that operands of a binary operation are evaluated left-to-right. But in my example in order to understand method invocation sequence it is necessary to understand which operation (and both its operands respectively!) is considered first. Am I wrong here - why?

Yes, you are wrong, and JLS notwithstanding, your examples provide very good evidence for that.

The problem seems to be the "and both its operands respectively" bit. Your idea seems to be roughly that Java should look through the expression, find all the multiplication operations and fully evaluate them, including their operands, and only afterward go back and do the same for the addition operations. But that is not required to observe precedence, and it is not consistent with the left-to-right evaluation order of addition operations, and it would make Java code much harder for humans to write or read.

Let's consider your longer example:

int result = f1() + f2()*f3() + f4() + f5()*f6() + (f7()+f8());

Evaluation proceeds as follows:

  • f1() is evaluated first, because it is the left operand of the first operation, and the the operation's operands must be evaluated left-to-right
  • f2() is evaluated next, because it is part of the right operand to the addition (whose turn it is to be evaluated), and specifically the left operand of the multiplication
  • f3() is evaluated next, because the multiplication sub-expression to which it belongs is being evaluated, and it is the right operand of that multiplication. Precedence affects the order of operations here by establishing that in fact it is the product that is being evaluated. If that were not so, then instead, the result of f2() would be added to the result of f1() at this point, before f3() was computed.
  • the product of f2() and f3() is computed, in accordance with the precedence of multiplication over addition.
  • The sum of (previously evaluated) f1() and f2()*f3() is computed. This arises from a combination of precedence of * over + already discussed, and the left-to-right associativity of +, which establishes that the sum is the left operand of the next addition operation, and therefore must be evaluated before that operation's right operand.
  • f4() is evaluated, because it is the right operand of the addition operation being evaluated at that point.
  • The second sum is computed. This is again because of the left-to-right associativity of +, and the left-to-right evaluation order of the next addition operation.
  • f5() then f6() then their product is evaluated. This is completely analogous to the case of f2()*f3(), and thus is another exercise of operator precedence.
  • the result of f5()*f6(), as the right-hand operand of an addition operation, is added to the left-hand operand. This is again analogous to previous steps.
  • f7() is evaluated.
  • Because of the parentheses, f8() is evaluated next, and then its sum with the previously-determined result of evaluating f7(), and then that result is added to the preceding result to complete the computation. Without the parentheses, the order would be different: the result of f7() would be added to the preceding result, then f8() computed, then that result added.
Gytle answered 14/8, 2019 at 20:23 Comment(5)
I see what you're saying (and largely agree with it), but the OP's spec quote really is about the fact that multiplication is evaluated before addition, so you can't really say that this is "not a reasonable description of" it.Supposition
@ruakh, the comment you're referring to does not responding to the spec quote, but rather to the OP's own (mis-)characterization of what precedence implies in the case of their code. The fact is that precedence and parentheses don't affect evaluation order in the sense that interests the OP. They do it in the sense I've described here.Gytle
Sure, but "multiplication shall be evaluated before addition" is exactly (part of) what the spec is saying here. It's only the OP's follow-on interpretation, that the right-hand-side of the + should be evaluated before the left-hand-side (because the latter has a *), that's mistaken.Supposition
I disagree, @ruakh, and that's the whole point. The spec is not saying that, though its wording is susceptible to such a misinterpretation.Gytle
I have substantially expanded this answer to (I hope) clarify these points.Gytle
T
0

The most useful tip in my opinion was found in this discussion

enter image description here

JLS is not so clear on that - section 15.7 about evaluation speaks only about operands of a single operation, leaving unclear real-life complex cases with many operations combined.

Torrez answered 14/8, 2019 at 20:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.