Operators are the symbols that do things to values. Most of them behave exactly as you would guess from school arithmetic. This post covers all of them quickly, then spends its time on the four that do not behave as you would guess — which is where the bugs are.
Arithmetic
int a = 17, b = 5;
System.out.println(a + b); // 22
System.out.println(a - b); // 12
System.out.println(a * b); // 85
System.out.println(a / b); // 3 <- not 3.4
System.out.println(a % b); // 2 <- remainder
Trap 1: integer division truncates. 17 / 5 is 3, not
3.4, because both operands are int and Java produces an int.
The fractional part is discarded, not rounded.
int total = 17, count = 5;
double wrong = total / count; // 3.0 — division already happened in int
double right = (double) total / count; // 3.4 — one cast before the division fixes it
This is the single most common arithmetic bug in Java, and it is quiet: an average that comes out as a whole number looks plausible enough to ship. The fix is to cast one operand before dividing.
%, the remainder operator, is more useful than it looks — it is how you test
divisibility and how you wrap a value into a range:
int n = 14;
System.out.println(n % 2 == 0); // true — even
System.out.println(n % 10); // 4 — last digit
System.out.println((n + 3) % 7); // 3 — wrap into 0..6, e.g. days of the week
Increment and decrement
int i = 5;
i++; // 6
i--; // 5
int x = 5;
System.out.println(x++); // prints 5, THEN x becomes 6 (post-increment)
int y = 5;
System.out.println(++y); // y becomes 6, THEN prints 6 (pre-increment)
On its own line they are identical, and that is where you should keep them. The prefix/postfix
distinction only matters when you use the value in the same expression — which is a thing you can
simply not do. Code like arr[i++] = i++; is legal, confusing, and has no place in
anything you want to maintain.
Comparison, and the == trap
int a = 5, b = 8;
System.out.println(a < b); // true
System.out.println(a >= b); // false
System.out.println(a != b); // true
Trap 2: == on objects compares identity, not contents. For
primitives it compares values, which is what you want. For anything else it asks "are these the very
same object in memory", which is almost never what you want:
String one = "hello";
String two = "hello";
String three = new String("hello");
System.out.println(one == two); // true — both point at the same pooled literal
System.out.println(one == three); // false — new String() made a second object
System.out.println(one.equals(three)); // true — same characters. This is the right question.
The true on that first line is what makes this dangerous: == appears to
work on strings during testing and then fails on a string that came from a file, a database or a
web request. The rule is unconditional — compare objects with equals,
primitives with ==.
The one place == is right for an object is checking for null, since
null is not an object and has no equals to call.
Logical operators and short-circuiting
boolean loggedIn = true, admin = false;
System.out.println(loggedIn && admin); // false — AND, both must be true
System.out.println(loggedIn || admin); // true — OR, either will do
System.out.println(!admin); // true — NOT
Trap 3 — except this one is a feature. && and
|| short-circuit: if the left side settles the answer, the right side is never
evaluated. You should rely on this deliberately, because it is how you guard against
null in a single expression:
String name = null;
// Safe: if name is null, the left side is false and length() is never called
if (name != null && name.length() > 3) {
System.out.println("long name");
}
// The single & does NOT short-circuit — it evaluates both sides and throws
// if (name != null & name.length() > 3) { } // NullPointerException
Order matters, and it is not stylistic: the null check must come first. Swap the two halves and the guard does nothing.
Assignment and the compound operators
int total = 10;
total += 5; // 15 same as total = total + 5
total -= 3; // 12
total *= 2; // 24
total /= 4; // 6
String s = "a";
s += "b"; // "ab" — works on Strings too
The compound operators quietly include a cast, which is occasionally a trap of its own:
int n = 10;
// n = n * 1.5; // does not compile: double cannot be converted to int
n *= 1.5; // compiles, gives 15 — the hidden cast truncates
The ternary operator
condition ? valueIfTrue : valueIfFalse — an if that produces a value
rather than executing a branch:
int age = 20;
String status = age >= 18 ? "adult" : "minor";
// Genuinely useful for defaults
String input = null;
String display = input != null ? input : "unknown";
It is at its best assigning one variable, and at its worst nested. If you find yourself writing
a ? b : c ? d : e, use an if — or a switch, which
Conditional Statements covers.
Precedence
The rules follow arithmetic, extended: multiplicative before additive, comparison before logical,
assignment last. && binds tighter than ||.
System.out.println(2 + 3 * 4); // 14, not 20
boolean a = true, b = false, c = true;
System.out.println(a || b && c); // true — reads as a || (b && c)
System.out.println((a || b) && c); // true — but say which one you meant
You do not need to memorise the full precedence table. You need to know that one exists, and to add parentheses whenever a reader would otherwise have to consult it. Explicit parentheses cost nothing and remove an entire class of argument.
Next
Operators produce values, and the type you will combine them with most is
String. String is next — why it is immutable, why
== lies, and when + in a loop becomes a performance problem.