1.3 Operators, Precedence, Evaluation Order, and Short-Circuit Evaluation
Key Takeaways
Java operators adhere to a strict precedence hierarchy that parentheses override; postfix increment/decrement binds tighter than prefix unary operators, which bind tighter than binary arithmetic, relational, logical, and assignment operators.
Java guarantees strict left-to-right evaluation of operands before the operator executes; all subexpressions, method arguments, and array indices are evaluated in lexical sequence regardless of operator precedence.
Logical short-circuit operators (&&, ||) skip evaluation of the right-hand operand when the left-hand operand definitively determines the outcome, whereas bitwise/logical operators (&, |, ^) unconditionally evaluate both operands.
The ternary operator (? :) associates from right to left when nested and applies binary numeric promotion across its result branches, which can lead to unexpected unboxing NullPointerExceptions.
Compound assignment operators (+=, -=, *=, /=) embed an implicit narrowing cast back to the target variable's type, masking potential precision loss and integer overflow.
Operators, Precedence, Evaluation Order, and Short-Circuit Evaluation
Mastering operator interactions requires separating two distinct concepts: operator precedence (which dictates how expressions are grouped into an abstract syntax tree) and operand evaluation order (which dictates the runtime sequence in which expressions are evaluated). The Java Language Specification guarantees deterministic, left-to-right operand evaluation.
Operator Precedence and Associativity Hierarchy
When an unparenthesized expression contains multiple operators, operator precedence determines the grouping of operands with operators. Associativity resolves expressions containing operators of equal precedence:
| Precedence Level | Operators | Description | Associativity |
|---|---|---|---|
| 1 (Highest) | expr++, expr-- | Postfix increment / decrement | Left to right |
| 2 | ++expr, --expr, +, -, ~, ! | Prefix increment/decrement, unary plus/minus, bitwise complement, logical NOT | Right to left |
| 3 | (type), new | Cast, object creation | Right to left |
| 4 | *, /, % | Multiplicative (multiplication, division, remainder) | Left to right |
| 5 | +, -, + (string concat) | Additive (addition, subtraction, string concatenation) | Left to right |
| 6 | <<, >>, >>> | Bitwise shift (signed left, signed right, unsigned right) | Left to right |
| 7 | <, <=, >, >=, instanceof | Relational comparison, type comparison | Left to right |
| 8 | ==, != | Equality and inequality | Left to right |
| 9 | & | Bitwise AND / Logical non-short-circuit AND | Left to right |
| 10 | ^ | Bitwise XOR / Logical non-short-circuit XOR | Left to right |
| 11 | | | Bitwise OR / Logical non-short-circuit OR | Left to right |
| 12 | && | Logical short-circuit AND | Left to right |
| 13 | || | Logical short-circuit OR | Left to right |
| 14 | ? : | Ternary conditional | Right to left |
| 15 (Lowest) | =, +=, -=, *=, /=, %=, etc. | Assignment and compound assignments | Right to left |
Postfix vs. Prefix Evaluation Mechanics
Increment (++) and decrement (--) operators modify the underlying variable directly:
- Prefix (
++x,--x): Increments/decrements the variable first, then yields the new value to the enclosing expression. - Postfix (
x++,x--): Yields the original value to the enclosing expression, then increments/decrements the variable.
int x = 5;
int y = ++x; // x becomes 6, y receives 6
int a = 5;
int b = a++; // b receives 5, a becomes 6
The Classic Self-Assignment Trap
Consider the expression x = x++:
- The current value of
x(e.g.,5) is stored in an internal evaluation buffer for the right-hand operand. - The postfix increment executes, updating
xto6in memory. - The assignment operator
=executes, writing the buffered value (5) back intox! - The net result is that
xremains5:
int val = 10;
val = val++;
System.out.println(val); // Prints 10!
Left-to-Right Operand Evaluation Order
A critical rule of the Java specification is that operands are evaluated left to right before the operator itself executes, regardless of operator precedence:
int a = 2;
int b = 4;
int result = a + a * b; // 'a' on the left is evaluated (2), then 'a * b' (2 * 4 = 8), yielding 10
When side effects (such as method calls or increments) are involved, left-to-right evaluation strictly dictates state changes:
public class OrderDemo {
static int compute(String label, int val) {
System.out.print(label + " ");
return val;
}
public static void main(String[] args) {
// In compute("A", 1) + compute("B", 2) * compute("C", 3):
// compute("A", 1) runs first, then compute("B", 2), then compute("C", 3)
// Output: A B C (and result is 1 + (2 * 3) = 7)
int total = compute("A", 1) + compute("B", 2) * compute("C", 3);
}
}
Array Index Evaluation Nuance
In array assignments, the target array reference and the index expression are evaluated before the right-hand side expression:
int[] arr = { 10, 20 };
int idx = 0;
arr[idx] = idx = 1;
// Step 1: arr[idx] evaluates idx -> targets arr[0]
// Step 2: right-hand side evaluates idx = 1 -> sets idx to 1 and yields 1
// Step 3: assignment stores 1 into arr[0]
System.out.println(arr[0] + ", " + arr[1]); // Prints 1, 20
Short-Circuit (&&, ||) vs. Non-Short-Circuit (&, |)
Java provides two sets of logical operators:
- Short-circuit operators (
&&,||): If the left-hand operand determines the final outcome, the right-hand operand is never evaluated.- For
false && expr,expris skipped. - For
true || expr,expris skipped.
- For
- Non-short-circuit operators (
&,|,^): Both operands are always evaluated, even if the left operand has already determined the logical result.
Warning
Skipped Side Effects in Short-Circuiting
If the right-hand side of && or || contains an increment, assignment, or method invocation, that side effect will not occur if the left operand short-circuits. Questions on the exam frequently hide critical state updates on the right side of &&.
int count = 0;
boolean flag = false;
if (flag && (++count > 0)) {
// Not executed
}
System.out.println(count); // Prints 0 (increment was never evaluated!)
if (flag & (++count > 0)) {
// Not executed, but & forces evaluation of right operand
}
System.out.println(count); // Prints 1 (increment was evaluated)
Bitwise Shift Operators
<<(Signed left shift): Shifts bits left, filling low-order bits with0(multiplication by ).>>(Signed right shift): Shifts bits right, preserving the sign bit (filling high-order bits with1for negative numbers,0for positive numbers).>>>(Unsigned right shift): Shifts bits right, always filling high-order bits with0, regardless of sign.
Ternary Operator (? :) Type Resolution
The conditional ternary operator takes the form condition ? expression1 : expression2. If condition is true, expression1 is evaluated; otherwise expression2 is evaluated.
Right-to-Left Associativity
Because the ternary operator associates from right to left, nested ternaries group without parentheses as follows:
int score = 75;
String grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";
// Equivalent to: score >= 90 ? "A" : (score >= 80 ? "B" : (score >= 70 ? "C" : "F"))
Numeric Type Promotion and Unboxing Pitfalls
If the two result expressions of a ternary operator have different numeric types, the compiler applies binary numeric promotion to determine the overall expression type:
int n = 5;
double result = true ? n : 2.5; // n is promoted to double (5.0)
// Unboxing NullPointerException Trap:
Integer boxedNull = null;
boolean test = false;
int val = test ? 10 : boxedNull; // Evaluates boxedNull, unboxing it to int -> throws NullPointerException!
Compound Assignment Operators and Implicit Narrowing
Compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>>=) provide an automatic narrowing cast:
This behavior eliminates compiler errors when performing arithmetic on narrow primitive types (byte, short, char):
short s = 50;
// s = s + 10; // COMPILE ERROR: s + 10 promotes to int; cannot assign int to short
s += 10; // Compiles! Automatically treated as s = (short)(s + 10)
byte b = 127;
b += 1; // Compiles! Silently overflows to -128 without any warning or error
Parentheses, the Math API, and Arithmetic Edge Cases
The first official objective names the Math API and parentheses alongside promotion and casting, so expect code that mixes them. Parentheses override the precedence table: 2 + 3 * 4 is 14, while (2 + 3) * 4 is 20. Operands are still evaluated left to right inside and outside the parentheses.
Core java.lang.Math methods
| Call | Result | Return type and trap |
|---|---|---|
Math.round(2.5) / Math.round(-2.5) | 3 / -2 | Rounds half up (toward positive infinity); round(double) returns long, round(float) returns int |
Math.round(-2.6) | -3 | Ties only matter at exactly .5 |
Math.floor(-2.5) / Math.ceil(-2.5) | -3.0 / -2.0 | Both return double, never int |
Math.abs(Integer.MIN_VALUE) | -2147483648 | Overflow: the absolute value does not fit in an int |
Math.max(3, 4.0) | 4.0 | The double overload is chosen, so the result is a double |
Math.max(3, 4L) | 4 (a long) | Overloads exist for int, long, float, double |
Math.pow(2, 3) | 8.0 | Always double; assigning to int needs a cast |
Math.sqrt(16) / Math.sqrt(-1) | 4.0 / NaN | No exception for negative input |
Math.random() | 0.0 <= x < 1.0 | double; (int) (Math.random() * 6) + 1 simulates a die |
Math.addExact(Integer.MAX_VALUE, 1) | throws ArithmeticException | The *Exact methods detect overflow instead of wrapping |
Integer division, remainder, and floating-point surprises
System.out.println(7 / 2); // 3 (integer division truncates toward zero)
System.out.println(-7 / 2); // -3
System.out.println(-7 % 2); // -1 (the remainder takes the sign of the dividend)
System.out.println(7 % -2); // 1
System.out.println(Math.floorDiv(-7, 2)); // -4 (rounds toward negative infinity)
System.out.println(Math.floorMod(-7, 2)); // 1
System.out.println(1.0 / 0); // Infinity (no exception for floating point)
System.out.println(0.0 / 0.0); // NaN
System.out.println(Double.NaN != Double.NaN); // true: NaN is never equal to itself; use Double.isNaN()
// System.out.println(7 / 0); // ArithmeticException: / by zero (integer types only)
char arithmetic and string concatenation order
A char operand is promoted to int, so 'a' + 1 is 98, while (char) ('a' + 1) is 'b'. The + operator is evaluated left to right: once one operand is a String, every later + is concatenation. Therefore 1 + 2 + "3" is "33" but "1" + 2 + 3 is "123".
What is the output of the following code snippet?
int a = 5;
int b = a++ + ++a * 2;
System.out.println("a=" + a + ", b=" + b);
a=7, b=19
a=6, b=19
a=7, b=17
a=6, b=18
Consider the following program:
int count = 0;
boolean check1 = false;
boolean check2 = true;
if (check1 && (++count > 0)) {
count += 10;
}
if (check2 | (++count > 0)) {
count += 100;
}
System.out.println(count);
What does this program print?
111
100
0
101
Given the following variable declaration and statements:
short s = 100;
s += 20; // Line 1
s = s + 20; // Line 2
Which statement accurately describes the compilation outcome?
Line 1 causes a compilation error, but Line 2 compiles successfully
Line 1 compiles successfully, but Line 2 causes a compilation error due to possible loss of precision
Both Line 1 and Line 2 compile successfully because 120 fits in a short
Both Line 1 and Line 2 fail to compile because short operands cannot participate in arithmetic
What does the following code print?
long a = Math.round(-2.5);
double b = Math.ceil(-2.5);
int c = -7 % 2;
System.out.println(a + " " + b + " " + c);
-3 -2.0 -1
-2 -2.0 -1
-3 -3.0 1
-2 -2 -1
Sections you finish are checked off in the contents.