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.
precedence decided the shape of the tree before anything runs. step to evaluate it, one operator at a time.
+ - * / % < > == != && || ! 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+, so3 * 4becomes one subtree. parentheses override it. - same precedence groups left to right.
10 - 4 - 3is(10 - 4) - 3= 3, not10 - (4 - 3)= 9. - types flow up the tree.
1 + 2 + "3" + 4 + 5is"3345":1 + 2is still int math (3), then a String joins and every+after that glues text. - int division throws away the fraction before the
* 2.0ever sees it:7 / 2 * 2.0is6.0, not7.0.
the arithmetic ones
+ - * / % work like you'd expect, with two catches when both sides are integers:
void main() {
IO.println(7 / 2 + " " + -7 / 2 + " " + 7 % 3 + " " + -7 % 3);
IO.println(Math.floorDiv(-7, 2) + " " + Math.floorMod(-7, 3));
}3 -3 1 -1 -4 2
- integer
/truncates toward zero:7 / 2is 3 and-7 / 2is -3 (not -4). %is the remainder, and it takes the sign of the left side:-7 % 3is -1. if you wanted a "wrap around" modulo (like for clock arithmetic or array indexes), useMath.floorMod, which gives 2.- integer
/ 0throwsArithmeticException. adoubledivided by zero givesInfinityinstead (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.
- 1void main() {
- 2 int i = 5;
- 3 int a = i++;
- 4 int b = ++i;
- 5 int c = i++ + ++i;
- 6 IO.println(a + " " + b + " " + c + " " + i);
- 7}
·main starts. no locals yet.
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.
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:
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.
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 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.void main() {
IO.println((-8 >> 1) + " " + (-8 >>> 1) + " " + (1 << 32) + " " + (1L << 32));
IO.println((5 & 3) + " " + (5 | 3) + " " + (5 ^ 3) + " " + ~5);
}-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.
| level | operators |
|---|---|
| postfix | x++ 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) |
- precedence builds the tree (
*before+), then java evaluates it children-first, operands left to right. - int
/truncates,%keeps the left side's sign,/ 0throws. doubles give Infinity instead. i++is worth the old value,++ithe 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.