RizTech Academy logo
RizTech Academy
Language BasicsLesson 3 of 615 min

Operators and precedence

Most operators do exactly what you expect, which is why the four that do not are worth a lesson of their own. Each one here has produced a real bug in real code, and three of them compile without a murmur.

+ is two operators wearing one symbol

It adds numbers, and it joins strings. Which one you get depends on the operands, and it is evaluated strictly left to right:

System.out.println(1 + 2 + " tiffins");
System.out.println("tiffins: " + 1 + 2);
System.out.println("total: " + 1 + 2 * 3);
3 tiffins
tiffins: 12
total: 16

Line one adds 1 and 2 first, then joins. Line two joins "tiffins: " with 1, producing a string, so the 2 is joined too — 12, not 3. Line three shows that precedence still applies inside: 2 * 3 runs before either join.

The moment one side is a String, everything after it becomes string joining. Wrap arithmetic in brackets when you mix them:

System.out.println("total: " + (1 + 2 * 3));

Precedence, in full

Keep this. It answers most "why did that happen" questions about an expression. Higher rows bind tighter.

Level Operators Associativity
1 x++ x-- (postfix) left
2 ++x --x +x -x ! ~ right
3 (type) cast, new right
4 * / % left
5 + - left
6 << >> >>> left
7 < <= > >= instanceof left
8 == != left
9 & left
10 ^ left
11 | left
12 && left
13 || left
14 ? : right
15 = += -= *= /= %= and the rest right

Two consequences people trip over. && binds tighter than ||, so a || b && c is a || (b && c). And - is left-associative, so 10 - 4 - 3 is 3, not 9.

Do not memorise this. Memorise * before +, and use brackets for everything else. Brackets cost nothing and a reviewer never has to check.

++ and --, and the assignment that undoes itself

Postfix returns the old value and then increments. Prefix increments and then returns the new value.

int i = 5;
System.out.println(i++);   // prints 5
System.out.println(i);     // prints 6

int j = 5;
System.out.println(++j);   // prints 6
System.out.println(j);     // prints 6

On a line of its own, i++ and ++i are identical, and that is where you should keep them. Inside a larger expression they reward cleverness with bugs.

The classic:

int k = 5;
k = k++;
System.out.println(k);
5

k++ returns 5 and sets k to 6 — and then the assignment writes the returned 5 back over it. The increment is real and is immediately destroyed. Every reviewer has seen this, usually written as count = count++ inside a loop that then never terminates.

Use ++ as a statement, never inside an expression you also assign from.

Short-circuit, and the idiom it makes possible

&& evaluates its right side only if the left side is true. || evaluates its right side only if the left side is false.

static boolean expensive() {
    calls++;
    return true;
}

boolean r1 = false && expensive();   // calls: 0
boolean r2 = false &  expensive();   // calls: 1

& and | are the non-short-circuiting versions. They always evaluate both sides, and on booleans that is almost never what you want.

Short-circuiting is not merely an optimisation. It is what makes this safe:

String name = null;
System.out.println(name != null && name.length() > 3);
false

name.length() is never called, because the left side was already false. Change that && to & and the same line throws NullPointerException. This null-guard is the single most common use of && in Java, and you will write it several times a day until module 7 shows you how to need it less.

The mirror-image form uses ||:

if (value == null || value.isBlank()) { ... }

Order matters in both. name.length() > 3 && name != null is a guard that guards nothing.

The ternary, and the type it quietly chooses

String label = count == 1 ? "tiffin" : "tiffins";

Readable, and worth using for small either-or values. Two things to know.

First, it is an expression, not a statement — it produces a value, so it cannot stand on its own line. If both branches are actions rather than values, you want an if.

Second, and this is the trap: both branches are forced into one common type.

Object o = true ? 1 : 2.0;
System.out.println(o);
System.out.println(o.getClass().getSimpleName());
1.0
Double

The condition was true, so the answer should be the int 1. It printed 1.0, a Double, because the two branches were unified to double before either was chosen. Keep both branches the same type. The version of this that actually hurts involves an Integer and an int, where unification forces unboxing and a null branch throws NullPointerException on a line with no visible method call.

Bitwise and shifts

You will not need these often. You will need to recognise them.

Operator Meaning Example
& Bitwise AND 12 & 10 is 8
| Bitwise OR 12 | 10 is 14
^ Bitwise XOR 12 ^ 10 is 6
~ Bitwise NOT ~12 is -13
<< Shift left, fill with zeros 1 << 10 is 1024
>> Shift right, keep the sign -8 >> 1 is -4
>>> Shift right, fill with zeros -8 >>> 1 is 2147483644

