Layers of Logic

3.2

Conditionals

Use if and switch to make a program choose what to do.

Core18 min read4 exercises

Before you start

Topics used in this lesson

01

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.

02

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.

03

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:

B

The 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: and case 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.

04

Optional internals

Going deeper
Open 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 labelsArrow labels
Fallthroughyes, and silentnone. Each case is separate.
Leaving a statement caseusually break, return or throwthe arm ends by itself
Several valuesstack the case labelscase 6, 7 ->
Usecommon in older switch statementspreferred 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

AllowedNot allowed
Whole numbersbyte, short, char, int and their wrapperslong and Long
Decimalsnonefloat, double
OtherString, enumboolean

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);    // false

You 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 tolerance

One 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.

05

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.

06

Check yourself

Answer from memory before opening the explanation. Then compare your rule with the answer and revisit the example if they differ.

  1. 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 the if and else if conditions 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.

  2. 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, String and enum types. They do not support long, float, double or boolean.

    A compiler may emit a table switch, a lookup switch, or other code depending on the cases. Do not choose switch because you assume it is faster. Choose it when one selector is being matched against fixed alternatives.

  3. 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 break or 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.

07

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.

  1. Warm up: one idea with close guidance.
  2. Practice: combine the idea with earlier material.
  3. Challenge: investigate an edge case or implementation detail.

4 exercises85 pointsabout 95 minutes

Coding exercises provide starter code and concrete success checks. Exercises marked checked can also be run through the VS Code extension. Concept questions are selectable quizzes that give immediate feedback on this page.
A

Read a Choice and Branch

Warm up·20 min·15 points

checkedex-3-2-a

Write 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
Access.java
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
Compare typed text with "admin".equals(role), not role == "admin".
Hint 2
Put the guest check in an else if. The final else handles every value that matched neither word.
Hint 3almost the answer
The complete shape is if ("admin".equals(role)) return "settings"; else if ("guest".equals(role)) return "read-only"; else return "denied";
Why this exercise exists

This exercise checks whether you can read one line from standard input and choose a result with an if and else-if chain.

B

Find and Fix switch Fallthrough

Practice·20 min·20 points

checkedex-3-2-b

The 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
Fall.java
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
March, April and May fall through into summer, because the spring branch has no break. Nine of the twelve months are still correct.
Hint 2
The arrow form is case 12, 1, 2 -> "winter"; and it never falls through, so this bug cannot be written in it at all.
Hint 3almost the answer
Stacked labels with no code between them share one branch. Fallthrough occurs when execution runs statements under one label and then continues into statements under the next label.
Why this exercise exists

This exercise checks whether you can spot a missing break by reading, and whether you can rewrite an old switch in the arrow form.

C

Comparing Decimals Safely

Challenge·20 min·20 points

checkedex-3-2-c

Write 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
Compare.java
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
A tolerance around 1e-9 is a reasonable fixed choice, and Math.abs(a - b) is the comparison.
Hint 2
At 1e15 the gap between one double and the next is larger than 1, so two neighbouring values are more than your tolerance apart. The tolerance did not change and the numbers did.
Hint 3almost the answer
The relative version divides the difference by the size of the numbers, so the allowance grows with them. Handle a == b first, or comparing zero with zero divides by zero.
Why this exercise exists

This exercise checks whether you can compare double values with absolute and relative tolerances.

D

Registry Status Rules

Challenge·35 min·30 points·The Registry

checkedex-3-2-d

Implement 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
Status.java
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
Three of the rules are absolute: not active, no supplies, medical hold. None of them care about readiness, so check them before you work out any threshold.
Hint 2
Rule 4 does not add a case, it changes a number. double required = clearance >= 5 ? 40.0 : 60.0; and then one comparison covers both.
Hint 3almost the answer
"At least 60" includes 60. Use >= for both threshold checks and test the boundary values directly.
Why this exercise exists

This exercise checks whether you can turn a set of written rules into readable conditional code.

08

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