Part 2 · selection-and-iteration
Boolean expressions and short-circuiting
Unit 2 is the largest slice of the exam after data collections, and this is where its multiple-choice questions live.
By the end of this chapter you can
- Evaluate a compound boolean expression by hand
- Explain what short-circuit evaluation does and when it matters
- Apply De Morgan's laws to simplify a negated condition
- Avoid the == versus .equals trap on objects
Unit 2 is worth 25–35% of the exam score, and a large share of that is multiple-choice questions asking what a condition evaluates to. Those questions are not about cleverness — they are about tracing carefully, and about three specific traps.
The operators
public class Main {
public static void main(String[] args) {
int x = 5;
System.out.println(x > 3 && x < 10); // both must hold
System.out.println(x < 3 || x > 4); // either will do
System.out.println(!(x == 5)); // flips it
}
}Nothing surprising yet. The surprises start when the operands have side effects or can fail.
Short-circuiting
&& and || stop as soon as the answer is decided. If the left side of
&& is false, the right side is never evaluated at all.
public class Main {
static boolean shout(String label) {
System.out.println("evaluated " + label);
return true;
}
public static void main(String[] args) {
System.out.println("--- false && ... ---");
boolean a = false && shout("right of &&");
System.out.println("--- true || ... ---");
boolean b = true || shout("right of ||");
System.out.println("--- true && ... ---");
boolean c = true && shout("right of &&");
System.out.println(a + " " + b + " " + c);
}
}The first two never print their “evaluated” line. The third does.
This is not an optimisation detail — it is a guarantee you are expected to rely on, and it is what makes this safe:
public class Main {
public static void main(String[] args) {
String s = null;
// Safe: the left side is false, so length() is never called.
if (s != null && s.length() > 0) {
System.out.println("not empty");
} else {
System.out.println("null or empty, and no exception");
}
}
}Swap those two conditions and the program throws a NullPointerException.
The order of the operands is part of the correctness, which is unusual — for
most operators, order does not matter.
De Morgan’s laws
Negating a compound condition flips the operator as well as the parts:
!(a && b) is !a || !b
!(a || b) is !a && !bpublic class Main {
public static void main(String[] args) {
boolean[] values = { true, false };
boolean ok = true;
for (boolean a : values) {
for (boolean b : values) {
if (!(a && b) != (!a || !b)) ok = false;
if (!(a || b) != (!a && !b)) ok = false;
}
}
System.out.println("both laws hold in all four cases: " + ok);
}
}Four cases is the entire truth table, so that loop is a proof rather than a sample. Worth doing once: the laws then stop being something to recall under pressure.
The common error is flipping the parts and leaving the operator alone —
writing !a && !b for !(a && b). That is a different condition, true in
fewer cases, and it produces code that looks right and behaves wrongly.
== against .equals
For objects, == asks whether two references point at the same object;
.equals asks whether they represent the same value.
public class Main {
public static void main(String[] args) {
String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println("a == b : " + (a == b)); // true — pooled
System.out.println("a == c : " + (a == c)); // false
System.out.println("a.equals(c): " + a.equals(c)); // true
Integer m = 127, n = 127;
Integer p = 128, q = 128;
System.out.println("127 == 127 : " + (m == n)); // true — cached
System.out.println("128 == 128 : " + (p == q)); // false
}
}This is the trap the exam loves, and the reason it is a trap is that ==
often appears to work. Identical string literals are pooled into one object,
and small Integer values are cached, so == gives the right answer for
"hello" and for 127 — then silently gives the wrong one for a string built
at run time or an integer above 127.
The rule is simple and admits no exceptions worth remembering:
- Primitives (
int,double,char,boolean): use==. - Objects (
String,Integer, anything you wrote): use.equals.