The six relational operators and the boolean type they produce, if and if-else, the optional braces and stray semicolons that have caused real production bugs, chaining and nesting conditionals, and the switch statement. Follows Think Java 2e, Chapter 5 (Conditionals and Logic), Sections 5.1-5.4, pp. 71-77, cross-referenced against The Java Tutorials — The if-then and if-then-else Statements.
Subject: Java · 65 slides · code lesson
Open the interactive version of this deck
Title
Think Java 2e · Chapter 5 · Conditionals and Logic
Sections 5.1-5.4 · pp. 71-77
Objectives
This lesson follows Think Java 2e, Chapter 5 (Conditionals and Logic), Sections 5.1-5.4, pp. 71-77. Everything on these slides can be checked against those pages.
1. Write the six relational operators correctly, and say why = and == are different.
2. Write an if statement and an if-else statement, and name the condition and the branches.
3. Explain what happens when the braces are omitted, and why you should write them anyway.
4. Spot a stray semicolon after a condition and say what it does to the program.
5. Chain and nest conditionals, and say why the last branch of a chain needs no test.
6. Write a switch statement with cases, breaks and a default, and group several cases together.
Warm-up
Every program you have written so far has one thing in common. Name it.
Discussion prompt
The programs in the previous chapters do the same thing every time they run, regardless of the input. What is missing from them — and which of the five basic instructions from Lesson 1a have you still not met?
Hint: Look back at the list: input, output, math, decision, repetition.
Answer:
Decision and repetition. You have written input, output and math since Chapter 3; this chapter adds decision, and Chapter 6 adds repetition.
That is why every program so far has been predictable: with no way to check a condition, execution goes straight through in source order every time. From this chapter on, tracing a program means tracing it with a particular input in mind.
Concept
For more complex computations, programs need to react to inputs, check conditions and generate applicable results. This chapter introduces the Java features for expressing logic and making decisions.
Figure (svg): A flowchart showing a condition diamond with a true branch and a false branch rejoining afterwards
Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 5 (Conditionals and Logic), Sections 5.1-5.4, pp. 71-77 — Chapter 5 opens on printed page 71.
Section
Section 5.1
Concept
Java has six relational operators that test the relationship between two values. The result of any of them is one of two special values, true or false, which belong to the data type boolean — named after the mathematician George Boole, who developed an algebraic way of representing logic.
x == y // x is equal to y
x != y // x is not equal to y
x > y // x is greater than y
x < y // x is less than y
x >= y // x is greater than or equal to y
x <= y // x is less than or equal to y| operator | means | with x = 5, y = 3 |
|---|---|---|
| == | equal to | false |
| != | not equal to | true |
| > | greater than | true |
| < | less than | false |
| >= | greater than or equal to | true |
| <= | less than or equal to | false |
boolean — A data type with only two possible values, true and false.
relational operator — An operator that compares two values and produces a boolean indicating the relationship between them.
You are probably familiar with these from mathematics. Notice how Java differs from the mathematical symbols: there is no single-character 'not equal' or 'greater than or equal' sign, so each is written as two characters.
Notation
The operators themselves are familiar. The mistakes are specific to Java and worth naming before you make them.
Annotate
= instead of ==. Remember Chapter 2: = is the assignment operator and == is a relational operator. x = y changes x; x == y asks a question about it.=< and => do not exist. The order is 'less than or equal', so the angle bracket comes first: <= and >=.5 < "6" is invalid because 5 is an int and "6" is a String — which is Chapter 2's distinction between a numeric-looking string and a number, appearing again.5 < 6.0 converts the 5 to 5.0 automatically and compares two doubles.The = for == mistake deserves special attention. In some languages it silently compiles into an assignment; in Java it usually fails, because an assignment does not produce a boolean.
Worked example
A relational expression is evaluated the same way as any other: substitute the values, then apply the operator. What is new is that the result is a boolean.
int x = 7;
int y = 7;
System.out.println(x == y);
System.out.println(x != y);
System.out.println(x >= y);| expression | after substitution | result |
|---|---|---|
| x == y | 7 == 7 | true |
| x != y | 7 != 7 | false |
| x >= y | 7 >= 7 | true — 'or equal to' is satisfied |
Substitute each variable with its current value.
Why: The same first step as any expression, from Chapter 2.
Apply the operator and read off true or false.
Why: The result is a boolean value, which println can display directly.
Watch the third one.
Why: >= is true when the values are equal, because it means 'greater than OR equal to'. This is where off-by-one errors come from.
Verify: Expect true, false, true.
Why: Then change y to 8 and predict again before running: false, true, false. Being able to predict all six operators on equal values is worth checking now, because Chapter 6's loops depend on getting < and <= right.
Prediction
Relational operators on equal values.
int a = 4, b = 4;
System.out.println(a > b);
System.out.println(a >= b);| expression | values | result |
|---|---|---|
| a > b | 4 > 4 | false — strictly greater |
| a >= b | 4 >= 4 | true — or equal to |
Predict first
What are the two values printed?
Correct: false then true
Why: > is strictly greater, so 4 > 4 is false; >= includes equality, so 4 >= 4 is true. The difference between the two on equal values is the single most common source of off-by-one errors, and it becomes critical in Chapter 6 where a loop's condition decides how many times it runs.
Concept
It is easy to think of true and false as something special. They are ordinary values of an ordinary type — you can print them, and Section 5.7 will store them in variables.
| type | example values | produced by |
|---|---|---|
| int | 1, -5, 719 | arithmetic operators |
| double | 2.54, 0.98333 | arithmetic on doubles |
| String | "Hello" | quotation marks, concatenation |
| boolean | true, false | relational and logical operators |
The fourth row is the new one, and it is the last primitive type this book needs. Everything in this chapter is about producing boolean values and then doing something with them.
Trap
The classic slip. One character, and a completely different meaning.
int x = 5;
if (x = 0) { // assignment, not comparison
System.out.println("x is zero");
}| what you wrote | what it means | result |
|---|---|---|
| x = 0 | assign 0 to x | an int, not a boolean |
| if (an int) | a condition must be boolean | compile error: incompatible types |
In Java this is caught at compile time, because a condition must be a boolean and an assignment produces an int. In C and C++ the same line compiles and silently sets x to zero — which is why the mistake has such a reputation.
== asks the question; = performs the action.
int x = 5;
if (x == 0) { // comparison
System.out.println("x is zero");
}| operator | kind | produces |
|---|---|---|
| = | assignment | stores a value; the expression's type is the variable's |
| == | relational | a boolean — true or false |
Say them differently in your head: = is gets, == is equals. Reading x = 0 as 'x gets zero' makes it obvious that it does not belong in a condition.
Definition probe
The two sides of a relational operator must be compatible.
Sort into buckets
Sort each expression.
<= and >=, never =< or =>.Matching
Four of the six.
Match the pairs
Why: The last pairing is the one to fix in memory. = is not a relational operator and produces no boolean; it is the assignment from Chapter 2. Three of these ask questions and one performs an action.
Explain it to yourself
Some languages use 0 and 1 instead.
Discussion prompt
C uses the integer 0 for false and anything else for true, with no separate boolean type. Java has one. What does having a distinct type buy you? Use the if (x = 0) mistake in your answer.
Hint: What happened when the condition was an int?
Answer:
It lets the compiler catch the = for == mistake. Because a condition must be a boolean and an assignment produces an int, if (x = 0) fails to compile in Java. In C it compiles, assigns zero, and evaluates as false — silently.
More generally, a separate type means the language can distinguish a question from a quantity. That is the same argument as declaring variable types at all: saying what kind of thing you mean lets the compiler check the places you could be wrong.
Section
Section 5.2
Concept
Conditional statements let a program check conditions and react. The simplest is the if statement: the expression in parentheses is the condition, and if it is true the statements in braces run. If it is false, execution skips over that block.
if (x > 0) {
System.out.println("x is positive");
}
if (x % 2 == 0) {
System.out.println("x is even");
} else {
System.out.println("x is odd");
}| form | branches | how many run |
|---|---|---|
| if | one | zero or one |
| if-else | two | exactly one |
conditional statement — A statement that uses a condition to determine which statements to execute.
block — A sequence of statements surrounded by braces, generally run as the result of a condition.
branch — One of the alternative blocks after a conditional statement.
Since the condition must be either true or false, exactly one of the two branches of an if-else will run. Never both, and never neither.
Picture it
The difference is what happens when the condition is false: an if simply skips; an if-else takes the other road.
Figure (svg): A flowchart for a plain if statement, showing the false path skipping the block entirely
Both paths rejoin afterwards. Whatever the condition, the statement after the conditional runs — which is worth holding on to, because it is exactly what the brace mistakes on the next slides break.
Worked example
From this chapter on, a trace needs a specific input. Run the same code twice with different values and watch the branch change.
if (x % 2 == 0) {
System.out.println("x is even");
} else {
System.out.println("x is odd");
}| x | x % 2 | condition | branch taken | output |
|---|---|---|---|---|
| 4 | 0 | 0 == 0 is true | the if branch | x is even |
| 7 | 1 | 1 == 0 is false | the else branch | x is odd |
| 0 | 0 | true | the if branch | x is even |
| -3 | -1 | false | the else branch | x is odd |
Evaluate the condition with the given value.
Why: x % 2 uses the remainder operator from Lesson 3b.
Compare against zero.
Why: If the remainder when x is divided by 2 is 0, x is even.
Take exactly one branch.
Why: The condition is either true or false, so precisely one println runs.
Note the last row.
Why: -3 % 2 is -1 in Java, not 1 — because the remainder takes the sign of the left operand. The test still works, because -1 is not 0.
Verify: Run it with each of the four values and check the output matches the table.
Why: The negative case is the one worth checking deliberately. A test written as x % 2 == 1 would fail for -3, which is a real bug that this table would have caught.
Prediction
One specific input.
int x = -4;
if (x > 0) {
System.out.println("positive");
} else {
System.out.println("not positive");
}| step | value |
|---|---|
| the condition | -4 > 0 |
| evaluates to | false |
| branch taken | else |
Predict first
What is displayed?
Correct: not positive
Why: -4 is not greater than 0, so the condition is false and the else branch runs. Exactly one branch of an if-else always runs — never both and never neither — because the condition must be either true or false.
Concept
The parentheses do not have to contain a comparison. They have to contain something whose value is a boolean — which, as Section 5.7 will show, includes a variable.
| condition | legal? | why |
|---|---|---|
| if (x > 0) | yes | a relational expression, producing a boolean |
| if (x % 2 == 0) | yes | arithmetic, then a comparison |
| if (isSingleDigit(x)) | yes | a method returning a boolean — Section 5.8 |
| if (x) | no, if x is an int | an int is not a boolean |
| if (x = 0) | no | an assignment produces an int, not a boolean |
The fourth row is where Java differs from C and Python, both of which accept a number as a condition. Java insists on a boolean, which is stricter and catches the mistakes in rows four and five.
Trap
One extra character. This compiles, and the message appears regardless of the value of x.
int x = 1;
if (x % 2 == 0); { // incorrect semicolon
System.out.println("x is even");
}| what the compiler sees | effect |
|---|---|
| if (x % 2 == 0) | a condition... |
| ; | ...whose body is an EMPTY statement |
| { println(...); } | a separate block, run unconditionally |
| output for x = 1 | x is even — which is false |
Reformatted to show what really happens, the semicolon is the entire body of the if, and the braced block that follows is nothing to do with it. The output is wrong for every odd number and nothing warns you.
No semicolon after the condition. A block follows, defined with braces.
int x = 1;
if (x % 2 == 0) {
System.out.println("x is even");
}| rule | check |
|---|---|
| no semicolon at the end of an if or else line | a brace follows instead |
| each line ends with a semicolon OR a brace | never both |
Think Java's general rule is worth memorising: each line of Java code should end with a semicolon or a brace — but not both. Checkstyle and similar tools warn about exactly this.
Prediction
x is 1, which is odd.
int x = 1;
if (x % 2 == 0);
{
System.out.println("x is even");
}| part | role |
|---|---|
| if (x % 2 == 0) | a condition |
| ; | an empty statement — the if's entire body |
| { ... } | an independent block |
Predict first
What does this display?
Correct: x is even — the block runs regardless of x
Why: The semicolon becomes the if statement's body — an empty statement that does nothing — and the braced block afterwards is a separate, unconditional block. It compiles cleanly and prints the wrong thing for every value of x, which makes it a logic error rather than a compile-time one.
Fill the middle
Report whether a number is negative.
Fill in the blanks
if (n < 0) else ___ ___
Why: n < 0 is true exactly when n is negative, and else supplies the other branch. Note there is no semicolon after the condition or after else — each of those lines ends with a brace instead, which is the rule that prevents the stray-semicolon bug.
Socratic
It sounds obvious. Say precisely why it is guaranteed.
Discussion prompt
In an if-else, exactly one branch runs — never both, never neither. What property of the condition guarantees that? And what would have to be true of a condition for neither branch to run?
Hint: How many values does a boolean have?
Answer:
A boolean has exactly two values, so the condition is either true or false with no third possibility. One branch handles each, so precisely one runs.
For neither to run, the condition would have to evaluate to something that is neither true nor false — which Java's type system makes impossible for a boolean. In a language where a condition can be null or an error value, that guarantee weakens, and reasoning about the code gets harder.
This guarantee is what makes the last branch of a chain need no test, which is the next section.
Section
Section 5.2, continued
Concept
The braces are optional for a branch that has only one statement. That flexibility is the source of one of the most famous bugs in modern software.
if (x > 0)
System.out.println("x is positive");
System.out.println("x is not zero");| line | part of the if? | runs when x <= 0? |
|---|---|---|
| println("x is positive") | yes — the one statement after the condition | no |
| println("x is not zero") | no — despite the indentation | yes |
The indentation says both lines are conditional. Java disagrees: without braces, only the first println is part of the if statement, and the second runs no matter what.
Picture it
The two are not the same program, and only one of them is indented honestly.
Figure (svg): Two panels comparing indented code that appears to have two conditional statements with what the compiler actually reads
This is Lesson 1b's point about misleading indentation, with real consequences attached. Java ignores whitespace, so the indentation is a claim about the code that nothing checks.
Worked example
Think Java points at this by name: even experienced programmers make this mistake. It shipped in Apple's SSL code in 2014 and broke certificate verification for millions of devices.
// the shape of the bug, in Java terms
if (someCheckFailed)
goToFail();
goToFail(); // always runs
// ... the rest of the verification, now unreachable| line | conditional? | consequence |
|---|---|---|
| the first call | yes | correct |
| the duplicated second call | no — no braces | jumps to the end unconditionally |
| the remaining checks | — | never reached |
| the effect | — | invalid certificates were accepted |
Notice that the second line was almost certainly a copy-paste slip.
Why: One duplicated line, in code that was reviewed and shipped.
Notice that the indentation hid it.
Why: Both lines were indented equally, so the code read as though both were conditional.
Notice that it compiled without a warning.
Why: Omitting optional braces is allowed by the language, so nothing objected.
Notice what braces would have done.
Why: With braces, the duplicated line would have been inside the block and harmless.
Verify: Search the web for Apple's 'goto fail' bug and read one account of it.
Why: The lesson Think Java draws is not 'be careful' — it is always write the braces, because carefulness is exactly what failed here. A rule that removes the possibility beats a habit of vigilance.
Prediction
Read the braces, not the indentation.
int x = -5;
if (x > 0)
System.out.println("A");
System.out.println("B");| line | conditional? | runs? |
|---|---|---|
| println("A") | yes | no — x is not > 0 |
| println("B") | no | yes — unconditional |
Predict first
What is displayed?
Correct: B
Why: Without braces only the first statement belongs to the if, so A is skipped and B runs regardless. The indentation suggests both are conditional and the compiler ignores indentation entirely — which is the whole hazard.
Concept
The compiler will not complain if you omit optional braces or write empty statements. Doing so is allowed by the Java language, but it often results in bugs that are difficult to find.
Figure (svg): A rule card stating that every branch should use braces even when they are optional
It is worth being clear that this is a style rule rather than a language rule. The language permits the shorter form; experience says do not use it.
Trap
How the bug is usually born. The original code was correct; a later edit broke it.
// originally, and correct:
if (x > 0)
System.out.println("positive");
// later, someone adds a line:
if (x > 0)
System.out.println("positive");
System.out.println("and nonzero"); // NOT conditional| version | x = 5 | x = -5 |
|---|---|---|
| original | positive | (nothing) |
| after the edit | positive / and nonzero | and nonzero |
The second row of the second version is the bug: a message about being nonzero, printed for a negative number. The person who added the line indented it correctly and was still wrong.
With braces from the start, the later edit is safe.
if (x > 0) {
System.out.println("positive");
System.out.println("and nonzero"); // inside the block
}| version | x = 5 | x = -5 |
|---|---|---|
| with braces | positive / and nonzero | (nothing) |
The braces were written when the branch had one statement, and cost nothing then. Their value appeared later, when someone who had not read this lesson added a line — which is exactly the situation the rule exists for.
Error analysis
Three separate mistakes from this section, in one fragment. Each compiles.
Annotate
= instead of ==. This one is caught by the compiler — an assignment produces an int and a condition must be a boolean — so it is the least dangerous of the three.The general point is that Java's punctuation is unforgiving in the direction that matters least. The errors it catches are cheap; the ones it permits are expensive.
Two truths and a lie
Four claims. Three are false.
Eliminate the wrong options
Eliminate the false claims and keep the true one.
Survives elimination: B
Why: Without braces the branch consists of exactly one statement, whatever the indentation suggests. Because the language permits this and the compiler says nothing, the only reliable defence is the habit of always writing the braces — which costs two characters and removes the whole class of bug.
Counterexample
Argue the other side before dismissing it.
Discussion prompt
Some experienced programmers write if (x < 0) return; on one line with no braces. What is the argument for that, and does it survive the goto fail story?
Hint: What is different about a one-line form compared with a two-line one?
Answer:
The argument is that on a single line the danger disappears: there is no indented second statement to be misread, and adding one would obviously require restructuring the line. Guard clauses written this way are compact and readable.
It partly survives. The goto fail bug had the body on its own line, indented, which is the dangerous form. A same-line guard does not have that failure mode.
But most style guides still require braces, on the grounds that one rule with no exceptions is easier to follow and to check automatically than one with a carve-out. For now, write the braces every time — you can adopt a team's exceptions when you join a team that has them.
Section
Section 5.3
Concept
Sometimes you want to check related conditions and choose one of several actions. One way is by chaining a series of if and else blocks.
if (x > 0) {
System.out.println("x is positive");
} else if (x < 0) {
System.out.println("x is negative");
} else {
System.out.println("x is zero");
}| x | x > 0 | x < 0 | branch taken | output |
|---|---|---|---|---|
| 5 | true | not tested | first | x is positive |
| -5 | false | true | second | x is negative |
| 0 | false | false | the final else | x is zero |
chaining — A way of joining several conditional statements in sequence.
Notice the second row: once the first condition is false, the second is tested. And notice the middle column of the first row — once a branch is taken, the remaining conditions are not evaluated at all.
Notation
The final branch is simply else, not else if (x == 0). That is not laziness; it is a claim about what is left.
Annotate
else if (x == 0) would not be wrong, but it would suggest to a reader that some fourth case might exist — and if you got the condition slightly wrong, some value could fall through with nothing happening.else is a guarantee. It says: whatever is left, handle it here. Chains can be as long as you want, though they get difficult to read if they get out of hand.The reasoning here — by the time we reach this branch, we know these things are false — is worth doing explicitly whenever you write a chain. It is where off-by-one and missing-case bugs hide.
Worked example
In addition to chaining you can make complex decisions by nesting one conditional inside another. Here is the previous example rewritten.
if (x > 0) {
System.out.println("x is positive");
} else {
if (x < 0) {
System.out.println("x is negative");
} else {
System.out.println("x is zero");
}
}| structure | chained version | nested version |
|---|---|---|
| outer conditional | if / else if / else | if / else — two branches |
| second branch contains | another condition on the same level | a whole conditional statement |
| behaviour | identical | identical |
| readability | flatter | deeper — one more level of indentation |
Read the outer conditional first.
Why: It has two branches: a print statement, and another conditional.
Read the inner conditional.
Why: It has two branches of its own, which are print statements — but they could have been conditionals as well.
Check that the behaviour matches the chain.
Why: Positive, negative, zero — the same three outcomes for the same three inputs.
Compare the indentation depth.
Why: The nested version indents the zero case twice. With four cases it would indent three times, and quickly become hard to read.
Verify: Run both versions with 5, -5 and 0 and confirm the outputs are identical.
Why: These nested structures are common and can become difficult to read very quickly. Good indentation is essential to make the structure — or the intended structure — apparent to the reader.
Prediction
A three-way chain.
if (x > 0) {
System.out.println("x is positive");
} else if (x < 0) {
System.out.println("x is negative");
} else {
System.out.println("x is zero");
}| condition | value for x = 0 | reached? |
|---|---|---|
| x > 0 | false | yes — and fails |
| x < 0 | false | yes — and fails |
| else | — | yes — this branch runs |
Predict first
What is displayed?
Correct: x is zero
Why: Zero is neither greater than nor less than zero, so both conditions fail and the final else runs. That branch needs no test of its own precisely because the two earlier conditions have eliminated every other possibility.
Concept
They can express the same logic, so the choice is about what a reader will find clearest.
| situation | prefer | why |
|---|---|---|
| several alternatives at the same level | chaining | one indentation level; reads as a list of cases |
| a decision that only matters inside another | nesting | the structure mirrors the logic |
| more than about four cases | a switch, if the cases are values | Section 5.4 |
| two conditions that must both hold | a single condition with && | Lesson 5b |
The last row is worth waiting for. Nesting if (x == 0) { if (y == 0) { ... } } is exactly the case that the && operator collapses into one line — and Lesson 5b opens with it.
Trap
Missing else. Four separate if statements, each tested independently.
if (score >= 90) { grade = "A"; }
if (score >= 80) { grade = "B"; }
if (score >= 70) { grade = "C"; }
if (score >= 0) { grade = "D"; }| score | conditions that are true | final grade |
|---|---|---|
| 95 | all four | D |
| 85 | the last three | D |
| 75 | the last two | D |
| 50 | only the last | D |
Every score gets a D, because each condition is tested independently and the last one that is true wins. The program compiles, runs, and is wrong for every input.
A real chain, where each branch is reached only if the earlier ones failed.
if (score >= 90) { grade = "A"; }
else if (score >= 80) { grade = "B"; }
else if (score >= 70) { grade = "C"; }
else { grade = "D"; }| score | first condition that is true | grade |
|---|---|---|
| 95 | score >= 90 | A |
| 85 | score >= 80 | B |
| 75 | score >= 70 | C |
| 50 | none — the final else | D |
The else is what makes the order meaningful: once a branch is taken, the rest are skipped entirely. That is also why the conditions must be written from most specific to least — reversing them would give everyone a D again.
Ranking
For a grading chain using else if, from the branch that must come first.
Put in order
Why: In a chain, the first condition that is true wins and the rest are skipped, so the most restrictive test must come first. Putting score >= 70 first would give a C to a score of 95, because that condition is true and the chain would stop there.
Discrimination
The presence of else changes the meaning entirely.
Sort into buckets
Sort each situation by which structure it needs.
Constraint
Practise moving between the two forms.
Discussion prompt
Take this nested version and write it as a chain: if (a) { X } else { if (b) { Y } else { Z } }. Then say when you would prefer each form, and what happens to the indentation as the number of cases grows.
Hint: The else and the inner if can be written on one line.
Answer:
The chain is if (a) { X } else if (b) { Y } else { Z } — identical behaviour, one indentation level instead of two.
Prefer the chain when the cases are alternatives at the same conceptual level, which is most of the time. Prefer nesting when the inner decision genuinely only matters inside the outer one, because then the structure mirrors the logic.
With four cases the nested form indents three times and the chain still indents once. That is why long chains are common in real code and deeply nested conditionals are treated as a warning sign.
Section
Section 5.4
Concept
If you need to make a series of decisions, chaining else if blocks can get long and redundant. An alternative for evaluating many possible values of a single expression is the switch statement.
switch (number) {
case 1:
word = "one";
break;
case 2:
word = "two";
break;
case 3:
word = "three";
break;
default:
word = "unknown";
break;
}| number | case matched | word becomes |
|---|---|---|
| 1 | case 1 | "one" |
| 2 | case 2 | "two" |
| 3 | case 3 | "three" |
| 7 | none — default | "unknown" |
The body of a switch is organised into one or more case blocks. Each case ends with a break statement, which exits the switch body. The default block is optional and runs only if none of the cases apply.
Picture it
Both do the same job. The switch is longer on the page and says more clearly that all the cases are testing the same expression.
Figure (svg): Two panels comparing a chained else-if version with the equivalent switch statement
The chain repeats number == on every line; the switch names it once. For three cases that hardly matters, and for the banking programs Think Java mentions — writing out numbers in long form — it matters a great deal.
Worked example
Although switch statements look longer than chained else-if blocks, they are particularly useful when multiple cases can be grouped.
String food = "banana";
switch (food) {
case "apple":
case "banana":
case "cherry":
System.out.println("Fruit!");
break;
case "asparagus":
case "broccoli":
case "carrot":
System.out.println("Vegetable!");
break;
}| food | case matched | output |
|---|---|---|
| "apple" | case "apple" — falls through | Fruit! |
| "banana" | case "banana" — falls through | Fruit! |
| "carrot" | case "carrot" | Vegetable! |
| "bread" | none, and no default | (nothing) |
Stack the case labels with no statements between them.
Why: case "apple": immediately followed by case "banana": means both labels lead to the same block.
Put the shared statements after the last label of the group.
Why: All three fruits reach the same println.
End the group with a break.
Why: Without it, execution would continue into the vegetable block.
Note there is no default here.
Why: So an unmatched value produces no output at all — which may or may not be what you want.
Verify: Try each of the six foods and confirm three print Fruit! and three print Vegetable!
Why: Then try "bread" and confirm nothing happens. If that is not the behaviour you want, add a default block — its absence is a decision, not an oversight.
Prediction
A missing break in the first case.
int number = 1;
String word;
switch (number) {
case 1:
word = "one";
case 2:
word = "two";
break;
default:
word = "unknown";
}| step | what happens | word |
|---|---|---|
| match case 1 | word = "one" | "one" |
| no break — fall through | word = "two" | "two" |
| break | leave the switch | "two" |
Predict first
What is word after the switch?
Correct: "two"
Why: Without a break, execution falls through from case 1 into case 2's statements, so word is assigned "one" and then immediately overwritten with "two". Fall-through is a real feature — it is what makes grouped case labels work — which is exactly why the compiler does not warn you about it.
Concept
The break at the end of each case is not decoration. Without it, execution falls through into the next case — which is what makes grouping work, and what causes bugs when it is unintended.
| with break | without break |
|---|---|
| execution leaves the switch after the case's statements | execution continues into the next case's statements |
| exactly one block runs | several blocks may run |
| what you almost always want | what you want only when grouping labels |
| required at the end of each case | deliberate, and worth a comment when used |
The grouping on the previous slide relies on fall-through: three case labels in a row with nothing between them all reach the same statements. That is the one common, intentional use of it.
Trap
One break omitted, and two cases run.
switch (number) {
case 1:
word = "one";
// no break
case 2:
word = "two";
break;
default:
word = "unknown";
}| number | what runs | word ends as |
|---|---|---|
| 1 | case 1's statement, then falls into case 2 | "two" — wrong |
| 2 | case 2's statement, then break | "two" — correct |
| 7 | default | "unknown" — correct |
Only the first row is wrong, which is what makes this hard to spot: two thirds of your test cases pass. The compiler says nothing, because fall-through is a legitimate feature.
Every case ends with a break, unless you are deliberately grouping labels.
switch (number) {
case 1:
word = "one";
break;
case 2:
word = "two";
break;
case 3:
word = "three";
break;
default:
word = "unknown";
break;
}| number | runs | word |
|---|---|---|
| 1 | case 1, then break | "one" |
| 2 | case 2, then break | "two" |
| 3 | case 3, then break | "three" |
| anything else | default | "unknown" |
Note that the book puts a break at the end of the default block too, even though nothing follows it. That is a good habit: if someone later adds a case after the default, the break is already there.
Definition probe
A switch tests one expression against constant values. A chain can test anything.
Sort into buckets
Sort each decision by the structure that fits.
Fill the middle
Group two cases and supply a fallback.
Fill in the blanks
switch (day) case} "Sunday":
System.out.println("Weekend");
break;
default:
System.out.println("Weekday");
}
Why: Two case labels stacked with nothing between them share the block that follows, which is how grouping works. The break stops execution falling through into the weekday block, and default catches every value that matched no case.
Edge cases
Push on where the switch statement stops being usable.
Discussion prompt
The examples switch on an int and on a String. Could you switch on x > 5? On a double? Work out why each would or would not make sense before looking it up.
Hint: What has to be written after the word case?
Answer:
Not x > 5 — that is a boolean, and while it is technically one value, a switch on it would have only two cases and an if-else says it better.
Not a double. Java forbids it, and Lesson 2b explains why: floating-point values are approximate, so testing one for exact equality against a constant is unreliable. A case label has to be a value you can match exactly.
What works: integer types, characters, Strings, and enumerated types you will meet later. The common thread is that each has exact, discrete values. That is the real boundary — a switch matches exact values, so it can never express a range, which is why the grading example needs a chain.
Comparison
Fill the blanks to keep them apart.
Comparison matrix
| form | how many branches run | tests | best for |
|---|---|---|---|
| if | zero or one | one boolean condition | an optional action |
| if-else | exactly one | one boolean condition | two alternatives |
| chained else if | exactly one | several conditions, in order, until one is true | several mutually exclusive cases, including ranges |
| switch | one block, unless a break is missing | one expression against exact constant values | many specific values, especially when grouped |
The third row is the one to reach for most often. A switch is better only when you are matching exact values — it cannot express a range, which rules it out for anything involving greater-than.
Pattern
Three habits from this lesson prevent nearly every conditional bug a beginner writes.
// 1. always braces, even for one statement
if (x > 0) {
System.out.println("positive");
}
// 2. no semicolon after the condition
if (x > 0) { ... } // not if (x > 0);
// 3. a chain uses else; separate ifs do not
if (score >= 90) { ... }
else if (score >= 80) { ... }
else { ... }| habit | the bug it prevents |
|---|---|
| always write braces | a later-added statement silently leaving the branch |
| no semicolon after a condition | the branch controlling an empty statement |
| use else in a chain | every case being tested and the last one winning |
| end every switch case with break | falling through into the next case |
= is gets; == is equals.Check
Work it out before you click.
int x = 0;
if (x > 0) {
System.out.println("A");
} else if (x < 0) {
System.out.println("B");
} else {
System.out.println("C");
}| condition | for x = 0 |
|---|---|
| x > 0 | false |
| x < 0 | false |
| else | runs |
Check your understanding
What does this display?
Answer: A
Why: Zero is neither greater than nor less than zero, so both conditions are false and the final else branch runs. In a chain exactly one branch always runs when there is a bare else at the end, because that branch catches everything the earlier conditions did not.
Check
Work it out before you click.
int n = 3;
if (n % 2 == 0);
{
System.out.println("even");
}| element | role |
|---|---|
| if (n % 2 == 0) | the condition |
| ; | an empty statement — the if's body |
| { println(...); } | an unconditional block |
Check your understanding
What does this display, given that n is 3?
Answer: A
Why: The semicolon becomes the entire body of the if statement, so the condition controls an empty statement that does nothing. The braced block that follows is independent and runs unconditionally, printing 'even' for every value of n including odd ones.
Check
Work it out before you click.
int n = 2;
switch (n) {
case 1:
case 2:
System.out.println("small");
case 3:
System.out.println("medium");
break;
default:
System.out.println("large");
}| step | what happens |
|---|---|
| match case 2 | no statements of its own |
| fall into the shared block | prints small |
| no break | falls into case 3 |
| case 3's statements | prints medium, then break |
Check your understanding
What does this display?
Answer: A
Why: Cases 1 and 2 share a block that prints 'small', and because that block has no break, execution falls through into case 3 and prints 'medium' before the break finally exits the switch. Grouping labels is intentional fall-through; the missing break after 'small' almost certainly is not.
Real world
The missing-braces hazard is not hypothetical. It shipped in production code used by hundreds of millions of people.
Discussion prompt
In 2014 a duplicated line inside a braceless if statement disabled SSL certificate verification in Apple's software. Given what you now know, list everything that would have had to go wrong for that bug to reach users — and which single habit would have stopped it.
Hint: There were at least three separate opportunities to catch it.
Answer:
The chain was: a duplicated line was written; the indentation made it look conditional; the language permitted braceless branches so the compiler said nothing; no automated style check flagged it; and code review did not catch it because the code looked right.
The single habit that would have stopped it is always writing braces. With braces the duplicated line would have been inside the block and harmless. Not carefulness — carefulness is what failed at three separate points.
That is the general lesson worth taking: prefer rules that remove the possibility of a mistake over habits of vigilance. It is the same reasoning behind final in Lesson 3a and behind declaring types at all — let the structure of the code, or the compiler, do the remembering.
Commit first
Commit to an answer and to your confidence.
Predict first
How many branches of an if-else statement run?
Correct: Exactly one, always
Why: The condition must evaluate to a boolean, which has exactly two possible values, so one branch handles true and the other handles false and precisely one of them runs. Note the contrast with a plain if with no else, where zero or one statement blocks run — that is the case the third option describes. Braces affect which statements are IN a branch, never how many branches run.
Explain it
Two minutes, out loud, with the code in front of you.
Discussion prompt
A classmate wrote if (x > 0) System.out.println("a"); System.out.println("b"); on separate indented lines and cannot understand why 'b' always prints. Explain what the compiler sees, and then explain why you would tell them to always write braces rather than just to be careful.
Hint: The second half is the more important one.
Answer:
Without braces, a branch is exactly one statement — the very next one. So the compiler reads it as though the first println were wrapped in braces and the second were outside entirely. Your indentation says something the compiler never reads.
And the reason to always write braces is not that you are careless. Apple shipped this exact bug in security code that had been reviewed. Being careful failed. Writing the braces makes the mistake impossible rather than merely unlikely.
If your explanation stopped at the first paragraph, it was correct and less useful. The interesting claim is the second one, and it generalises well beyond braces.
Exit ticket
One question before you close the deck.
Predict first
Why does the last branch of if (x > 0) ... else if (x < 0) ... else ... need no condition?
Correct: Because it is reached only when both earlier conditions were false, which leaves only one possibility
Why: In a chain each branch is reached only when every earlier condition failed. By the final else we know x is not greater than zero and not less than zero, so it must be zero — there is no other possibility to distinguish. else has no fixed meaning of its own; it means whatever is left over, which is exactly why it guarantees that some branch always runs.
Connect it up
One page, from memory.
Draw it
Draw a flowchart for the three-way chain that classifies x as positive, negative or zero, marking which conditions are tested for an input of -5 and which are skipped. Beside it, write out the same logic as a nested conditional. Then list the three punctuation hazards from this lesson — the stray semicolon, the missing braces, and the missing break — and for each, one sentence on what it does to the program.
Recap
Four sections that add decision-making, and three punctuation hazards that come with it.
| if you remember one thing | it is this |
|---|---|
| about operators | one equals gets, two equals asks |
| about punctuation | a line ends with a semicolon or a brace, never both |
| about structure | always write the braces |
==, !=, >, <, >=, <= — and =< and => do not exist.= is assignment, == is comparison, and Java catches the confusion because a condition must be a boolean.else if; each branch is reached only when the earlier ones failed.break, and grouped labels share a block.Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.