Index

Control Flow Syntax

Java Syntax

4.1 if, else if, and else

Conditional branching is a fundamental control flow structure in Java that allows the program to execute different code blocks based on conditions.

Basic Syntax

The if statement checks a boolean condition. If true, its block executes. Optionally, you can add an else if to check additional conditions, and an else block as a fallback.

int score = 75;

if (score >= 90) {
    System.out.println("Grade: A");
} else if (score >= 80) {
    System.out.println("Grade: B");
} else if (score >= 70) {
    System.out.println("Grade: C");
} else {
    System.out.println("Grade: F");
}

How Conditions Are Evaluated

Nested if Statements

You can nest if inside another if to check complex conditions:

int age = 20;
boolean hasLicense = true;

if (age >= 18) {
    if (hasLicense) {
        System.out.println("Allowed to drive.");
    } else {
        System.out.println("Needs a license.");
    }
} else {
    System.out.println("Too young to drive.");
}
Click to view full runnable Code

public class IfStatementDemo {
    public static void main(String[] args) {
        // Example 1: Grading using if-else if-else
        int score = 75;

        if (score >= 90) {
            System.out.println("Grade: A");
        } else if (score >= 80) {
            System.out.println("Grade: B");
        } else if (score >= 70) {
            System.out.println("Grade: C");
        } else {
            System.out.println("Grade: F");
        }

        // Example 2: Nested if statements
        int age = 20;
        boolean hasLicense = true;

        if (age >= 18) {
            if (hasLicense) {
                System.out.println("Allowed to drive.");
            } else {
                System.out.println("Needs a license.");
            }
        } else {
            System.out.println("Too young to drive.");
        }
    }
}

Reflection

Proper indentation and clear structure make if-else logic easy to follow and maintain. Omitting an else may cause missed cases, leading to unexpected behavior. Be cautious with overlapping conditions and always test edge cases. Using if-else if-else correctly ensures your program handles all possibilities gracefully.

Index

4.2 switch Statements and Fallthrough

The switch statement provides a clean way to execute different code blocks based on the value of a variable, often used as an alternative to long if-else if chains when testing one variable against multiple possible values.

Basic Syntax

switch (variable) {
    case value1:
        // code block
        break;
    case value2:
        // code block
        break;
    default:
        // code block executed if no case matches
}

Example with break

int day = 3;
String dayName;

switch (day) {
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    case 3:
        dayName = "Wednesday";
        break;
    default:
        dayName = "Invalid day";
}
System.out.println(dayName);  // Output: Wednesday

Intentional Fallthrough

If you omit break, execution continues into subsequent cases:

int month = 12;

switch (month) {
    case 12:
    case 1:
    case 2:
        System.out.println("Winter");
        break;
    case 3:
    case 4:
    case 5:
        System.out.println("Spring");
        break;
    default:
        System.out.println("Other season");
}

Here, months 12, 1, and 2 all print "Winter" because of fallthrough without intervening breaks.

Click to view full runnable Code

public class SwitchDemo {
    public static void main(String[] args) {
        // Example 1: switch with break
        int day = 3;
        String dayName;

        switch (day) {
            case 1:
                dayName = "Monday";
                break;
            case 2:
                dayName = "Tuesday";
                break;
            case 3:
                dayName = "Wednesday";
                break;
            default:
                dayName = "Invalid day";
        }
        System.out.println("Day " + day + " is " + dayName);  // Output: Wednesday

        // Example 2: Intentional fallthrough
        int month = 12;
        System.out.print("Month " + month + " is in ");

        switch (month) {
            case 12:
            case 1:
            case 2:
                System.out.println("Winter");
                break;
            case 3:
            case 4:
            case 5:
                System.out.println("Spring");
                break;
            default:
                System.out.println("Other season");
        }
    }
}

switch is often cleaner and more readable than multiple if-else if statements when testing a single variable against many discrete values. It can improve maintainability and reduce errors related to complex nested conditions. However, switch works only with discrete values (like primitives, enums, and strings), while if statements handle a broader range of conditions, including ranges and complex expressions. Being mindful of break usage is crucial to prevent unexpected fallthrough bugs.

Switch Expressions (Java 14)

Starting with Java 14, the traditional switch statement was enhanced with switch expressions, introducing a more concise and expressive way to handle multiple cases and return values directly.

What Are Switch Expressions?

Unlike the classic switch statement, which is used primarily for control flow, switch expressions can produce a value. This means you can assign the result of a switch expression directly to a variable.

Basic Syntax

int day = 3;

String dayName = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";
    default -> "Invalid day";
};

System.out.println(dayName);  // Output: Wednesday

Multiple Labels in One Case

Switch expressions support grouping multiple labels to share the same result:

