Loops & Control Flow

if / else

if (temp > 30) {
    IO.println("hot");
} else if (temp > 15) {
    IO.println("mild");
} else {
    IO.println("cold");
}

Braces are optional for a single statement — always write them anyway.

switch

Old form, needs break or it falls through:

switch (day) {
    case 1:
    case 7:
        IO.println("weekend");
        break;
    default:
        IO.println("weekday");
}

Arrow form — no fallthrough, and it produces a value:

var label = switch (day) {
    case 1, 7 -> "weekend";
    case 2, 3, 4, 5, 6 -> "weekday";
    default -> throw new IllegalArgumentException("bad day: " + day);
};

var text = switch (grade) {
    case 1 -> "excellent";
    default -> {
        var s = "grade " + grade;
        yield s.toUpperCase();     // yield inside a block
    }
};

Pattern matching

String describe(Object o) {
    return switch (o) {
        case Integer i when i > 100 -> "big number: " + i;
        case Integer i              -> "number: " + i;
        case String s               -> "text of %d chars".formatted(s.length());
        case int[] a                -> "array of " + a.length;
        case null                   -> "nothing";
        default                     -> "unknown";
    };
}

when adds a guard. Without case null, a null value throws.

Record patterns

sealed interface Shape permits Circle, Rect {}
record Circle(double radius) implements Shape {}
record Rect(double w, double h) implements Shape {}

double area(Shape s) {
    return switch (s) {
        case Circle(double r)    -> Math.PI * r * r;
        case Rect(double w, double h) -> w * h;
    };
}
Note

No default needed: because Shape is sealed, the compiler knows every case — and will flag this switch if you add another shape later.

instanceof with a pattern

if (o instanceof String s && s.length() > 3) {
    IO.println(s.toUpperCase());
}

Loops

for (var name : names) {  }                  // for-each: the default

for (var i = 0; i < 5; i++) {  }             // when you need the index
for (var i = 10; i > 0; i -= 2) {  }

while (rest > 1) { rest /= 2; }

do { answer = IO.readln("again? "); }         // runs at least once
while (!answer.equals("n"));

break and continue

for (var n : numbers) {
    if (n % 2 != 0) continue;   // skip
    if (n > 15) break;          // leave the loop
    IO.println(n);
}

Labels exist for nested loops, but extracting a method and return is usually cleaner:

outer:
for () { for () { if () break outer; } }

Putting it together

void main() {
    var secret = new java.util.Random().nextInt(1, 101);
    var tries = 0;

    while (true) {
        var input = IO.readln("Guess 1-100: ");
        int guess;
        try {
            guess = Integer.parseInt(input.strip());
        } catch (NumberFormatException e) {
            IO.println("Not a number.");
            continue;
        }

        tries++;
        switch (Integer.compare(guess, secret)) {
            case -1 -> IO.println("too low");
            case  1 -> IO.println("too high");
            default -> {
                IO.println("Got it in %d tries!".formatted(tries));
                return;
            }
        }
    }
}

★ Exercises

  1. Print a multiplication table from 1 to 10, aligned with "%4d".
  2. FizzBuzz for 1–100 — once with if/else, once with a switch expression.
  3. Print all primes up to n.
  4. Write String classify(Object o) handling Integer, Double, String, List and null.
  5. Add a Triangle to the shapes above. What does the compiler say about your old switch?
  6. Change the guessing game to stop after 7 wrong guesses.