JAVA / BRANCHING AND SWITCH EXPRESSIONS
if, blocks and the dangling else trap
Read a braceless if/else the way the compiler does, predict which if a dangling else binds to, and place braces so the branch you meant is the one that runs.
What you will learn
- Read if (c) S; as governing exactly one statement, not an indented region
- Brace an inner if so a following else binds to the outer if instead
- Spot if (cond); where the empty statement swallows the whole branch
- Use if/else to satisfy definite assignment for a declared, unset local
Understanding if, blocks and the dangling else trap
Java's if statement takes exactly one statement as its body: the grammar is if (Expression) Statement, optionally followed by else Statement. The expression must have type boolean (or Boolean, which is unboxed), so if (list.size()) and if (name) do not compile; there is no truthiness to fall back on. A block, { ... }, is itself a single statement, and that is the only reason if (c) { a(); b(); } works at all. Indentation is whitespace to the compiler and carries no meaning.
That one-statement rule creates a real ambiguity the moment an if without an else is nested directly inside another if. In if (a) if (b) x(); else y(); the else could grammatically attach to either if, and Java resolves it in one fixed direction: an else binds to the nearest preceding if that does not already have one. Picture the parser as greedy, grabbing the innermost if still waiting for a partner, and remember that it does this no matter how the source is laid out.
Braces are how you override that greedy match: wrapping the inner if in { } closes it off, so the following else has to reach the outer if. The same braces buy two other things, a scope in which branch-local variables live and die, and immunity to the classic edit where someone adds a second line to a one-line branch. A block generates no extra bytecode, so the only cost of writing them is two characters.
public class DanglingElse {
// Indented as if the else belonged to the outer if.
static String loose(int n) {
if (n > 0)
if (n % 2 == 0)
return "positive even";
else
return "zero or negative";
return "positive odd";
}
// The same intent, stated with braces.
static String braced(int n) {
if (n > 0) {
if (n % 2 == 0) {
return "positive even";
}
return "positive odd";
} else {
return "zero or negative";
}
}
public static void main(String[] args) {
for (int n : new int[] {4, 7, -3}) {
System.out.println(n + " -> loose: " + loose(n) + ", braced: " + braced(n));
}
}
}An if governs exactly one statement, and an else binds to the nearest if that lacks one, so braces are the only way to state a different pairing.
Worked examples
One statement, and the semicolon that becomes it
Shows that a braceless if controls a single statement, and that a stray semicolon can be that statement.
public class MissingBraces {
public static void main(String[] args) {
int stock = 0;
int shipped = 0;
if (stock > 0)
stock--;
shipped++;
System.out.println("stock=" + stock + " shipped=" + shipped);
int retries = 3;
if (retries > 5);
retries = 0;
System.out.println("retries=" + retries);
}
}Example explained
Line 1if (stock > 0) governs only stock--, which is skipped because stock is 0.
Line 2shipped++ is indented into the branch but sits outside it, so shipped reaches 1 even though nothing was taken from stock.
Line 3if (retries > 5); has the empty statement ; as its entire body, so the test decides nothing.
Line 4retries = 0; is the statement after the if, so the counter is cleared even though 3 is not greater than 5.
What the braces give you besides grouping
Demonstrates that a branch block is a scope, and that if/else can satisfy definite assignment where a lone if cannot.
public class IfBlockScope {
public static void main(String[] args) {
int minutes = 130;
if (minutes >= 60) {
int hours = minutes / 60;
System.out.println(hours + "h " + (minutes % 60) + "m");
}
// System.out.println(hours); // out of scope here
String label;
if (minutes >= 60)
label = "long";
else
label = "short";
System.out.println("label=" + label);
}
}Example explained
Line 1int hours is declared inside the branch block, so its scope ends at the closing brace and the commented line would not compile.
Line 2A braceless branch could not declare it at all: if (c) int hours = 2; is rejected, because a local declaration is not a legal single-statement body.
Line 3String label; is deliberately left unassigned, and the read below is accepted only because every path through the if/else assigns it.
Line 4Remove the else and the same program fails with "variable label might not have been initialized".
Important notes
javac has no dangling-else warning, because both readings are legal code; -Xlint:empty only catches the empty if (cond); body, so braces, a formatter or Checkstyle are what protect you from misbinding.
Braces around a one-line branch cost nothing at run time, since a block emits no extra bytecode; the argument against them is taste, and the argument for them is the pairing rule above.
Common mistakes
Adding a second line under a braceless if and trusting the indentation: the new line runs on every path, so counters increment and logs fire for cases that were supposed to be skipped.
Assuming an else pairs with the if at the same indentation level; when an unbraced inner if sits between them the else belongs to the inner one, and two cases quietly swap labels instead of throwing an error.
Leaving a semicolon after the condition, as in if (n == 1);, often by copying a line that ended in one: the branch becomes empty and the following statement executes unconditionally.
Try it yourself
Change, predict, then run
Paste the loose method into an editor and add exactly one pair of braces, without moving or deleting any line, so that 4, 7 and -3 print the labels the indentation promises. Then check that your version agrees with braced for all three inputs.
Open the Java workspaceCheck your understanding
A method contains, on four separate lines: if (n > 0) / if (n > 10) System.out.println("big"); / else / System.out.println("not positive"); and n is 7. What happens?
- It prints "not positive", because the else pairs with if (n > 10), which is false
- It prints nothing, because the else pairs with if (n > 0), which is true
- It prints "big", because 7 passes the outer test and the inner statement then runs
- It does not compile, because a braceless if cannot contain a nested if
Show answer
The else attaches to the nearest if that has no else yet, which is if (n > 10); 7 fails that test, so the else branch runs and "not positive" is printed for a positive number. Option 1 is the reading the indentation suggests, and it would only be correct if the inner if were wrapped in braces. Option 3 is wrong because a nested if is a perfectly legal single-statement body.