4.5 Lab: Branches, Loops, and Boundaries
Prerequisites: All control flow constructs in this chapter Estimated time: 50 minutes Practice environment: JDK 17+ and Bash; all files written to a temporary directory You are ready to continue when: You have completed four required exercises and provide boundary cases for each
chapter4_lab_root=$(mktemp -d)
readonly chapter4_lab_root
mkdir -p "$chapter4_lab_root/chapter04-lab/out"
cd "$chapter4_lab_root/chapter04-lab"Required Exercise 1: Score Classification Boundaries
Save as GradeClassifier.java:
public class GradeClassifier {
static String classify(int score) {
if (score < 0 || score > 100) {
return "invalid";
} else if (score >= 90) {
return "excellent";
} else if (score >= 80) {
return "good";
} else if (score >= 60) {
return "pass";
}
return "retry";
}
public static void main(String[] args) {
int[] cases = {-1, 0, 59, 60, 79, 80, 89, 90, 100, 101};
for (int score : cases) {
System.out.println(score + ":" + classify(score));
}
}
}javac -d out GradeClassifier.java
java -cp out GradeClassifierCarefully verify each boundary point, don’t just check whether the program exits successfully.
Required Exercise 2: Switch Expression
Save as ItemPrice.java:
public class ItemPrice {
static int priceOf(String item) {
if (item == null) {
throw new IllegalArgumentException("item is null");
}
return switch (item) {
case "book" -> 50;
case "magazine" -> 40;
case "notebook" -> 35;
default -> throw new IllegalArgumentException("item: " + item);
};
}
public static void main(String[] args) {
System.out.println(priceOf("book"));
System.out.println(priceOf("magazine"));
}
}javac -d out ItemPrice.java
java -cp out ItemPriceThen pass in "hammer" and null, recording the exception type and message. Unknown values should not silently default to a price of 0.
Required Exercise 3: A 1–9 Multiplication Table
Save as MultiplicationTable.java:
public class MultiplicationTable {
public static void main(String[] args) {
for (int row = 1; row <= 9; row++) {
for (int column = 1; column <= row; column++) {
System.out.print(column + "×" + row + "="
+ (column * row) + "\t");
}
System.out.println();
}
}
}javac -d out MultiplicationTable.java
java -cp out MultiplicationTableVerification: 9 rows in total; row 1 contains 1 item; row 9 contains 9 items; the final item is 9×9=81.
Required Exercise 4: Find the First Target
Save as GridSearch.java:
public class GridSearch {
static int[] findFirst(int[][] grid, int target) {
search:
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
if (grid[row][column] == target) {
return new int[] {row, column};
}
}
}
return new int[] {-1, -1};
}
public static void main(String[] args) {
int[][] grid = {{1, 2}, {3, 7}, {7, 9}};
System.out.println(java.util.Arrays.toString(findFirst(grid, 7)));
System.out.println(java.util.Arrays.toString(findFirst(grid, 8)));
}
}javac -d out GridSearch.java
java -cp out GridSearchExpect [1, 1] and [-1, -1]: the two integers are the row and column, and two -1 values mean no match. This example borrows array syntax from the next chapter to return a coordinate pair. Packing coordinates into row * 1000 + column would allow collisions when rows have many columns. The outer array and each row must be non-null; empty arrays and rows are allowed. Production code can return a result type with named fields.
The unused search: label is intentional: after extracting a method that returns the result, the label is redundant. Remove it.
Optional Exercise 1: Number Guessing That Can Stop
Separate the random number generation from user interaction to first test the deterministic core:
static String compareGuess(int target, int guess) {
if (guess < target) {
return "low";
}
if (guess > target) {
return "high";
}
return "correct";
}When testing target=50 with values 49, 50, and 51, remember that the actual interactive loop must handle non-numeric input, input termination, maximum attempt limits, and cancellation. Do not write while (true) first and then assume the user will always guess correctly.
Optional Exercise 2: Observe the Landing of continue
for (int value = 0; value < 5; value++) {
if (value == 2) {
continue;
}
System.out.println(value);
}Rewrite it as an equivalent while, ensuring that state progression occurs before continue. If the program hangs, interrupt execution first, then inspect where variables are updated; do not let a stalled process consume the terminal session for an extended period.
Fault Experiment
Open one at a time:
// if (1) { }
// switch (1L) { default -> { } }
// for (int index = 0; index < 3; index++); { System.out.println("once"); }The first two fail to compile; the third compiles but the block executes only once. Record the difference between "compile failure" and "semantic error".
Safe Cleanup
pwd
test ! -e out || rm -r -- outEnsure you're in the directory for this experiment and only remove artifacts produced by this experiment.
Acceptance and Cleanup
Verify each item: score thresholds, unknown items and null, the number of rows and endpoints in the multiplication table, the two search paths: a match and no match. In the failure experiment, separately document compilation errors, runtime exceptions, and cases where the program runs but behaves incorrectly. After saving your experiment notes, clean up the entire temporary directory:
cd
ls -ld -- "$chapter4_lab_root"
rm -r -- "$chapter4_lab_root"Classification, pricing, and search use different syntax, but each needs the same path checks: where execution enters, how state changes, where it exits, and who handles exceptions.
The next chapter will encapsulate this repeated logic.