skip to content
everything.basics.operators

operators

precedence, short-circuiting, and the ++i vs i++ thing.

chapter
chapter 02.02
reading time
8 min read

operators are the verbs: +, *, ==, &&. precedence decides how an expression is grouped (* before +), left-to-right evaluation decides the order things actually run, and && / || short-circuit: they skip the right side when the left already decides. i++ hands over the old value, ++i the new one.

the picture

you know how 2 + 3 * 4 is 14 and not 20? same as in math class. java doesn't read an expression left to right like a sentence. before anything runs, the compiler groups it into a tree using precedence rules, and the tree is what gets evaluated: children first, then their parent.

234*+
2 + 3 * 4

precedence decided the shape of the tree before anything runs. step to evaluate it, one operator at a time.

step 1/3
pick an example or type your own (ints, decimals, strings, true/false, + - * / % < > == != && || ! and parentheses). step through: each step evaluates one operator whose operands are ready, and the line under the tree rewrites itself. try the last one: the 1 / 0 that never runs.

a few things that tree just showed you:

  • precedence groups, it doesn't schedule. * binds tighter than +, so 3 * 4 becomes one subtree. parentheses override it.
  • same precedence groups left to right. 10 - 4 - 3 is (10 - 4) - 3 = 3, not 10 - (4 - 3) = 9.
  • types flow up the tree. 1 + 2 + "3" + 4 + 5 is "3345": 1 + 2 is still int math (3), then a String joins and every + after that glues text.
  • int division throws away the fraction before the * 2.0 ever sees it: 7 / 2 * 2.0 is 6.0, not 7.0.

the arithmetic ones

+ - * / % work like you'd expect, with two catches when both sides are integers:

Main.java
void main() {
    IO.println(7 / 2 + " " + -7 / 2 + " " + 7 % 3 + " " + -7 % 3);
    IO.println(Math.floorDiv(-7, 2) + " " + Math.floorMod(-7, 3));
}
output
3 -3 1 -1
-4 2
  • integer / truncates toward zero: 7 / 2 is 3 and -7 / 2 is -3 (not -4).
  • % is the remainder, and it takes the sign of the left side: -7 % 3 is -1. if you wanted a "wrap around" modulo (like for clock arithmetic or array indexes), use Math.floorMod, which gives 2.
  • integer / 0 throws ArithmeticException. a double divided by zero gives Infinity instead (more in floating point).
  • int math that doesn't fit in 32 bits silently wraps around. that's its own chapter: casting and overflow.

compound assignment is shorthand: x += 5 means x = x + 5, same for -=, *=, /=, %= and the bit ones. (with one sneaky extra: it casts the result back to x's type, which casting explains.)

i++ vs ++i

both add 1 to i. the difference is what the expression is worth: i++ (postfix) is worth the value before the increment, ++i (prefix) the value after.

Main.java next to run
  1. 1void main() {
  2. 2 int i = 5;
  3. 3 int a = i++;
  4. 4 int b = ++i;
  5. 5 int c = i++ + ++i;
  6. 6 IO.println(a + " " + b + " " + c + " " + i);
  7. 7}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

main starts. no locals yet.

step 1/10
step through and watch i in the frame. the steps in the middle of a line are real: java does the increment the moment it evaluates that operand, not at the end of the statement.

on its own line (i++; or a for loop's update) the two are identical, since nobody uses the value. the difference only bites when the increment is inside a bigger expression. which is a good reason to never put it there.

predict the output
java
int i = 0;
i = i++;
IO.println(i);

comparison and logic

comparisons (< <= > >= == !=) produce a boolean. logic combines booleans: && (and), || (or), ! (not).

the important part: && and || short-circuit. if the left side of && is false, the answer is false no matter what, so the right side never runs. same for || with true. that's not an optimization you can ignore, it's a feature you lean on:

don't& evaluates both sides, always
java
String name = null;
if (name != null & name.length() > 3) {
    IO.println("long name");
}

NullPointerException: & on booleans is a logical and without the short circuit, so name.length() runs anyway.

do&& stops at the first false
java
String name = null;
if (name != null && name.length() > 3) {
    IO.println("long name");
}

name != null is false, so name.length() is never called. the null check guards the call.

a ? b : c (the ternary) picks one of two values: int max = a > b ? a : b;. only the chosen side is evaluated. it nests, but past two levels nobody can read it, use a switch.

== on primitives compares values. on objects it compares references (is it the same object?), which is why == on strings is a bug. see strings, the basics.

bits and shifts

the bitwise operators work on the individual bits of integers: & (and), | (or), ^ (xor), ~ (flip all), and three shifts. << shifts left (each step doubles), >> shifts right keeping the sign, >>> shifts right filling with zeros.

-8 >> 1-40xfffffffc
sign · 1 bitvalue · 31 bits
-8 >>> 121474836440x7ffffffc
sign · 1 bitvalue · 31 bits
the same -8 shifted right by one, two ways. >> copies the sign bit in from the left, so the number stays negative (-4). >>> shifts in a zero, and a negative number becomes a huge positive one. click bits to play.
Main.java
void main() {
    IO.println((-8 >> 1) + " " + (-8 >>> 1) + " " + (1 << 32) + " " + (1L << 32));
    IO.println((5 & 3) + " " + (5 | 3) + " " + (5 ^ 3) + " " + ~5);
}
output
-4 2147483644 1 4294967296
1 7 6 -6

note 1 << 32 is 1, not 0: for an int only the low 5 bits of the shift distance count, so shifting by 32 is shifting by 0. (long uses the low 6 bits.) you'll see these mostly in hashing, flags and low-level code, for example inside HashMap.

the precedence table

tightest first. you don't need to memorize it: when in doubt, add parentheses, they're free and the next reader will thank you.

leveloperators
postfixx++ x--
unary++x --x +x -x ! ~ (type)
multiplicative* / %
additive+ -
shift<< >> >>>
relational< > <= >= instanceof
equality== !=
bitwise&, then ^, then |
logical&&, then ||
ternary? :
assignment= += -= *= ... (groups right to left: a = b = 5)
keylines
  • precedence builds the tree (* before +), then java evaluates it children-first, operands left to right.
  • int / truncates, % keeps the left side's sign, / 0 throws. doubles give Infinity instead.
  • i++ is worth the old value, ++i the new one. keep them out of bigger expressions.
  • && and || short-circuit, & and | don't. use the short-circuit ones to guard calls.
  • when precedence isn't obvious, parenthesize. it costs nothing.