3.2
Conditionals
Use if and switch to make a program choose what to do.
Before you start
Topics used in this lesson
Prerequisites
Section 3.1 gave you the relational operators, which turn numbers into true or false, and the logical operators that combine them. You also saw that && stops early, which is what makes a null check safe.
That short circuit becomes load bearing in this section. Nearly every condition you write from here on relies on it.
Why this matters
Everything you have written so far runs from top to bottom. Most programs also need to choose which lines to run. A login can show one message for an accepted password and another for a rejected password. A price can change when the quantity reaches a discount level.
Java uses if when the choice is based on a boolean condition. It uses switch when one value
is compared with a fixed set of alternatives.
Core explanation
if, else if, else
public class GradeDemo {
public static void main(String[] args) {
int score = 84;
char grade;
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else {
grade = 'C';
}
System.out.println(grade);
}
}Output:
BThe condition in the brackets must give a boolean. Not a number, not null, not an object. In C, if (5) is legal and means true. In Java it does not compile.
Java is refusing a whole category of bug there, the classic being if (x = 5), which in C assigns 5 and is always true. In Java that line is a type error, because assignment gives back a number and if wants a boolean.
switch
When you are checking one value against many fixed options, a ladder of if statements gets noisy. switch says it directly.
public class DayType {
public static void main(String[] args) {
int day = 6;
String type;
switch (day) {
case 6:
case 7:
type = "weekend";
break;
case 1:
type = "monday";
break;
default:
type = "midweek";
}
System.out.println(type);
}
}The value inside switch (day) is the selector. Java compares it with each case value. When
it finds a match, it runs the statements under that label.
Read the control flow from top to bottom:
case 6:andcase 7:stacked together means “either of these”.default:catches everything else. It is optional.- Without
break, execution continues into the statements under the next label.
Branch on keyboard input
Section 2.1 showed how to read a line with
Scanner. Put that value in a condition and the same compiled program can take different paths:
import java.util.Scanner;
public class AccessMessage {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Type admin or guest: ");
String role = input.nextLine();
if ("admin".equals(role)) {
System.out.println("Settings unlocked");
} else {
System.out.println("Read-only access");
}
}
}Compile and run the program, then type admin. Run it again and type guest. The same compiled program follows a different branch because the input value changed.
Optional internals
Going deeperOpen optional internalsYou can finish the core lesson without this section.
Fallthrough, and why it exists
Without break, switch does not stop at the end of a case. It carries on.
switch (day) {
case 1:
result = "monday"; // no break
case 2:
result += " tuesday";
break;
}
// day = 1 gives you "monday tuesday"This behaviour is called fallthrough. It is almost always a bug when it happens by accident, and Java gives you no warning at all.
So why keep it? Because of the stacked-label shape:
case 6:
case 7:
type = "weekend";
break;case 6: has an empty body, so it falls straight into case 7:. That useful pattern and the dangerous bug are the same mechanism.
Arrow labels and switch expressions
Java 14 made arrow labels and switch expressions permanent language features.
String type = switch (day) {
case 6, 7 -> "weekend";
case 1 -> "monday";
default -> "midweek";
};| Colon labels | Arrow labels | |
|---|---|---|
| Fallthrough | yes, and silent | none. Each case is separate. |
| Leaving a statement case | usually break, return or throw | the arm ends by itself |
| Several values | stack the case labels | case 6, 7 -> |
| Use | common in older switch statements | preferred when fallthrough is not intended |
Label syntax and construct type are separate choices. A switch statement performs work. A switch expression produces a value and must cover every possible selector value. Arrow labels work in either construct. The example above is an expression because its result is assigned to type.
What classic switch accepts
| Allowed | Not allowed | |
|---|---|---|
| Whole numbers | byte, short, char, int and their wrappers | long and Long |
| Decimals | none | float, double |
| Other | String, enum | boolean |
These are language rules. They do not promise one bytecode or machine-code strategy. A compiler can use a dense table for dense integer cases or a lookup for sparse cases. String switching must still distinguish different strings that have the same hash code.
Use switch because it states a fixed choice over one selector. Use if for ranges, unrelated conditions, or conditions that depend on several values.
Passing null to a classic String, wrapper, or enum switch throws NullPointerException. Check for null first when it is a valid input state.
Floating-point equality
System.out.println(0.1 + 0.2 == 0.3); // falseYou already know why, from Section 2.2. Those three numbers have no exact form in binary, so the closest-to-0.1 plus the closest-to-0.2 is not bit for bit the closest-to-0.3.
If the domain means “close enough,” define a tolerance that matches its units and scale:
double tolerance = 0.000001;
if (Math.abs(a - b) <= tolerance) { } // domain-specific absolute toleranceOne fixed epsilon is not correct for every magnitude. Some programs use a relative tolerance,
some require exact bit-level equality, and money often needs decimal arithmetic such as
BigDecimal. Choose the comparison contract before choosing the code.
The text-comparison rule for now
Use "admin".equals(typed) when a condition must compare text. Do not use typed == "admin".
The full reason requires objects, references, and the String pool, which have not been taught yet. Section 8.3 explains the equals method contract. Section 9.1 then explains why == asks a different question for String values.
The ternary, used well
String label = (count == 1) ? "item" : "items";Use it to choose between two short value expressions. If either branch performs several actions,
or nesting makes the types and grouping hard to read, use an if statement.
Tradeoffs
Deep if nesting becomes difficult to trace. Use an else if chain when the program must select
one result from an ordered set of conditions:
if (!hasTicket) {
System.out.println("ticket required");
} else if (!seatAvailable) {
System.out.println("no seat available");
} else {
System.out.println("seat confirmed");
}The constant-label switches in this lesson match one selector against fixed alternatives. Use if for ranges or unrelated boolean conditions. Modern Java also has pattern switch, which belongs after classes and inheritance.
Classic switch permits fallthrough, which can be useful for grouped behaviour but is easy to introduce accidentally. Arrow labels do not fall through.
For text input, use equals to test the characters. The object-level reason is deliberately deferred until the required object model has been taught.
Check yourself
Answer from memory before opening the explanation. Then compare your rule with the answer and revisit the example if they differ.
A learner types `admin`, but the program follows the guest branch. Which three points should you check first?
Show the answer
First, confirm that
nextLine()stored the text in the variable used by the condition. Second, compare text with"admin".equals(role), not==. Third, check the order of theifandelse ifconditions so an earlier broad condition is not taking the value first.Print the value before the condition when you are unsure what the program received.
Why does `switch` refuse a `long`, when it happily accepts `byte`, `short`, `char` and `int`?
Show the answer
That is a rule of the Java language, not a portable conclusion about one mandatory machine layout. Classic switch selectors support
char,byte,short,int, their wrappers,Stringand enum types. They do not supportlong,float,doubleorboolean.A compiler may emit a table switch, a lookup switch, or other code depending on the cases. Do not choose
switchbecause you assume it is faster. Choose it when one selector is being matched against fixed alternatives.In the old style `switch`, what happens if you forget a `break`, and why was that ever considered a good idea?
Show the answer
Execution carries on into the next case and keeps going until it hits a
breakor the end. This is called fallthrough.It is a bug almost every time it happens by accident, and it is silent. No warning, just extra code running.
It was kept because of one useful shape: stacking labels so several values share one block, like
case 6: case 7: return "weekend";. That works because of fallthrough.Modern arrow switch (
case 6, 7 -> ...) gives you that shape directly and prevents accidental fallthrough. Prefer it when fallthrough is not part of the design.
Exercises
Start with the warm-ups. Continue to practice exercises when the basic idea works. Challenges are optional: skip them on a first pass and return after the next lesson.
- Warm up: one idea with close guidance.
- Practice: combine the idea with earlier material.
- Challenge: investigate an edge case or implementation detail.
4 exercises85 pointsabout 95 minutes
Read a Choice and Branch
ex-3-2-aWrite a small program that behaves differently for two keyboard inputs.
Run the starter and type admin. At first it prints an empty result because accessFor is unfinished. Complete the three branches and run it again with admin, guest, and another word.
For now, remember one rule for text: use equals to ask whether two Strings contain the same characters. Section 8.3 explains the method contract, and Section 9.1 explains why == asks a different question.
Starter code
This exercise includes a method so the checker has a consistent entry point. Methods are taught in Phase V. For now, change only the lines marked TODO.
Required class and methods
Keep these names and parameter types. The checker uses them to find your code.
class Access {
static String accessFor(String role)
}Return settings for admin, read-only for guest, and denied for every other value. A null value must also return denied.
Steps and success checks
- Run the starter once and type admin
- Return settings when the role text is admin
- Return read-only when the role text is guest
- Return denied for any other text, including an empty line
public class Access {
// Return "settings", "read-only", or "denied".
static String accessFor(String role) {
return ""; // TODO: write an if / else-if / else chain
}
public static void main(String[] args) {
java.util.Scanner input = new java.util.Scanner(System.in);
System.out.print("Type admin or guest: ");
String role = input.nextLine();
System.out.println(accessFor(role));
}
}
Hint 1
"admin".equals(role), not role == "admin".Hint 2
else if. The final else handles every value that matched neither word.Hint 3almost the answer
if ("admin".equals(role)) return "settings"; else if ("guest".equals(role)) return "read-only"; else return "denied";Find and Fix switch Fallthrough
ex-3-2-bThe provided method has one missing break. Read the cases and predict which months return the wrong season.
Write down the incorrect months and their returned value, then run the program and compare the result with your prediction.
Add the missing break. Then write the same selection with arrow cases and explain why an arrow case cannot continue into the next case.
Starter code
This exercise includes a method so the checker has a consistent entry point. Methods are taught in Phase V. For now, change only the lines marked TODO.
Required class and methods
Keep these names and parameter types. The checker uses them to find your code.
class Fall {
static String describeBroken(int month)
static String describeFixed(int month)
static String describeArrow(int month)
}Keep describeBroken exactly as it is, missing break and all. describeArrow uses the arrow form of switch, which cannot fall through.
Steps and success checks
- Find which months the broken version gets wrong, without changing it
- Say how many of the twelve it gets right
- Write the fixed version and the arrow version
- Say why grouped case labels are not the same thing as fallthrough
public class Fall {
// Leave this one broken. Find the missing break before you look at anything else.
static String describeBroken(int month) {
String season = "";
switch (month) {
case 12: case 1: case 2: season = "winter"; break;
case 3: case 4: case 5: season = "spring";
case 6: case 7: case 8: season = "summer"; break;
case 9: case 10: case 11: season = "autumn"; break;
default: season = "not a month";
}
return season;
}
// The same thing, fixed.
static String describeFixed(int month) { return ""; } // TODO
// The same again with the arrow form, which cannot fall through at all.
static String describeArrow(int month) { return ""; } // TODO
public static void main(String[] args) {
// TODO: print all three for months 1 to 13 and find where they disagree
}
}
Hint 1
break. Nine of the twelve months are still correct.Hint 2
case 12, 1, 2 -> "winter"; and it never falls through, so this bug cannot be written in it at all.Hint 3almost the answer
Comparing Decimals Safely
ex-3-2-cWrite two ways to decide whether double values are close enough for a calculation.
First, compare the absolute difference with a fixed tolerance. Confirm that it treats 0.1 + 0.2 as close to 0.3.
Next, test values near 1e15. A fixed tolerance that works near zero may be too small at that scale because the spacing between adjacent double values grows with magnitude.
Implement the relative comparison and state which tolerance rule you would use for the sample values. The required tolerance depends on the units and acceptable error of the program using it.
Starter code
This exercise includes a method so the checker has a consistent entry point. Methods are taught in Phase V. For now, change only the lines marked TODO.
Required class and methods
Keep these names and parameter types. The checker uses them to find your code.
class Compare {
static boolean closeEnough(double a, double b)
static boolean closeRelative(double a, double b)
static double addTenth()
static boolean naiveEquals()
}closeEnough uses a fixed tolerance. closeRelative scales the tolerance to the size of the numbers. Both must say a value equals itself, including zero.
Steps and success checks
- Show that 0.1 + 0.2 does not equal 0.3
- Write a fixed tolerance version and show it handles that case
- Find where the fixed tolerance stops working
- Write a relative version and say when you would use each
public class Compare {
// A fixed tolerance.
static boolean closeEnough(double a, double b) { return false; } // TODO
// A tolerance that scales with the size of the numbers.
static boolean closeRelative(double a, double b) { return false; } // TODO
static double addTenth() { return 0; } // TODO: 0.1 + 0.2
static boolean naiveEquals() { return true; } // TODO: 0.1 + 0.2 == 0.3
public static void main(String[] args) {
// TODO: print the naive comparison and the real value of 0.1 + 0.2
// TODO: try closeEnough on 1e15 and 1e15 + 1. Predict first.
}
}
Hint 1
Math.abs(a - b) is the comparison.Hint 2
Hint 3almost the answer
a == b first, or comparing zero with zero divides by zero.Registry Status Rules
ex-3-2-dImplement the five deployment rules in the order shown in the starter code.
Check the absolute blockers first: inactive status, zero supplies, and medical hold. Then calculate the required readiness from the clearance level and compare it with the supplied readiness.
Return a specific reason for every refusal so the caller can tell which rule failed.
Finally, add one more rule of your own. Record where it belongs in the order and which result should win when more than one rule fails.
Starter code
This exercise includes a method so the checker has a consistent entry point. Methods are taught in Phase V. For now, change only the lines marked TODO.
Required class and methods
Keep these names and parameter types. The checker uses them to find your code.
class Status {
static String deploymentDecision(boolean isActive, double readiness, boolean onMedicalHold, int clearanceLevel, int supplies)
}Return exactly "DEPLOY" when the unit can go. Otherwise return a string starting with "NO" that names the rule that stopped it, using the words active, supplies, medical or readiness.
Steps and success checks
- Implement all five rules and give a specific reason for every refusal
- Work out which rules are absolute blockers and check those first
- Decide what happens at exactly 60.0 and exactly 40.0, and defend it
- Try a unit that breaks several rules and say which reason you report
public class Status {
// Rules for whether a unit can be deployed:
// 1. The unit must be active.
// 2. Readiness must be at least 60.0
// 3. A unit on medical hold cannot deploy, whatever its readiness.
// 4. A unit with clearance level 5 can deploy at readiness 40.0 or more.
// 5. A unit with zero supplies cannot deploy, no matter what.
//
// Return "DEPLOY", or a reason starting with "NO" that names the rule.
static String deploymentDecision(
boolean isActive, double readiness, boolean onMedicalHold,
int clearanceLevel, int supplies) {
return ""; // TODO
}
public static void main(String[] args) {
System.out.println(deploymentDecision(true, 75.0, false, 3, 100));
System.out.println(deploymentDecision(true, 45.0, false, 5, 100));
System.out.println(deploymentDecision(true, 95.0, true, 5, 100));
System.out.println(deploymentDecision(false, 95.0, false, 5, 100));
System.out.println(deploymentDecision(true, 95.0, false, 5, 0));
}
}
Hint 1
Hint 2
double required = clearance >= 5 ? 40.0 : 60.0; and then one comparison covers both.Hint 3almost the answer
>= for both threshold checks and test the boundary values directly.Where this leads
The Scanner setup returns in Section 8.1, after classes and objects are available. There you will trace standard input through byte streams, character decoding, buffering, and token parsing.
The text-comparison rule returns in Section 8.3 and Section 9.1. Those lessons supply the object and String models needed to explain it rather than asking you to memorise an unexplained rule.
Section 8.4 uses an exhaustive switch expression over an enum, where the compiler can reject a missing case.
switch is next used in 8.4 - Enums