Relational Operators, if-else, and switch

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

What this lesson covers

The lesson, slide by slide

1. Relational Operators, if-else, and switch

Title

Think Java 2e · Chapter 5 · Conditionals and Logic

Sections 5.1-5.4 · pp. 71-77

2. What you will be able to do

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.

3. Retrieve before you read

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.

4. The chapter that makes programs react

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.

5. Relational operators and boolean

Section

Section 5.1

6. Six operators that produce true or false

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
operatormeanswith x = 5, y = 3
==equal tofalse
!=not equal totrue
>greater thantrue
<less thanfalse
>=greater than or equal totrue
<=less than or equal tofalse

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.

7. Three ways to write these wrongly

Notation

The operators themselves are familiar. The mistakes are specific to Java and worth naming before you make them.

Annotate

  • A common error is a single = 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.
  • The operators =< and => do not exist. The order is 'less than or equal', so the angle bracket comes first: <= and >=.
  • The two sides have to be compatible. 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.
  • Different numeric types are fine, because Java applies the same conversion rules as with assignment. Evaluating 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.

8. Evaluating a relational expression by hand

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);
expressionafter substitutionresult
x == y7 == 7true
x != y7 != 7false
x >= y7 >= 7true — '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.

9. What does this display?

Prediction

Relational operators on equal values.

int a = 4, b = 4;
System.out.println(a > b);
System.out.println(a >= b);
expressionvaluesresult
a > b4 > 4false — strictly greater
a >= b4 >= 4true — or equal to

Predict first

What are the two values printed?

  • false then true
  • true then true
  • false then false
  • true then false

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.

10. A boolean is a value like any other

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.

typeexample valuesproduced by
int1, -5, 719arithmetic operators
double2.54, 0.98333arithmetic on doubles
String"Hello"quotation marks, concatenation
booleantrue, falserelational 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.

11. Using = where you meant ==

Trap

The 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 wrotewhat it meansresult
x = 0assign 0 to xan int, not a boolean
if (an int)a condition must be booleancompile 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.

The fix

== asks the question; = performs the action.

int x = 5;
if (x == 0) {             // comparison
    System.out.println("x is zero");
}
operatorkindproduces
=assignmentstores a value; the expression's type is the variable's
==relationala 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.

12. Legal or not?

Definition probe

The two sides of a relational operator must be compatible.

Sort into buckets

Sort each expression.

legal Java
5 < 6.0; x != y; x <= y
not legal
5 < "6"; x =< y
ok
Both sides are compatible types — Java converts between numeric types automatically — and the operator is spelled correctly.
no
Either the operands cannot be compared (an int against a String) or the operator does not exist: Java has <= and >=, never =< or =>.

13. Match the operator to its meaning

Matching

Four of the six.

Match the pairs

  • a. ==
  • b. !=
  • c. >=
  • d. =
  • r1. asks whether two values are equal
  • r2. asks whether two values differ
  • r3. asks whether one is greater than or equal to the other
  • r4. stores a value in a variable — not a comparison at all

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.

14. Why does Java need a boolean type at all?

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.

15. The if-else statement

Section

Section 5.2

16. A condition decides which statements run

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");
}
formbrancheshow many run
ifonezero or one
if-elsetwoexactly 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.

17. if against if-else

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.

18. Tracing an if-else with two different inputs

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");
}
xx % 2conditionbranch takenoutput
400 == 0 is truethe if branchx is even
711 == 0 is falsethe else branchx is odd
00truethe if branchx is even
-3-1falsethe else branchx 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.

19. Which branch runs?

Prediction

One specific input.

int x = -4;
if (x > 0) {
    System.out.println("positive");
} else {
    System.out.println("not positive");
}
stepvalue
the condition-4 > 0
evaluates tofalse
branch takenelse

Predict first

What is displayed?

  • not positive
  • positive
  • both lines
  • nothing

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.

20. The condition can be any boolean expression

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.

conditionlegal?why
if (x > 0)yesa relational expression, producing a boolean
if (x % 2 == 0)yesarithmetic, then a comparison
if (isSingleDigit(x))yesa method returning a boolean — Section 5.8
if (x)no, if x is an intan int is not a boolean
if (x = 0)noan 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.

21. A semicolon after the condition

Trap

The 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 seeseffect
if (x % 2 == 0)a condition...
;...whose body is an EMPTY statement
{ println(...); }a separate block, run unconditionally
output for x = 1x 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.

The fix

No semicolon after the condition. A block follows, defined with braces.

int x = 1;
if (x % 2 == 0) {
    System.out.println("x is even");
}
rulecheck
no semicolon at the end of an if or else linea brace follows instead
each line ends with a semicolon OR a bracenever 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.

22. What happens with the stray semicolon?

Prediction

x is 1, which is odd.

int x = 1;
if (x % 2 == 0);
{
    System.out.println("x is even");
}
partrole
if (x % 2 == 0)a condition
;an empty statement — the if's entire body
{ ... }an independent block

Predict first

What does this display?

  • x is even — the block runs regardless of x
  • nothing, because x is odd
  • a compile error
  • x is even, then a run-time error

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.

