Java: pre-,postfix operator precedences
Asked Answered
S

5

18

I have two similar questions about operator precedences in Java.

First one:

int X = 10;
System.out.println(X++ * ++X * X++); //it prints 1440 

According to Oracle tutorial:
postfix (expr++, expr--) operators have higher precedence than prefix (++expr, --expr)

So, I suppose that evaluation order:

1) first postfix operator: X++ 
   1.a) X++ "replaced" by 10
   1.b) X incremented by one: 10+1=11
   At this step it should look like:  System.out.println(10 * ++X * X++), X = 11;

2) second POSTfix operator: X++ 
   2.a) X++ "replaced" by 11
   2.b) X incremented by one: 11+1=12
   At this step it should look like:  System.out.println(10 * ++X * 11), X = 12;

3) prefix operator: ++X
   3.a) X incremented by one: 12+1=13
   3.b) ++X "replaced" by 13
   At this step it should look like:  System.out.println(10 * 13 * 11), X = 13;

4) evaluating 10*13 = 130, 130*11 = 1430.

But Java seems to ignore PRE/POST ordering and puts them on one level. So the real order:

 X++ -> ++X -> X++ 

what causes the answer to be (10 * 12 * 12) = 1440.

Second one:

Example from this question:

    int a=1, b=2;             
    a = b + a++;

Part of accepted answer: "By the time of assignment, ++ has already incremented the value of a to 2 (because of precedence), so = overwrites that incremented value."

OK, let's look step-by-step:

 1) replacing "b" with 2
 2) replacing "a++" with 1
 3) incrementing "a" by 1 -> at this point a==2
 4) evaluating 2+1 = 3
 5) overwriting incremented value of "a" with 3

Seems everything is fine. But let's make a little change in that code (replace "=" with "+=")

    a += b + a++;

steps 1-4 should be same as above. so, after step 4 we have something like that:

    a += 3;

where a==2

And then I think: OK, a = 2+3, so a should be 5. BUT the answer is only 4

I'm really confused. I already spent couple of hours but still can't understand where I am wrong.

P.S. I know, that I shouldn't use this "style" in real applications. I just want to understand what is wrong in my thoughts.

Shanell answered 24/9, 2013 at 20:21 Comment(3)
No, expressions are evaluated left to right and * has higher precedence. So you need to swap 2 and 3 in your first analysis. And a+= is replaced with a=a+.Merari
@assylias: * has higher precedence than what? I think that part is misleading. But it is true that expressions are evaluated left to right and that that explains everything (see my answer).Siamese
Sorry lower, than pre/post increment - meaning that the operands of the multiplication are evaluated first, from left to right.Merari
S
17

The confusion stems from the fact that the operands are evaluated from left to right. This is done first, before any attention is paid to operator precedence/order of operations.

This behavior is specified in JLS 15.7.2. Evaluate Operands before Operation

So X++ * ++X * X++ is first evaluated as 10 * 12 * 12 which yields, as you saw, 1440.

To convince yourself of this, consider the following:

X = 10; System.out.println(X++ * ++X);
X = 10; System.out.println(++X * X++);

If X++ were done first, then ++X second, then multiplication, both should print the same number.

But they do not:

X = 10; System.out.println(X++ * ++X); // 120
X = 10; System.out.println(++X * X++); // 121

So how does this make sense? Well if we realize that operands are evaluated from left to right, then it makes perfect sense.

X = 10; System.out.println(X++ * ++X); // 120 (10 * 12)
X = 10; System.out.println(++X * X++); // 121 (11 * 11)

The first line looks like

X++       * ++X
10 (X=11) * (X=12) 12
10        * 12 = 120

and the second

++X       * X++
(X=11) 11 * 11 (X=12)
11        * 11 = 121

So why are prefix and postfix increment/decrement operators in the table?

It is true that increment and decrement must be performed before multiplication. But what that is saying is that:

Y = A * B++

// Should be interpreted as
Y = A * (B++)

// and not
Y = (A * B)++

Just as

Y = A + B * C

// Should be interpreted as
Y = A + (B * C)

// and not
Y = (A + B) * C

It remains that the order of the evaluation of the operands occurs left-to-right.


If you're still not conviced:

Consider the following program:

class Test
{
    public static int a(){ System.out.println("a"); return 2; }
    public static int b(){ System.out.println("b"); return 3; }
    public static int c(){ System.out.println("c"); return 4; }

    public static void main(String[] args)
    {
        System.out.println(a() + b() * c());
        // Lets make it even more explicit
        System.out.println(a() + (b() * c()));
    }
}

If the arguments were evaluated at the time they were needed, either b or c would come first, the other next, and lastly a. However, the program outputs:

a
b
c
14
a
b
c
14

Because, regardless of the order that they're needed and used in the equation, they're still evaluated left to right.

Helpful reading:

Siamese answered 24/9, 2013 at 21:4 Comment(8)
oh, I found document from Princeton and coded almost the same code. So, we always move from left to right. And ONLY if we have two operators share same operand - we look at the Precedence Order Table.Shanell
Not really. Think of it as a two step process. First we evaluate the operands, from left to right. Then we use the operators, as determined by the table, to compute the result.Siamese
This may help: JLS 15.7.2. Evaluate Operands before OperationSiamese
nope. "The Java programming language guarantees that every operand of an operator appears to be fully evaluated before any part of the operation itself is performed." int arr[] = null;int tmp = 5 * 3 / 0 / arr[0]; - I have / by zero exception, not NullPointerException. That is, we have operator * - we know both operands - we evaluate it, now we have 15 / 0 / arr[0]. Next operator / - we know both operands, evaluate them and boom - / by zero exception.Shanell
Nope what? And why are you quoting from the link, I read it, I pasted it.Siamese
I understood your comment as "first we evaluate ALL operands, then we use the operators". If you didn't mean that - my "nope" is wrong, I'm sorry.Shanell
One thing I still have trouble understanding is that if operands are evaluated from left to right, "does it matter" that x++ has a higher precedence than ++x. In other words, can I concoct an expression where the result would be different if the precedences were reversed?Rica
@Siamese - This is the best and amazing explanation I have ever read. Cleared the confusion. Thanks a lot!Bowyer
R
2

In short,

Precedence is like preparing the expression to be calculated by putting parentheses. Evaluation comes next from left to right considering each pair of parentheses as a separate operation.

For example,if i=2 then i+i++ becomes i+(i++) after precedence and evaluates to 2+2 = 4. However, i+++i becomes (i++)+i and evaluates to 2+3 = 5.

Same to i+(i=5) which evaluates to 2+5 = 7.

In fact the postfix operators do have higher precedence than prefix operators. For example, i+++++i becomes ((i++)++)+i after precedence which gives a compile error (the second postfix operator needs a variable to operate on but a value is found instead!). If both postfix and prefix operators had had equal precedence then the expression would have become (i++)+(++i) and evaluates to 2+4 = 6.

If you need more explanation you can compile and run the following code and inspect the examples printed at the output.

public class TestPrecedence {
    public static void main(String[] str) {
        int i = 0;
        System.out.println("\n");
        i = 2; System.out.println("i = " + i + "\n");
        i = 2; System.out.println("i++ = " + i++ + "\n");
        i = 2; System.out.println("++i = " + ++i + "\n");
        i = 2; System.out.println("i++i = (i++)i TestPrecedence.java:8: error: ')' expected\n"+
                                  "              i++i\n"+
                                  "                 ^\n");
        i = 2; System.out.println("i+-i = i+(-i) = " + (i+-i) + "\n");
        i = 2; System.out.println("++i++ = ++(i++) TestPrecedence.java:12: error: unexpected type\n"+
                                  "                ++i++ \n"+
                                  "                   ^\n"+
                                  "                required: variable\n"+
                                  "                found:    value\n");
        i = 2; System.out.println("i+++++i = ((i++)++)+i TestPrecedence.java:17: error: unexpected type\n"+
                                  "                      i+++++i\n"+
                                  "                       ^\n"+
                                  "                      required: variable\n"+
                                  "                      found:    value\n");
        i = 2; System.out.println("i++ + ++i = " + (i++ + ++i) + "\n");
        i = 2; System.out.println("i+(i=3) = " + (i+(i=3)) + " evaluates left to right\n");
        i = 2; System.out.println("i+i++ precedence yields i+(i++) evaluates to 2+2 = " + (i+i++) + "\n");
        i = 2; System.out.println("i+++i precedence yields (i++)+i evaluates to 2+3 = " + (i+++i) + "\n");
        System.out.println("\n");
    }
}
Recommendatory answered 6/8, 2016 at 22:31 Comment(0)
H
1