>>> is the one Java has and most languages do not. The difference only shows up on negative numbers, and it shows up dramatically. Shifts left and right are fast multiplication and division by powers of two, but write * 2 — the compiler knows the trick and the reader does not have to.

Where you will genuinely meet bitwise operators is flag sets, hashing code, and anything reading a binary file format.

Math, worth having on one page

Call Gives Note
Math.max(3, 9) 9
Math.min(3, 9) 3
Math.abs(-7) 7
Math.pow(2, 10) 1024.0 Always a double
Math.sqrt(144) 12.0
Math.round(2.4) 2
Math.round(2.5) 3
Math.round(-2.5) -2 Not -3 — halves go towards positive infinity
Math.ceil(2.1) 3.0
Math.floor(2.9) 2.0
Math.floorDiv(-7, 2) -4 -7 / 2 is -3
Math.floorMod(-7, 2) 1 -7 % 2 is -1
Math.random() 0.0 up to but excluding 1.0 Prefer java.util.Random

Math.round(-2.5) giving -2 catches people who expect symmetry. The rule is "add 0.5 and take the floor", which leans positive.

And one carried over from the last lesson: compound assignment (+=, -=, *=) performs a hidden narrowing cast, so byte b = 10; b += 300; compiles and gives 54. The plain b = b + 300 does not compile. Same trap, worth seeing twice.

Check your work

What does "tiffins: " + 1 + 2 print, and why? tiffins: 12. + is left-associative, the first operation joins a string with 1 producing a string, and the 2 is then joined too. Bracket the arithmetic: + (1 + 2).

What does k = k++; leave in k? The original value. k++ returns the old value and increments, then the assignment writes the old value back over the increment.

Why does name != null && name.length() > 3 not throw when name is null? && short-circuits: the right side is never evaluated once the left is false. Writing & instead would throw, as would reversing the order.

What is the type of true ? 1 : 2.0? double, so the expression is 1.0. Both branches are unified to a common type before one is selected. Keep the two branches the same type.

What is -8 >> 1 and -8 >>> 1? -4 and 2147483644. >> preserves the sign bit; >>> shifts zeros in from the left and turns a negative into a large positive.

What is 10 - 4 - 3? 3. Subtraction is left-associative.

What is true || true && false? true. && binds tighter than ||, so it reads as true || (true && false).

What is Math.round(-2.5)? -2. Halfway values round towards positive infinity.

Practice 1, the five expressions.

1 + 2 + "3" + 4 + 5     ->  3345
10 % 3 * 2              ->  2
1 + 2 * 3 - 4 / 2       ->  5
true || false && false  ->  true
5 / 2 * 2.0             ->  4.0

The first adds 1 and 2, then joins everything after the string. The last is the one worth dwelling on: 5 / 2 is integer division giving 2, and only then is 2.0 involved. Writing 5 / 2.0 * 2 gives 5.0.

Practice 4, the safe average.

static String average(int total, int count) {
    if (count == 0) {
        return "no data";
    }
    return "%.2f".formatted((double) total / count);
}

The guard has to come first, and the cast has to be on one operand before the division, not on the result — (double) (total / count) would divide in integers and then widen a value that has already lost its fraction.

Practice

  1. Predict, then run. Write down your answer for each of these before compiling. Then run them.

    System.out.println(1 + 2 + "3" + 4 + 5);
    System.out.println(10 % 3 * 2);
    System.out.println(1 + 2 * 3 - 4 / 2);
    System.out.println(true || false && false);
    System.out.println(5 / 2 * 2.0);
    

    The last one is the interesting one: 5 / 2 happens in integers first.

  2. Reproduce k = k++. Write it, print the result, then rewrite it as three separate statements that make the sequence visible. Then write a loop using count = count++ as its increment and watch it never finish. Stop it with Ctrl+C.

  3. Prove short-circuiting. Write a method that prints something and returns true, and call it on the right of && with false on the left. Confirm nothing prints. Change && to & and confirm it does.

  4. Write a safe average. Given a total and a count, return the average to two decimal places, or the text "no data" when the count is zero. Do it without an exception, and be careful where the cast goes.

  5. Harder — a delivery charge. A tiffin delivery is free above Rs 500, Rs 20 within 3 km, and Rs 20 plus Rs 8 per additional kilometre beyond that. Write it as a single expression using nested ternaries. Then rewrite it as an if/else chain, and decide honestly which one you would rather find in a pull request. There is a right answer and it is not the clever one.

Next: control flow — if, the modern switch, and the loop that runs one time too many.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship