JAVA / OPERATORS
Operator precedence and grouping with parentheses
Predict how Java groups a mixed expression from precedence and associativity, and use parentheses to force the grouping you intend.
What you will learn
- Rank operators: unary, * / %, + -, << >>, < >, == !=, &, ^, |, &&, ||, ?:, =
- Break same-rank ties with associativity: a - b - c means (a - b) - c
- Explain why 1 << 2 + 3 is 32, and why "n=" + 1 + 2 prints n=12
- Add parentheses to fix grouping bugs the compiler cannot catch for you
Understanding Operator precedence and grouping with parentheses
Java does not read 2 + 3 * 4 left to right. Before any code runs, the compiler groups the tokens into a tree using each operator's precedence rank, which the language fixes once and for all: unary operators and casts bind tightest, then * / %, then + -, then the shifts << >> >>>, then relational < <= > >= instanceof, then equality == !=, then & then ^ then |, then && then ||, then the conditional ?:, and finally assignment. A tighter operator claims its operands first, so 3 * 4 becomes a single operand of +. Spacing has no effect: 2+3 * 4 groups exactly the same way.
When two operators of equal rank compete for the same operand, associativity breaks the tie. Every binary operator in Java except assignment is left-associative, which is why c - b - a means (c - b) - a, and why "n=" + 1 + 2 appends the digits one at a time instead of adding them. Assignment and the conditional group right to left, so x = y = 5 means x = (y = 5). Grouping is not timing, though: Java always finishes the left operand of a binary operator before starting the right one, so f() + g() * h() still calls f, g, h in that order even though the multiplication is grouped first.
Parentheses insert a node of your own into that tree and outrank everything else, which makes them the cheapest fix available. javac resolves the grouping at compile time, so (a + b) * c produces the same bytecode as splitting the expression into a temporary variable. Reach for them whenever an expression mixes families that rarely meet: shifts with arithmetic, & with ==, ?: with +. Java's type rules catch a few precedence mistakes for you, since flags & 1 == 0 tries to combine an int with a boolean, but any mis-grouped expression that still type-checks compiles happily and quietly computes the wrong value.
public class PrecedenceDemo {
public static void main(String[] args) {
int a = 2, b = 3, c = 4;
System.out.println("a + b * c = " + (a + b * c));
System.out.println("(a + b) * c = " + ((a + b) * c));
System.out.println("1 << a + b = " + (1 << a + b));
System.out.println("(1 << a) + b = " + ((1 << a) + b));
System.out.println("c - b - a = " + (c - b - a));
System.out.println("c - (b - a) = " + (c - (b - a)));
String loose = "n=" + a + b;
String tight = "n=" + (a + b);
System.out.println(loose + " vs " + tight);
}
}Precedence and associativity decide how a Java expression is grouped into a tree, and parentheses let you dictate that grouping instead of trusting the table.
Worked examples
&& outranks ||
Shows that the two logical connectives sit at different ranks, so the same three operands give different answers depending on grouping.
public class LogicalPrecedence {
public static void main(String[] args) {
boolean p = true, q = false, r = false;
System.out.println(p || q && r);
System.out.println((p || q) && r);
System.out.println(!q == p);
}
}Example explained
Line 1p || q && r groups as p || (q && r) because && ranks above ||, so the expression reduces to true || false.
Line 2Parenthesising (p || q) turns it into the left operand of &&, and true && false is false: same operators, opposite result.
Line 3!q == p is (!q) == p, since unary ! binds tighter than every binary operator and so cannot apply to the comparison's result.
The bottom of the table
Demonstrates that assignment and ?: are the loosest operators, and that assignment groups right to left.
public class LowRankOperators {
public static void main(String[] args) {
int a = 1, b = 2, c;
c = a + b * 2;
System.out.println(c);
System.out.println(true ? "hit" : "mi" + "ss");
System.out.println((true ? "hit" : "mi") + "ss");
int x, y;
x = y = c;
System.out.println(x + " " + y);
}
}Example explained
Line 1c = a + b * 2 computes b * 2 and then a + 4 before storing anything, because = ranks below all arithmetic.
Line 2In true ? "hit" : "mi" + "ss" the + binds tighter than ?:, so the false branch is the single value "miss" and the printed result is still hit.
Line 3Wrapping the conditional makes it an operand of +, so the chosen branch "hit" is concatenated with "ss".
Line 4x = y = c groups as x = (y = c): the inner assignment runs first and its value, 5, is what lands in x.
Important notes
Precedence never reorders side effects: in first() + second() * third() the three calls happen left to right; only the grouping changes.
Casts and unary operators bind tighter than any binary operator, so (int) x / y casts x and then divides, rather than truncating the quotient.
Common mistakes
Assuming << binds like multiplication: 1 << n + 1 shifts by n + 1 and yields 2^(n+1) where you wanted 2^n + 1, and it compiles without a warning.
Porting the C idiom if (flags & 1 == 0): Java groups it as flags & (1 == 0) and rejects it with "bad operand types for binary operator '&'"; the fix is (flags & 1) == 0.
Writing System.out.println("sum: " + a + b) for a total: left-associative + with a String on the left concatenates both numbers, printing sum: 23 for a = 2, b = 3.
Try it yourself
Change, predict, then run
In a browser editor set int n = 5, write down your prediction, then print n << 1 + 1 and (n << 1) + 1 on separate lines. Add "n=" + n + 1 and "n=" + (n + 1) and say which precedence or associativity rule produced each of the four results.
Open the Java workspaceCheck your understanding
Each call in sum(1) + sum(2) * sum(3) prints its argument as it runs. What do Java's precedence rules actually determine here?
- Both grouping and call order: sum(2) and sum(3) run before sum(1), because * outranks +.
- Only grouping: the product is one operand of +, but the three calls still run in source order 1, 2, 3.
- Nothing about order; the JVM may evaluate the three calls in whichever order it finds faster.
- The calls run right to left, since the tightest-binding operator sits on the right.
Show answer
Precedence and associativity only build the expression tree, grouping sum(2) * sum(3) as the right operand of +. Operand evaluation order is a separate, fixed rule: the left operand of an operator is fully evaluated first, so sum(1) prints, then sum(2), then sum(3). Option 0 is tempting because the multiplication must complete before the addition can, but that is about when operators apply, not when their operands are evaluated.