String season = switch (month) {
    case 12, 1, 2 -> "Winter";
    case 3, 4, 5 -> "Spring";
    default -> "Other";
};

Block Bodies in Switch Expressions

For more complex logic, use a block with yield to return a value:

String category = switch (score) {
    case 90, 100 -> "Excellent";
    case 80, 89 -> "Good";
    default -> {
        System.out.println("Score below 80");
        yield "Needs Improvement";
    }
};
Click to view full runnable Code

public class SwitchExpressionDemo {
    public static void main(String[] args) {
        int day = 3;
        String dayName = switch (day) {
            case 1 -> "Monday";
            case 2 -> "Tuesday";
            case 3 -> "Wednesday";
            default -> "Invalid day";
        };
        System.out.println("Day " + day + " is " + dayName);  // Output: Wednesday

        int month = 12;
        String season = switch (month) {
            case 12, 1, 2 -> "Winter";
            case 3, 4, 5 -> "Spring";
            default -> "Other";
        };
        System.out.println("Month " + month + " is in " + season);

        int score = 75;
        String category = switch (score) {
            case 90, 100 -> "Excellent";
            case 80, 89 -> "Good";
            default -> {
                System.out.println("Score below 80");
                yield "Needs Improvement";
            }
        };
        System.out.println("Score " + score + " category: " + category);
    }
}

Reflection

Switch expressions simplify code by combining control flow and value production, reducing verbosity and errors like missing breaks. They make your logic clearer and more concise. However, since switch expressions require Java 14 or later, ensure your environment supports them before use. When used appropriately, switch expressions improve readability and maintainability in decision-making code.

Index

4.3 Pattern Matching with instanceof (Java 16)

Traditionally, the instanceof operator in Java is used to check if an object is an instance of a specific class or interface. However, this often requires an explicit type cast after the check, making the code verbose and repetitive.

Traditional instanceof Usage

Object obj = "Hello, World!";

if (obj instanceof String) {
    String str = (String) obj;  // explicit cast required
    System.out.println(str.toUpperCase());
}

Here, after confirming obj is a String, you must cast it before using String methods like toUpperCase().

Pattern Matching with instanceof

Java 16 introduced pattern matching for instanceof, which combines the check and cast in one step:

Object obj = "Hello, World!";

if (obj instanceof String str) {
    // str is automatically cast to String within this block
    System.out.println(str.toUpperCase());
}

The variable str is declared and initialized if the instanceof check succeeds, eliminating the need for a separate cast.

Click to view full runnable Code

public class InstanceofDemo {
    public static void main(String[] args) {
        Object obj1 = "Hello, World!";
        Object obj2 = 123;

        // Traditional instanceof usage with explicit cast
        if (obj1 instanceof String) {
            String str = (String) obj1;  // explicit cast required
            System.out.println("Traditional: " + str.toUpperCase());
        }

        // Pattern matching with instanceof (Java 16+)
        if (obj2 instanceof String str) {
            // This block won't execute because obj2 is not a String
            System.out.println("Pattern matching: " + str.toUpperCase());
        } else {
            System.out.println("Pattern matching: obj2 is not a String");
        }
    }
}

Benefits of Pattern Matching

Reflection

Pattern matching with instanceof streamlines common type-checking scenarios, making Java code more concise and expressive. It encourages safer coding practices by tying type checks directly to variable declarations, avoiding the boilerplate and risks associated with manual casting. As a result, developers write cleaner, easier-to-understand code when working with polymorphic types.

Index

4.4 while, do-while, and for Loops

Loops are essential in Java for executing code repeatedly based on a condition. Java provides three common loop types: while, do-while, and for. Each serves specific use cases depending on when the condition should be checked and how the loop is structured.

while Loop

The while loop checks its condition before each iteration. If the condition is true, the loop body runs; otherwise, it exits immediately.

int count = 1;
while (count <= 5) {
    System.out.println("Count: " + count);
    count++;
}

Use case: When the number of iterations is unknown and the loop may not run at all if the condition is initially false.

do-while Loop

The do-while loop executes the loop body at least once before checking the condition at the end of each iteration.

int count = 1;
do {
    System.out.println("Count: " + count);
    count++;
} while (count <= 5);

Use case: When you need the loop body to run at least once regardless of the condition.

for Loop

The for loop is compact and ideal when the number of iterations is known or controlled by a counter.

for (int i = 1; i <= 5; i++) {
    System.out.println("Count: " + i);
}

It has three parts:

Click to view full runnable Code