23. Write an if-else

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.

24. Why must exactly one branch run?

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.

25. Braces are optional, and you should write them

Section

Section 5.2, continued

26. Without braces, only the next statement is conditional

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");
linepart of the if?runs when x <= 0?
println("x is positive")yes — the one statement after the conditionno
println("x is not zero")no — despite the indentationyes

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.

27. What you wrote against what the compiler sees

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.

28. The goto fail bug

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
lineconditional?consequence
the first callyescorrect
the duplicated second callno — no bracesjumps 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.

29. What does this print when x is -5?

Prediction

Read the braces, not the indentation.

int x = -5;
if (x > 0)
    System.out.println("A");
    System.out.println("B");
lineconditional?runs?
println("A")yesno — x is not > 0
println("B")noyes — unconditional

Predict first

What is displayed?

  • B
  • nothing
  • A then B
  • A

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.

30. The rule, and the tool that enforces it

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.

31. Adding a statement to a braceless branch

Trap

The 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
versionx = 5x = -5
originalpositive(nothing)
after the editpositive / and nonzeroand 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.

The fix

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
}
versionx = 5x = -5
with bracespositive / 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.

32. Find the three punctuation faults

Error analysis

Three separate mistakes from this section, in one fragment. Each compiles.

Annotate

  • Line 1: a stray semicolon after the condition. The if's body becomes an empty statement, so the condition controls nothing at all.
  • Lines 2-3: no braces, two statements. Even without the semicolon, only the first would be conditional. With the semicolon, neither is.
  • Line 4: = 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.
  • Ranking them by danger: the assignment fails to compile, so you find it immediately. The other two compile and produce wrong output silently, which makes them far worse.
  • All three are prevented by two habits: always write braces, and never put a semicolon at the end of an if line.

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.

33. Which statement about braces survives?

Two truths and a lie

Four claims. Three are false.

Eliminate the wrong options

Eliminate the false claims and keep the true one.

  • A. Braces are required for every if statement.
  • B. Without braces, only the next single statement is part of the branch.
  • C. Indentation determines which statements are in the branch.
  • D. Omitting braces is a compile error.

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.

34. Is there ever a good reason to omit the braces?

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.

35. Chaining and nesting

Section

Section 5.3

36. Chaining: a series of else if blocks

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");
}
xx > 0x < 0branch takenoutput
5truenot testedfirstx is positive
-5falsetruesecondx is negative
0falsefalsethe final elsex 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.

37. Why the last branch needs no test

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

  • Each branch is reached only when all the earlier conditions were false. That is what makes a chain different from a sequence of separate if statements.
  • At the last branch we know x is not positive and not negative. There is no need to test whether x is 0, because there is no other possibility.
  • Writing 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.
  • A bare 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.
  • Keep the statements and braces lined up. Standard indentation makes a long chain readable and makes syntax errors less likely.

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.

38. The same logic, nested instead of chained

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");
    }
}
structurechained versionnested version
outer conditionalif / else if / elseif / else — two branches
second branch containsanother condition on the same levela whole conditional statement
behaviouridenticalidentical
readabilityflatterdeeper — 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.

39. What does this print for x = 0?

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");
}
conditionvalue for x = 0reached?
x > 0falseyes — and fails
x < 0falseyes — and fails
else—yes — this branch runs

Predict first

What is displayed?

  • x is zero
  • x is positive
  • x is negative
  • nothing

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.

40. Chaining or nesting: which to write

Concept

They can express the same logic, so the choice is about what a reader will find clearest.

situationpreferwhy
several alternatives at the same levelchainingone indentation level; reads as a list of cases
a decision that only matters inside anothernestingthe structure mirrors the logic
more than about four casesa switch, if the cases are valuesSection 5.4
two conditions that must both holda 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.

41. A chain of separate ifs is not a chain

Trap

The 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"; }
scoreconditions that are truefinal grade
95all fourD
85the last threeD
75the last twoD
50only the lastD

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.

The fix

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"; }
scorefirst condition that is truegrade
95score >= 90A
85score >= 80B
75score >= 70C
50none — the final elseD

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.

42. Order these conditions correctly

Ranking

For a grading chain using else if, from the branch that must come first.

Put in order

  1. score >= 90 gives A
  2. score >= 80 gives B
  3. score >= 70 gives C
  4. else gives D

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.

43. Chain or independent ifs?

Discrimination

The presence of else changes the meaning entirely.

Sort into buckets

Sort each situation by which structure it needs.

a chain, with else
assign exactly one grade from a score; categorise a number as positive, negative or zero
separate if statements
print a warning if the value is negative, AND a warning if it is very large; check several unrelated settings and act on each
chain
The cases are mutually exclusive — exactly one should apply — so each branch must be reached only when the earlier ones failed.
sep
The conditions are independent and more than one may hold at once, so each needs testing regardless of the others' outcomes.

44. Rewrite a nested conditional as a chain

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.

45. The switch statement

Section

Section 5.4

