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.

Last updated: October 2026

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 LevelOperatorsDescriptionAssociativity
1 (Highest)expr++, expr--Postfix increment / decrementLeft to right
2++expr, --expr, +, -, ~, !Prefix increment/decrement, unary plus/minus, bitwise complement, logical NOTRight to left
3(type), newCast, object creationRight 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<, <=, >, >=, instanceofRelational comparison, type comparisonLeft to right
8==, !=Equality and inequalityLeft to right
9&Bitwise AND / Logical non-short-circuit ANDLeft to right
10^Bitwise XOR / Logical non-short-circuit XORLeft to right
11|Bitwise OR / Logical non-short-circuit ORLeft to right
12&&Logical short-circuit ANDLeft to right
13||Logical short-circuit ORLeft to right
14? :Ternary conditionalRight to left
15 (Lowest)=, +=, -=, *=, /=, %=, etc.Assignment and compound assignmentsRight 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++:

  1. The current value of x (e.g., 5) is stored in an internal evaluation buffer for the right-hand operand.
  2. The postfix increment executes, updating x to 6 in memory.
  3. The assignment operator = executes, writing the buffered value (5) back into x!
  4. The net result is that x remains 5:
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, expr is skipped.
    • For true || expr, expr is skipped.
  • 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 with 0 (multiplication by 2n2^n).
  • >> (Signed right shift): Shifts bits right, preserving the sign bit (filling high-order bits with 1 for negative numbers, 0 for positive numbers).
  • >>> (Unsigned right shift): Shifts bits right, always filling high-order bits with 0, 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:

variable op= expr  ⟺  variable =(TypeOf(variable))(variable op expr)\text{variable } \text{op}=\text{ expr} \iff \text{variable } = (\text{TypeOf}(\text{variable}))(\text{variable } \text{op } \text{expr})

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

CallResultReturn type and trap
Math.round(2.5) / Math.round(-2.5)3 / -2Rounds half up (toward positive infinity); round(double) returns long, round(float) returns int
Math.round(-2.6)-3Ties only matter at exactly .5
Math.floor(-2.5) / Math.ceil(-2.5)-3.0 / -2.0Both return double, never int
Math.abs(Integer.MIN_VALUE)-2147483648Overflow: the absolute value does not fit in an int
Math.max(3, 4.0)4.0The 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.0Always double; assigning to int needs a cast
Math.sqrt(16) / Math.sqrt(-1)4.0 / NaNNo exception for negative input
Math.random()0.0 <= x < 1.0double; (int) (Math.random() * 6) + 1 simulates a die
Math.addExact(Integer.MAX_VALUE, 1)throws ArithmeticExceptionThe *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".

Test Your Knowledge

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

a=7, b=19

B

a=6, b=19

C

a=7, b=17

D

a=6, b=18

Test Your Knowledge

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?

A

111

B

100

C

0

D

101

Test Your Knowledge

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?

A

Line 1 causes a compilation error, but Line 2 compiles successfully

B

Line 1 compiles successfully, but Line 2 causes a compilation error due to possible loss of precision

C

Both Line 1 and Line 2 compile successfully because 120 fits in a short

D

Both Line 1 and Line 2 fail to compile because short operands cannot participate in arithmetic

Test Your Knowledge

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);
A

-3 -2.0 -1

B

-2 -2.0 -1

C

-3 -3.0 1

D

-2 -2 -1

Sections you finish are checked off in the contents.