public class LoopExamples {
    public static void main(String[] args) {
        System.out.println("While loop:");
        int count1 = 1;
        while (count1 <= 5) {
            System.out.println("Count: " + count1);
            count1++;
        }

        System.out.println("\nDo-while loop:");
        int count2 = 1;
        do {
            System.out.println("Count: " + count2);
            count2++;
        } while (count2 <= 5);

        System.out.println("\nFor loop:");
        for (int i = 1; i <= 5; i++) {
            System.out.println("Count: " + i);
        }
    }
}

Reflection

Choosing the appropriate loop improves readability and logic clarity. Misusing loops—like using while when the body must run once—can cause bugs or unnecessarily complex code. Understanding these differences empowers you to write efficient, predictable loops in your Java programs.

Index

4.5 Enhanced for Loop (for-each)

The enhanced for loop, also known as the for-each loop, provides a simplified syntax for iterating over arrays and collections without the need to manage an index variable explicitly.

Syntax

int[] numbers = {1, 2, 3, 4, 5};

for (int num : numbers) {
    System.out.println(num);
}

Here, num takes on each value in the numbers array, one by one, until the loop completes.

Comparison with Regular for Loop

The equivalent traditional loop looks like this:

for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
}

The enhanced for loop is more concise and eliminates boilerplate code such as index initialization, condition checking, and incrementing.

Usage with Collections

The for-each loop works similarly with any class implementing the Iterable interface, such as ArrayList:

List<String> names = List.of("Alice", "Bob", "Charlie");

for (String name : names) {
    System.out.println(name);
}
Click to view full runnable Code

import java.util.List;

public class ForEachDemo {
    public static void main(String[] args) {
        // Using enhanced for-each with an array
        int[] numbers = {1, 2, 3, 4, 5};
        System.out.println("Enhanced for-each with array:");
        for (int num : numbers) {
            System.out.println(num);
        }

        // Equivalent traditional for loop
        System.out.println("\nTraditional for loop with array:");
        for (int i = 0; i < numbers.length; i++) {
            System.out.println(numbers[i]);
        }

        // Using enhanced for-each with a List
        List<String> names = List.of("Alice", "Bob", "Charlie");
        System.out.println("\nEnhanced for-each with List:");
        for (String name : names) {
            System.out.println(name);
        }
    }
}

Reflection

The enhanced for loop improves readability by focusing on elements rather than indices, reducing common mistakes like off-by-one errors. However, it does have limitations:

Despite these, the enhanced for loop is an excellent choice for simple, safe, and clear iteration over arrays and collections in Java.

Index

4.6 Control Statements: break, continue, and Labels

Java provides control statements like break and continue to manage loop execution more precisely. These statements help alter the flow inside loops, and labeled statements extend this control especially in nested loops.

break Statement

The break statement immediately exits the nearest enclosing loop or switch block:

for (int i = 1; i <= 5; i++) {
    if (i == 3) {
        break;  // Exit loop when i is 3
    }
    System.out.println(i);
}
// Output: 1 2

Here, the loop terminates early when i equals 3.

continue Statement

The continue statement skips the current iteration and proceeds to the next one:

for (int i = 1; i <= 5; i++) {
    if (i == 3) {
        continue;  // Skip printing when i is 3
    }
    System.out.println(i);
}
// Output: 1 2 4 5

The iteration where i is 3 is skipped.

Labeled Statements

In nested loops, break and continue only affect the innermost loop by default. Labels allow you to specify which loop to control.

outerLoop:
for (int i = 1; i <= 3; i++) {
    innerLoop:
    for (int j = 1; j <= 3; j++) {
        if (i == 2 && j == 2) {
            break outerLoop;  // Exit the outer loop entirely
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}

This code exits both loops when i is 2 and j is 2.

Click to view full runnable Code

public class BreakContinueExample {
    public static void main(String[] args) {
        System.out.println("Break example:");
        for (int i = 1; i <= 5; i++) {
            if (i == 3) {
                break;  // Exit loop when i is 3
            }
            System.out.print(i + " ");
        }
        System.out.println("\n");

        System.out.println("Continue example:");
        for (int i = 1; i <= 5; i++) {
            if (i == 3) {
                continue;  // Skip printing when i is 3
            }
            System.out.print(i + " ");
        }
        System.out.println("\n");

        System.out.println("Labeled break example:");
        outerLoop:
        for (int i = 1; i <= 3; i++) {
            innerLoop:
            for (int j = 1; j <= 3; j++) {
                if (i == 2 && j == 2) {
                    break outerLoop;  // Exit the outer loop entirely
                }
                System.out.println("i=" + i + ", j=" + j);
            }
        }
    }
}

Reflection

While break and continue can simplify complex looping logic by allowing early exits or skipping iterations, overusing them—especially with labeled loops—can make code harder to read and maintain. Use these statements judiciously and document their intent clearly to keep your code understandable and bug-free.

Index