Lesson 4Phase 1: First steps
Making decisions: if, else and switch
Choosing between paths with if and else, and picking one of many cases with switch statements and switch expressions.
By BasithUpdated 55 min
- if
- else if
- boolean conditions
- switch
- fall-through
- ternary
Until now every program you wrote ran straight down the page, top to bottom, every line every time. Real programs are not like that. They look at what is true right now and choose what to do next. That choosing is called a decision, and this lesson is about the handful of ways Java lets you write one.
- Write an
ifthat runs a block of code only when a condition is true. - Chain choices with
else ifandelse, and predict which branch runs. - Build a condition from comparisons and
&&,||and!, and avoid the=versus==trap. - Use the ternary
cond ? a : bto choose between two values. - Write a classic
switchwithbreak, and explain fall-through. - Write the arrow form of
switchand switch expressions that produce a value, usingyieldwhen a case needs several lines.
<, <=, >, >=, ==, !=) and the logical operators (&&, ||, !), all of which produce a boolean. We use only those plus variables and types. No loops or arrays appear here yet.The if statement runs a block when something is true#
The smallest decision in Java is the if statement. You give it a condition in parentheses and a block of code in braces. If the condition is true, the block runs. If it is false, the block is skipped and the program carries on below it.
public class Main { public static void main(String[] args) { int temperature = 35; if (temperature > 30) { System.out.println("It is hot today."); } System.out.println("Have a good day."); }}Read it slowly. temperature is 35, so temperature > 30 is true, and the line inside the braces runs. The last line is outside the braces, so it always runs no matter what. Set temperature to 20 and the first message disappears while the second stays. The braces mark exactly which lines belong to the decision.
Key ideaThe thing inside the parentheses is not a sentence Java reads in English. It is an expression that evaluates to a single
boolean, eithertrueorfalse. Theifonly cares about that one value.
count is true whenever count is not zero. Java accepts only a boolean, so you write the comparison out: if (count != 0).if (x > 0) System.out.println("positive");. It works, but it invites a classic mistake: you add a second line later, indent it to match, and expect it to belong to the if. Without braces, only the first statement does. Always write the braces. They cost two characters.public class Main { public static void main(String[] args) { int stock = 12; if (stock < 5) System.out.println("Running low"); System.out.println("Order more today"); System.out.println("Stock: " + stock); }}There are no braces after this if, and the second print is indented like the first. What does the program print?
It prints Order more today and then Stock: 12. The condition stock < 5 is false, so the if skips its statement, and without braces its statement is only the first print. The second print merely looks like part of the if. Java ignores indentation, so that line runs every time.
else and else if: choosing between branches#
An ifon its own answers a yes-or-no question. To say “do this, otherwise do that” you add an else. The else block runs exactly when the if condition was false. Together they form a fork in the road: exactly one of the two blocks runs.
public class Main { public static void main(String[] args) { int age = 16; if (age >= 18) { System.out.println("You may vote."); } else { System.out.println("Too young to vote."); } }}Often you have more than two cases. For that you stack conditions with else if. Java checks each condition from the top down and runs the block of the first one that is true. As soon as one matches, the rest are skipped entirely. A final bare else catches everything that fell through.
public class Main { public static void main(String[] args) { int n = -7; if (n < 0) { System.out.println(n + " is negative"); } else if (n == 0) { System.out.println(n + " is zero"); } else { System.out.println(n + " is positive"); } }}Press Run and step through. Watch the order: Java tests n < 0 first. Because n is -7, that is true, so the negative branch runs and the other two are never even looked at. The output is -7 is negative. With n at 0, the first test would fail, the second would succeed, and you would get the zero branch. The order of the tests is the order of the checking.
Key ideaIn an
if/else if/elsechain, exactly one branch runs: the first whose condition is true, or the finalelseif none of them are. Order matters, because checking stops at the first match.
temp > 30 and prints hot, then tests temp > 20 and prints warm, and otherwise prints cold. What does it print when temp is 30?A worked example: a grade from a score#
Here is a fuller example. A score from 0 to 100 becomes a letter grade with the usual thresholds: 90 and up is an A, 80 and up a B, 70 and up a C, 60 and up a D, anything lower an F. Each branch can assume the higher ones already failed, so it needs only a single lower bound.
public class Main { public static void main(String[] args) { int score = 73; char grade; if (score >= 90) { grade = 'A'; } else if (score >= 80) { grade = 'B'; } else if (score >= 70) { grade = 'C'; } else if (score >= 60) { grade = 'D'; } else { grade = 'F'; } System.out.println("Score " + score + " earns grade " + grade); }}Trace it. With score at 73: 73 >= 90 is false, 73 >= 80 is false, 73 >= 70 is true, so grade becomes 'C' and the chain ends. The program prints Score 73 earns grade C. A score of 90 would give an A, 59 an F, and the boundary 80 a B, because the test is >=, not >.
80 a B or a C? With score >= 80 it is a B; with score > 80 it would slip down to a C. Whenever you write a threshold, pick one exact value sitting on the line and trace it by hand. The boundary is the case worth testing.Trying other scores: reading the keyboard#
To see another grade, you would have to change 73 in the code and run it again. A program can ask for the score instead. This version reads whatever whole number is typed on the keyboard. The Keyboard input box under the code holds 80. Run it, then change the number to 79, 90 or 100 and run it again.
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Score: "); int score = in.nextInt(); char grade; if (score >= 90) { grade = 'A'; } else if (score >= 80) { grade = 'B'; } else if (score >= 70) { grade = 'C'; } else if (score >= 60) { grade = 'D'; } else { grade = 'F'; } System.out.println("Score " + score + " earns grade " + grade); }}Three new lines do the reading. Treat them as a recipe for now:
import java.util.Scanner;sits above the class. It tells Java the program usesScanner, a ready-made class from Java's library whose job is reading input.Scanner in = new Scanner(System.in);makes a scanner namedinthat reads from the keyboard.System.inis the keyboard, the waySystem.outis the screen.int score = in.nextInt();waits until a whole number is typed, then stores it inscore.
With 80 typed, the console shows the prompt Score: with the 80 after it, then Score 80 earns grade B. How a scanner works, and what else it can read, is the subject of Scanner and console applications. Until then, these three lines are all you need.
Tap a line, then tap where it should go. Or use the arrows.
Conditions are just boolean expressions#
Because a condition is only an expression that produces a boolean, everything you learned about logical operators applies directly. You can join several tests into one with && (and), || (or), and ! (not). This keeps a chain short when a branch really depends on two facts at once.
public class Main { public static void main(String[] args) { int age = 65; boolean isMember = true; if (age >= 60 && isMember) { System.out.println("Senior member discount"); } else if (age >= 60 || isMember) { System.out.println("Partial discount"); } else { System.out.println("No discount"); } }}Here age >= 60 is true and isMember is already a boolean that is true, so age >= 60 && isMember is true and the first branch wins: Senior member discount. Notice isMember needs no comparison. It is already a yes-or-no value, so it can stand on its own inside the parentheses.
= where you meant ==. = puts a value into a variable; == asks whether two values are equal. Writing if (isMember = true) does not test isMember; it sets it to true and then uses that. Java catches this for plain numbers (if (x = 5) will not compile, because 5 is an int, not a boolean), but with a boolean variable it slips through. Always write == for a comparison, and test a boolean variable on its own: if (isMember).isMember is a boolean holding false, and count is an int. Which condition compiles but quietly does the wrong thing?leap is true exactly for leap years. The program reads the year from the keyboard, and each test types a different one.Nested ifs: a decision inside a decision#
You can put an if inside another if. The inner one is only reached when the outer condition is already true, so together they ask “is this true, and within that, is this other thing true?”. A classic use is finding the largest of three numbers.
public class Main { public static void main(String[] args) { double a = -1.0; double b = 4.5; double c = -5.3; double largest; if (a >= b) { if (a >= c) { largest = a; } else { largest = c; } } else { if (b >= c) { largest = b; } else { largest = c; } } System.out.println("Largest is " + largest); }}Read it as a tree. The outer if (a >= b) splits the problem in two. On the “yes” side we already know a beats b, so we only need to compare a against c. On the “no” side b beats a, so we compare b against c. Here b is the largest, so it prints Largest is 4.5.
&& instead of nesting. Nesting reads best when the inner question only makes sense once the outer one is settled. If you find yourself nested three or four deep, that is usually a sign to step back and simplify, perhaps with combined conditions or, later, with methods.The ternary: a compact if for choosing a value#
Some decisions pick a value rather than an action. You want max to be whichever of two numbers is larger, or a label to be one word or another. Writing a whole if / else just to set one variable feels heavy. Java has a one-line form for exactly this: the ternary operator, written condition ? a : b.
Read it as a question. The part before the ? is the condition. If it is true, the whole expression becomes the value after the ?. If it is false, it becomes the value after the :. It is the only operator in Java that takes three parts, which is where the name ternary comes from.
public class Main { public static void main(String[] args) { int age = 17; String status = age >= 18 ? "adult" : "minor"; System.out.println("At " + age + " you are a " + status); int x = -4; int size = x < 0 ? -x : x; System.out.println("The size of " + x + " is " + size); }}Two uses, side by side. In the first, age >= 18 is false (age is 17), so status becomes "minor" and we print At 17 you are a minor. In the second, x < 0 is true, so size becomes -x, which is 4, and we print The size of -4 is 4. That second line is the absolute value, written in a single expression.
= or inside a larger expression. It is not a place to run actions. If a branch needs to print, update several variables, or do real work, use a full if / else. The ternary suits the small case “set this one thing to A or B” and becomes hard to read the moment you try to stretch it further.switch: one value, many cases#
A common kind of decision checks one variable against a list of exact values: if it is 1 do this, if it is 2 do that, and so on. An else if chain can do it, but it repeats the variable on every line. A switch names the value once and then lists the cases. Java has two ways to write the cases, an older one with colons and a newer one with arrows. You will meet both in real code, so this section covers both, starting with the older one.
The classic form: case, colon and break#
public class Main { public static void main(String[] args) { int day = 3; switch (day) { case 1: System.out.println("Monday"); break; case 2: System.out.println("Tuesday"); break; case 3: System.out.println("Wednesday"); break; default: System.out.println("Another day"); } }}Step through it. day is 3, so control jumps straight to case 3, prints Wednesday, and the break ends the switch. Each case label is the word case, a value and a colon. The default label is the catch-all, like the final else in a chain: it runs when no case matched. If day had been 9, you would see Another day.
Fall-through: the job break does#
That break after each case is doing real work. In the colon form, a case label only marks a place to start. Once Java has jumped to the matching label, it keeps running downward through the code of the following cases until it reaches a break or the end of the switch. That behavior is called fall-through. Forget one break and the program quietly does more than you meant.
public class Main { public static void main(String[] args) { int day = 2; switch (day) { case 1: System.out.println("Monday"); break; case 2: System.out.println("Tuesday"); case 3: System.out.println("Wednesday"); break; default: System.out.println("Another day"); } }}This is the day switch with one break missing, and day is now 2. What does it print?
It prints Tuesday and then Wednesday. Control lands on case 2 and prints Tuesday. There is no break there, so it falls through into case 3, prints Wednesday, and only stops at the break below that. The default is never reached.
Fall-through is sometimes useful. When several values should share one block, you stack their case labels with no code between them and let them all fall into a single body. Here both 'A' and 'B' lead to the same message.
public class Main { public static void main(String[] args) { char grade = 'B'; switch (grade) { case 'A': case 'B': System.out.println("Well done"); break; case 'C': System.out.println("Solid pass"); break; default: System.out.println("Keep working"); } }}With grade equal to 'B', control lands on case 'B'. There is no code directly under it, so it falls into the shared body and prints Well done, then break ends the switch. Since Java 14 you can also put both values in one label, case 'A', 'B':, which says the same thing more plainly.
Key ideaIn the colon form, Java jumps to the matching case and keeps running until it meets a
break. A forgottenbreakis the classicswitchbug.
The arrow form: no fall-through#
Java 14 added a second way to write the cases. Replace the colon with an arrow, ->, and the code after the arrow is the whole case. Here is the feedback switch again in the arrow form:
public class Main { public static void main(String[] args) { char grade = 'B'; switch (grade) { case 'A', 'B' -> System.out.println("Well done"); case 'C' -> System.out.println("Solid pass"); default -> System.out.println("Keep working"); } }}It prints Well done, and there is no breakanywhere. None is needed: after an arrow, Java runs only that case's code and then leaves the switch, so there is nothing to fall through into. Values that share a case go in one label, separated by commas, as in case 'A', 'B' ->. To run several statements in one case, put them in braces after the arrow: case 'C' -> { ... }. One switch uses one form: mixing arrows and colons in the same switch is a compile error, “different case kinds used in the switch”.
case 1: followed by statements and usually a break.case 1 -> followed by one statement, a block in braces, or a value.break stops it.break out of.case 1, 2:.case 1, 2 ->.Switch expressions: a switch that produces a value#
Look at what the day switch does in every case: it prints a name. It could just as well choose the name and leave the printing for later. A switch can produce a value directly, the way the ternary does. Used like this it is called a switch expression:
public class Main { public static void main(String[] args) { int day = 3; String name = switch (day) { case 1 -> "Monday"; case 2 -> "Tuesday"; case 3 -> "Wednesday"; default -> "Another day"; }; System.out.println(name); }}The whole switch (day) { ... } now stands for a value, and that value goes into name. Each case says which value to produce: with day equal to 3 it is "Wednesday", so the program prints Wednesday. Compare it with the colon version above: no break, no repeated println, and the name ends up in a variable you can use afterwards. Note the semicolon after the closing brace. It ends the statement String name = switch (day) { ... };, the same way a semicolon ends any other assignment.
A switch expression must produce a value whatever day holds. An int can hold billions of values and the cases name only three, so the default is required. Leave it out and the program does not compile:
public class Main { public static void main(String[] args) { int day = 3; String name = switch (day) { // error: the switch expression does not cover all possible input values case 1 -> "Monday"; case 2 -> "Tuesday"; case 3 -> "Wednesday"; }; System.out.println(name); }}A switch statement has no such rule: if no case matches and there is no default, it simply does nothing. A switch expression must always come up with a value, so the compiler checks that every possible input is covered. For an int or a String, that means a default.
Key ideaA switch expression produces a value: each arrow names the value for its case, and a
defaultcovers everything else. After an arrow only that case runs, so there is no fall-through and nobreak.
yield: when a case needs more than one line#
Sometimes a case has to work something out before it knows its value. Then the arrow is followed by a block in braces, and the block ends with yield and the value the case produces. In this cinema, adults pay 12, and children and students pay 8, with 2 off on Tuesdays:
public class Main { public static void main(String[] args) { String ticket = "student"; boolean tuesday = true; int price = switch (ticket) { case "adult" -> 12; case "child", "student" -> { int base = 8; if (tuesday) { base = base - 2; } yield base; } default -> 0; }; System.out.println("A " + ticket + " ticket costs " + price); }}ticket is "student", which matches the second case. Its block starts base at 8, takes 2 off because tuesday is true, and yield base; hands 6 back as the value of the whole switch. The program prints A student ticket costs 6. Read yieldas “the value of this switch is”. A case that fits on one line, like case "adult" -> 12;, needs no yield.
This switch shows two more things. Its value is a String, and the cases compare text: case "child" matches any string that spells child, letter for letter. And one case lists two values, "child" and "student", separated by a comma.
== between two strings does not always do that. In an if, compare text with equals: if (ticket.equals("adult")). The strings lesson explains why == can say false for two strings with the same letters.switch is true?0 with a switch expression on month. Each test types a different month on the keyboard.case Circle c -> .... That needs classes and records first, so it comes much later, in sealed types and pattern matching.&&, ||, anything.score >= 90.1, 2, 3 or "small", "large".break.if that gets asked again and again. Everything you learned about boolean conditions here carries straight over.The shape of a decision, in one place#
if (cond) { }runs a block whencondis abooleanthat is true. Always write the braces.elsehandles the false case;else ifadds more conditions, checked top to bottom, first match wins.- A condition is any expression yielding a
boolean, so&&,||, and!all fit inside it. cond ? a : bchooses between two values.- A colon
switchjumps to the matching case and falls through until abreak;defaultis the catch-all. - An arrow
switchruns only the matching case. As a switch expression it produces a value, andyieldgives the value of a case written as a block. - Write
==to compare and=to assign. Mixing them up is the bug to watch for.
Try it
Answer each one in your head or on paper first, then open the answer.
if (...) evaluate to, and why is if (temperature) for an int temperature an error in Java?score >= 90, then >= 80, then >= 70, and so on. What grade does a score of 95 get if you reorder the chain to check score >= 60 first?String label; if (n % 2 == 0) label = "even"; else label = "odd";switch, suppose the break after case 1 is deleted and day is 1. What gets printed, and what is this behavior called?if (x = 5) fail to compile, while if (done = true) compiles when done is a boolean?Feedback switch, the one that stacks 'A' and 'B', as a switch expression that stores the message in a String called message and prints it once.Practice
Work through these on your own. Each one builds on this page.
int hour from 0 to 23 from the keyboard, with a Scanner as in the grade example, and prints "Good morning", "Good afternoon", or "Good evening" using an if / else if / else chain. Decide your own boundaries and type the value sitting exactly on each one.int larger whichever of two int variables a and b is greater. Then print it. Confirm it works when a is bigger, when b is bigger, and when they are equal.int month from 1 to 12 into its season in the northern hemisphere (12, 1 and 2 are winter, 3 to 5 spring, 6 to 8 summer, 9 to 11 autumn) and prints it. List each season's months in a single case label. Then write the same program with the colon form, stacked labels and break, and compare the two. Test months 3 and 12 in both.18 who is a member should get "Junior member discount". Where in the chain must this branch go so it is reached, and why? Write one sentence explaining the ordering before you change the code.a and b, then a number from 1 to 4 that picks the operation: 1 adds, 2 subtracts, 3 multiplies and 4 divides. Use a switch expression to compute the result and print it. What should happen when the choice is 4 and b is 0? Handle that case with an if before the switch.Further reading#
- Control flow statements on dev.java, the official Java learning site, covering if, else and the loops that come next
- Branching with switch statements on dev.java, with the types a switch accepts and how fall-through works
- Branching with switch expressions on dev.java: arrow labels, yield and exhaustiveness
- JEP 361: Switch Expressions, the proposal that added the arrow form and switch expressions in Java 14, with the reasons behind them
Spotted a mistake, or a part that did not make sense? Report it by email