46. Many possible values of one expression

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;
}
numbercase matchedword becomes
1case 1"one"
2case 2"two"
3case 3"three"
7none — 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.

47. The chain and the switch, side by side

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.

48. Grouping several cases together

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;
}
foodcase matchedoutput
"apple"case "apple" — falls throughFruit!
"banana"case "banana" — falls throughFruit!
"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.

49. What does word become?

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";
}
stepwhat happensword
match case 1word = "one""one"
no break — fall throughword = "two""two"
breakleave the switch"two"

Predict first

What is word after the switch?

  • "two"
  • "one"
  • "unknown"
  • a compile error

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.

50. What break does, and what happens without 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 breakwithout break
execution leaves the switch after the case's statementsexecution continues into the next case's statements
exactly one block runsseveral blocks may run
what you almost always wantwhat you want only when grouping labels
required at the end of each casedeliberate, 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.

51. A missing break

Trap

The trap

One break omitted, and two cases run.

switch (number) {
    case 1:
        word = "one";
        // no break
    case 2:
        word = "two";
        break;
    default:
        word = "unknown";
}
numberwhat runsword ends as
1case 1's statement, then falls into case 2"two" — wrong
2case 2's statement, then break"two" — correct
7default"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.

The fix

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;
}
numberrunsword
1case 1, then break"one"
2case 2, then break"two"
3case 3, then break"three"
anything elsedefault"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.

52. switch or chain?

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.

a switch fits well
convert 1, 2, 3 into 'one', 'two', 'three'; act on a menu choice of 'add', 'delete' or 'quit'
a chain is needed
categorise a score as A, B, C or D by range; check whether a value is positive, negative or zero
sw
One expression is being compared against a set of specific constant values, which is exactly what a switch expresses — and several values can share a block.
ch
The tests are RANGES or relationships rather than exact values. A switch case matches one value, so it cannot express 'greater than 90'.

53. Complete the switch

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.

54. What can a switch actually test?

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.

55. Four ways to make a decision

Comparison

Fill the blanks to keep them apart.

Comparison matrix

formhow many branches runtestsbest for
ifzero or oneone boolean conditionan optional action
if-elseexactly oneone boolean conditiontwo alternatives
chained else ifexactly oneseveral conditions, in order, until one is trueseveral mutually exclusive cases, including ranges
switchone block, unless a break is missingone expression against exact constant valuesmany 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.

56. The pattern to carry away

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 { ... }
habitthe bug it prevents
always write bracesa later-added statement silently leaving the branch
no semicolon after a conditionthe branch controlling an empty statement
use else in a chainevery case being tested and the last one winning
end every switch case with breakfalling through into the next case

57. Check: which branch

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");
}
conditionfor x = 0
x > 0false
x < 0false
elseruns

Check your understanding

What does this display?

  • A. C (correct)
  • B. A
  • C. B
  • D. nothing

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.

Why B tempts people
This would require x > 0, but x is exactly 0 and the operator is strictly greater than.
Why C tempts people
This would require x < 0, and 0 is not less than itself.
Why D tempts people
A chain ending in a bare else always runs exactly one branch — there is no input that falls through it.

58. Check: the stray semicolon

Check

Work it out before you click.

int n = 3;
if (n % 2 == 0);
{
    System.out.println("even");
}
elementrole
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?

  • A. even (correct)
  • B. nothing
  • C. odd
  • D. a compile error

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.

Why B tempts people
This assumes the block is still governed by the condition, but the semicolon ended the if statement before the block began.
Why C tempts people
There is no code here that prints 'odd' at all.
Why D tempts people
It compiles cleanly — an empty statement is legal Java, which is exactly why this bug is dangerous.

59. Check: switch fall-through

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");
}
stepwhat happens
match case 2no statements of its own
fall into the shared blockprints small
no breakfalls into case 3
case 3's statementsprints medium, then break

Check your understanding

What does this display?

  • A. small then medium (correct)
  • B. small
  • C. medium
  • D. small, medium, large

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.

Why B tempts people
This assumes execution stops at the end of the case's statements, but only a break leaves a switch.
Why C tempts people
Case 2 matches first, so 'small' is printed before execution reaches case 3.
Why D tempts people
The break at the end of case 3 exits the switch, so the default block is never reached.

60. goto fail, and what it cost

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.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

How many branches of an if-else statement run?

  • Exactly one, always
  • One or both, depending on the condition
  • Zero or one
  • It depends on whether braces are used

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.

62. Explain it to someone else

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.

63. Exit ticket

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?

  • Because it is reached only when both earlier conditions were false, which leaves only one possibility
  • Because else always means 'x equals zero'
  • Because Java requires the last branch to have no condition
  • Because the compiler works out the remaining case automatically

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.

64. Draw the whole lesson

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.

65. Recap

Recap

Four sections that add decision-making, and three punctuation hazards that come with it.

if you remember one thingit is this
about operatorsone equals gets, two equals asks
about punctuationa line ends with a semicolon or a brace, never both
about structurealways write the braces

Sources

  1. 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
  2. The Java Tutorials — The if-then and if-then-else Statements
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108