The reason why its 1440 is because

  1. x is set to 10 i.e 1st term is FIXED(overall equation 10 *)

  2. x is incremented by 1,x =11 now

  3. x is pre-incremented by 1 x=12 and second term FIXED now (overall equation 10 * 12 *)

  4. now x is set to 12 and third term FIXED(overall equation 10 * 12 *12)

  5. x is increment now but is in this case not used for evaluation,

in short a term is FIXED when variable occurs which in this case is X

2nd case: I'm not sure but I guess can be broken as,

  1. a=b+a
  2. a++

which I think is what is happening.

Harragan answered 24/9, 2013 at 20:41 Comment(2)
It's clear. But look at the table. prefix has lower precedence than postfix. So why is it evaluated before last postfix? That's the main problem for me.Shanell
this may be wrong,but just a guess,is because of left to right evaluation in java..say its 2 *3 *5 in java, i think it straightaway doesnt multiply it to 30 it does 2*3=6 1st and den multiplies it by 5Harragan
L
0

First step

1) first postfix operator: X++ 
   1.a) X++ "replaced" by 10
   1.b) X incremented by one: 10+1=11
   At this step it should look like:  System.out.println(10 * ++X * X++), X = 11;

2) prefix operator: ++X
   2.a) X "replaced" by 11
   2.b) X incremented by one: 11+1=12
   At this step it should look like:  System.out.println(10 * 12 * X++), X = 12;

3) second POSTfix operator: X++
   3.a) X "replaced" by 12
   3.b) X incremented by one: 12+1=13
   At this step it should look like:  System.out.println(10 * 12 * 12),  X = 13;

This prints 10 * 12 * 12 = 1440

This is the bytecode for the first step

 1. bipush 10
 2. istore_0
 3. getstatic java/lang/System/out Ljava/io/PrintStream;
 4. iload_0
 5. iinc 0 1
 6. iinc 0 1
 7. iload_0
 8. imul
 9. iload_0
10. iinc 0 1
11. imul
12. invokevirtual java/io/PrintStream/println(I)V
13. return

This do the following:

 1. Push 10 to the stack
 2. Pop 10 from the stack to X variable
 3. Push X variable value (10) to the stack
 5. Increment local variable X (11)
 6. Increment local variable X (12)
 7. Push X variable value (12) to the stack
 8. Multiply top and subtop (10*12) Now Top = 120 
 9. Push X variable value (12) to the stack
10. Increment local variable X (13)
11. Multiply top and subtop (120*12) Now Top = 1440

Notice than the last increment (10.) is done after the push of X to the stack

Locomotion answered 24/9, 2013 at 20:33 Comment(4)
Its now even worse, * doesn't have higher precedence -- see the link in the post.Siamese
@Siamese But expression are evaluated left to right, the prefix ++ is evaluated first, and then the second postfix ++. You are right about the precedence.Locomotion
@Siamese please evaluate my new answerLocomotion
@omainegra can you please keep the earlier explanation as well?Ceramics
C
0

For the second one ->

int a=1, b=2;             
a += b + a++;

Compiler will convert it to

 a = a + b + a++;

Then apply your logic and you will find the reason why a is 4.

Ceramics answered 24/9, 2013 at 21:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.