Learning outcome: Select suitable decision and repetition statements, trace the flow of a Java program, and develop small algorithms using conditions and loops.
Control flow decides the order in which statements execute. A program normally proceeds from top to bottom, but decisions can select one path and loops can repeat a task. These structures are essential for validating input, processing arrays, analysing text, and implementing numerical algorithms.
Boolean Conditions
A condition is an expression whose result is either true or false.
int marks = 72;
boolean passed = marks >= 40;
Conditions commonly use relational and logical operators.
| Operator | Meaning | Example |
|---|---|---|
== |
equal to | choice == 1 |
!= |
not equal to | age != 0 |
<, <= |
less than, less than or equal to | marks <= 100 |
>, >= |
greater than, greater than or equal to | balance >= amount |
&& |
both conditions must be true | age >= 18 && citizen |
|| |
at least one condition must be true | day == 6 || day == 7 |
! |
reverses a Boolean value | !isEmpty |
Use parentheses when they make a compound condition easier to read.
if, else if, and else
Use if when a statement should run only when a condition is true.
if (temperature > 40) {
System.out.println("Heat alert");
}
Use if-else when there are two alternatives.
if (number % 2 == 0) {
System.out.println("Even");
} else {
System.out.println("Odd");
}
An else-if ladder is suitable for mutually exclusive ranges.
if (marks >= 90) {
System.out.println("Outstanding");
} else if (marks >= 75) {
System.out.println("Distinction");
} else if (marks >= 60) {
System.out.println("First division");
} else if (marks >= 40) {
System.out.println("Pass");
} else {
System.out.println("Needs improvement");
}
The order matters. Once a condition is true, the remaining branches are skipped.
Comparing Characters
Java stores a char as a Unicode value. Relational operators can therefore compare characters.
public class CharacterOrder {
public static void main(String[] args) {
char first = 'd';
char second = 'a';
if (first < second) {
System.out.println(first + ", " + second);
} else {
System.out.println(second + ", " + first);
}
}
}
For case-insensitive alphabetic ordering, convert both values to the same case before comparison.
char left = Character.toLowerCase(first);
char right = Character.toLowerCase(second);
switch Statement
Use switch when one expression is compared with several fixed alternatives.
In simple terms: A chain of if / else if asks a series of yes-or-no questions. A switch looks one value up on a menu board and jumps straight to that line.
public class MenuChoice {
public static void main(String[] args) {
int choice = 2;
switch (choice) {
case 1:
System.out.println("Deposit");
break;
case 2:
System.out.println("Withdraw");
break;
case 3:
System.out.println("Balance enquiry");
break;
default:
System.out.println("Invalid choice");
}
}
}
Here choice is 2, so the program prints Withdraw. Each lowercase break; ends the switch after its matching action. Without those statements, execution would continue into later colon-style cases. The final default does not need break because the closing brace ends the statement, although adding one is harmless.
The default branch is optional and may be written anywhere in the block, although placing it last is conventional. If no label matches and there is no default, the whole statement is simply skipped.
What the Selector May Be
The selector expression is restricted to a small set of types.
| Permitted in the JDK 17 course baseline | Not permitted as a traditional selector |
|---|---|
byte, short, char, int |
long, float, double, boolean |
Byte, Short, Character, Integer |
Other arbitrary object types |
String |
|
An enum type |
In simple terms: Java defines an explicit list of selector types for a traditional switch. Treat that list as a language rule, not as a promise that every switch becomes a jump table. Depending on the labels, a compiler may generate a table lookup, a sparse lookup, or other comparison logic.
Two traps follow from the reference types:
- A
Stringselector is matched withequals(), so matching is case-sensitive. Normalise the input withtoLowerCase()first if that is not wanted. - If the selector evaluates to
null, the statement throws aNullPointerExceptionbefore any label is tested. Adefaultbranch does not catch this.
Rules for case Labels
- Every label must be a compile-time constant: a literal, a
finalvariable initialised with a constant, or an enum constant. A method call or an ordinary variable is rejected by the compiler. - Labels must be unique within one
switch, and each must fit the selector type. - Enum labels are written unqualified:
case NEW:, nevercase Status.NEW:. - In the colon form, the statements belong to one switch block. The scope of a local variable can therefore extend from its declaration into later case groups, where definite-assignment problems or duplicate declarations may occur. Wrap a case body in braces when it needs its own local scope.
switch (choice) {
case 1: { // braces give this case its own scope
double amount = 500.0;
System.out.println("Deposit " + amount);
break;
}
case 2:
System.out.println("Withdraw");
break;
}
Fall-Through and Grouped Labels
A case is a jump target rather than a self-contained block. Once control enters, execution continues downward through the following labels until it meets break, return, throw, or the closing brace. This behaviour is called fall-through.
public class FallThroughDemo {
public static void main(String[] args) {
int day = 3;
switch (day) {
case 3:
System.out.println("Wednesday");
// No break: execution intentionally continues into case 4.
case 4:
System.out.println("Thursday");
break;
default:
System.out.println("Another day");
}
}
}
Output:
Wednesday
Thursday
Accidental fall-through is a common defect, but deliberate fall-through is the neatest way to group labels that share one action.
switch (month) {
case 4: case 6: case 9: case 11:
System.out.println("30 days");
break;
case 2:
System.out.println("28 or 29 days");
break;
default:
System.out.println("31 days");
}
A trap worth remembering: when a switch sits inside a loop, break ends the switch only, not the loop. To leave the loop as well, label the loop and use break outer;.
switch Expressions
From Java 14, a switch can produce a value. The arrow form runs exactly one branch, so there is no fall-through and no break.
String category = switch (choice) {
case 1 -> "Deposit";
case 2 -> "Withdraw";
case 3 -> "Balance enquiry";
default -> "Invalid choice";
};
Several labels may share one arm, and a branch needing more than one statement uses a block with yield to supply the result.
int days = switch (month) {
case 1, 3, 5, 7, 8, 10, 12 -> 31;
case 4, 6, 9, 11 -> 30;
case 2 -> {
boolean leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
yield leap ? 29 : 28;
}
default -> throw new IllegalArgumentException("Invalid month: " + month);
};
yield returns a value from the switch and continues with the rest of the method. return would exit the method entirely, so the two are not interchangeable.
A switch expression must be exhaustive, because it always has to produce a value. Supply a default, or cover every constant of an enum. Covering all enum constants without a default is the safer habit: adding a new constant later then breaks compilation instead of silently falling into default. The colon form and the arrow form cannot be mixed in one switch.
Enrichment: the course baseline is JDK 17, where the material above is complete. Java 21 extends switch with finalised pattern matching: a reference selector may be matched by type patterns, optional when guards, and an explicit case null. This later feature should not be mixed into JDK 17 laboratory code.
Choosing switch or if-else
| Situation | Preferred construct |
|---|---|
| Dispatch on a small fixed set of discrete values, such as a menu | switch |
Ranges, compound conditions, or a long, double, or boolean test |
if-else |
| A pure lookup whose entries may change while the program runs | Map or EnumMap |
| Branching on your own object types with substantial behaviour | Polymorphism |
for Loop
A for loop is appropriate when the number of repetitions is known or controlled by a counter.
In simple terms: Use for when the number of repetitions is already known, like dealing cards to a fixed number of players.
for (int count = 1; count <= 5; count++) {
System.out.println(count);
}
The three parts are:
- initialisation, performed once;
- condition, tested before every iteration; and
- update, performed after each iteration.
while and do-while Loops
A while loop is useful when repetition depends on a condition and the number of iterations is not known beforehand.
In simple terms: while reads the sign before entering the room. do-while walks in first and reads the sign on the way out, so its body always runs at least once.
int number = 4821;
while (number > 0) {
int digit = number % 10;
System.out.println(digit);
number /= 10;
}
A do-while loop tests its condition after the body, so the body executes at least once.
int choice;
do {
choice = readChoice();
} while (choice < 1 || choice > 3);
Reversing a Number
Number reversal repeatedly removes the last digit and appends it to a result.
public class ReverseNumber {
public static void main(String[] args) {
int number = 4821;
int original = number;
int reversed = 0;
while (number != 0) {
int digit = number % 10;
reversed = reversed * 10 + digit;
number /= 10;
}
System.out.println("Reverse of " + original + " is " + reversed);
}
}
For the input 4821, the successive values of reversed are 1, 12, 128, and 1284.
Nested Loops
A loop can contain another loop. Matrix operations and pattern generation commonly use nested loops.
for (int row = 1; row <= 3; row++) {
for (int column = 1; column <= 4; column++) {
System.out.print("* ");
}
System.out.println();
}
The inner loop completes all its iterations for every one iteration of the outer loop.
Prime Numbers from 10 to 99
A prime number is greater than 1 and has exactly two factors: 1 and itself. To test a number, it is sufficient to check possible divisors up to its square root.
public class TwoDigitPrimes {
static boolean isPrime(int number) {
if (number < 2) {
return false;
}
for (int divisor = 2; divisor * divisor <= number; divisor++) {
if (number % divisor == 0) {
return false;
}
}
return true;
}
public static void main(String[] args) {
for (int number = 10; number <= 99; number++) {
if (isPrime(number)) {
System.out.print(number + " ");
}
}
}
}
The method keeps the test logic separate from the range traversal, making the program easier to test.
break and continue
An unlabelled break ends the nearest enclosing loop or switch. An unlabelled continue skips the remaining statements in the current loop iteration and proceeds to the next iteration; it applies to loops, not to switch itself.
In simple terms: break leaves the building. continue skips the rest of this room and moves on to the next one.
for (int number = 1; number <= 10; number++) {
if (number == 8) {
break;
}
if (number % 2 == 0) {
continue;
}
System.out.println(number);
}
Use both statements carefully. A well-written loop should remain easy to trace.
Developing a Small Algorithm
A dependable approach is:
- identify the input and expected output;
- write the rule in simple words;
- select the required variables;
- choose the decision or loop structure;
- trace the algorithm with a small example;
- test normal, boundary, and invalid cases.
For example, when reversing a number, test a usual value, a value ending in zero, zero itself, and a negative value if the problem permits it.
Common Mistakes
| Mistake | Correction |
|---|---|
Writing = instead of == in a condition |
Use == for equality comparison. |
Adding a semicolon after if or for |
Remove the unintended empty statement. |
| Creating a loop whose condition never becomes false | Check that the update changes the condition. |
Using many unrelated if statements for exclusive ranges |
Use an else-if ladder. |
Forgetting break in a traditional switch |
Add it unless fall-through is intended. |
Switching on a long, double, or boolean in JDK 17 |
Use if-else or redesign the selector; do not cast merely to satisfy switch. |
Using a variable or method call as a case label |
Labels must be compile-time constants. |
Switching on a String that may be null |
Check for null first; default does not catch it. |
Qualifying an enum label as case Status.NEW: |
Write enum labels unqualified: case NEW:. |
Expecting break inside a switch to leave the surrounding loop |
It ends the switch only; use a labelled break for the loop. |
Writing return where a switch expression needs a result |
Use yield inside a block arm. |
| Changing the loop counter inside the body without a clear reason | Keep counter updates in one predictable place. |
Practice
- Read three integers and print the greatest value.
- Print the multiplication table of a number from 1 to 10.
- Check whether a number is a palindrome by reversing it.
- Print all prime numbers in a user-supplied range.
- Build a menu-driven banking operation using
switch.
Quick Check
1. When is a for loop preferable to a while loop?
A for loop is usually preferable when repetition is controlled by a counter or the number of iterations is known.
2. Does a do-while loop always execute once?
Yes. Its condition is checked after the loop body.
3. What is the difference between break and continue?
break terminates the nearest loop or switch. continue applies to a loop and skips only the remaining work of the current iteration.
4. Why does the prime-number test stop when divisor * divisor > number?
If a number has a factor larger than its square root, the corresponding paired factor must be smaller than the square root and would already have been tested.
5. Which types may a switch selector have?
For the JDK 17 baseline: byte, short, char, and int with their wrapper classes, plus String and enum types. long, float, double, and boolean are not permitted.
6. What happens if a String selector is null?
A NullPointerException is thrown before any label is tested. The default branch does not protect against it, so the value must be checked beforehand.
7. What is the difference between yield and return in a switch expression?
yield supplies the value of the switch and the method continues. return exits the enclosing method altogether.
Summary
Decisions select a path, while loops repeat a task. if-else supports ranges and compound conditions, switch handles a fixed set of discrete values through a restricted selector type and constant labels, and for, while, and do-while address different repetition needs. Clear control flow is the foundation of numerical, array, string, and menu-driven programs.