Java Fundamentals Tutorial: Statements and Flow Control

5. Statements and Flow Control

Controlling the Flow of Java Programs

5.1. Expressions

  • An expression is a code fragment consisting of variables, constants, method invocations, and operators that evaluates to a single value.
  • Examples:

    • 2 + 2
    • a = b + c
    • myMethod()

5.2. Statements and Blocks

  • A statement is a complete unit of execution.
  • Many types of expressions can be made into statements by terminating them with a semicolon (;).

    • Assignment expressions: answer = 42;
    • Increment or decrement expressions: a++; or b--;
    • Method invocations: System.out.println("Hello, world!");
    • Object creation: Account client = new Account();
  • A declaration statement declares a variable, optionally assigning it an initial value. For example: int count;
  • A block is a group of zero or more statements between balanced braces ({ }).

    • A block can be used anywhere a single statement is allowed.
  • A control flow statement is a structure controlling the execution of other statements. We’ll explore these through the rest of this module.

Java whitespace characters are the space, tab, carriage return, newline (aka linefeed), and formfeed characters.

Java requires one or more whitespace characters between keywords and/or identifiers. Other whitespace characters that do not occur within quoted string literals are not considered significant and serve only to increase the readability of code.

5.3. Local Variable Storage: The Stack

  • All variables declared within a block are known as local variables, which are stored in a memory location known as the stack. Examples of where blocks are used include:

    • Method definitions
    • Flow control statements
    • Other blocks contained within a sequence of statements
  • The stack grows and shrinks as the program runs.

    • Entering a block expands the stack, storing local variables in memory.
    • Leaving a block shrinks the stack, flushing local variables from memory.
  • The scope of a local variable — that is, the context in which is visible and accessible to your program — is limited to the block in which it is declared, and all nested blocks.

    [Note]Note

    Java does not allow you to declare a local variable with the same name as a local variable already in the same scope.

public static void main(String[] args) {
    int x = 1;
    // A local scope where args and x are visible

    while (x <= 5) {
        String msg = "x is now " + x;
        // A local scope where args, x, and msg are visible
        System.out.println(msg);
    }
    // msg is no longer in scope, and has been flushed from memory
}

As previously mentioned, local variables must be initialized before they are used.

A local variable is visible only in the block of code (or sub blocks) where it is declared. Along with the variable’s value, its name is also flushed as the stack shrinks, allowing the same variable to be re-declared in another block of code.

[Note]Note

Method parameters are also considered local variables, because they too are declared (and initialized automatically by the calling code) on the stack.

[Tip]Tip

It is a good practice to limit the scope of local variables as much as possible. This promotes variable name reuse. In the case of objects, limiting local variable scope allows the JVM to quickly reclaim the memory occupied by objects that are no longer in use.

5.4. The return Statement

  • The return statement breaks out of the entire method.
  • It must return a value of the type specified in the method’s signature.
  • It must not return a value if the method’s return value is of type void.
  • The value can be an expression:

    public static int max(int x, int y) {
        return (x > y) ? x : y;
    }

public class FindNumber {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: FindNumber <num>");
            return;
        }
        int[] numbers = {1, 3, 4, 5, 7, 5, 2, 8, 9, 6};
        int findNumber = Integer.parseInt(args[0]);
        System.out.println(find(numbers, findNumber));
    }

    public static int find(int[] nums, int num) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == num) {
                return i;
            }
        }
        return -1;
    }
}

5.5. The if-else Statement

  • The if-else statement defines a block of code that is to be executed based on some boolean condition.

    if (boolean-expression) {
        // Run if BE is true
    } else if (boolean-expression2) {
        // Run if BE is false and BE2 is true
    } else {
        // Run if both BE and BE2 are false
    }
  • The else if part is optional and can appear many times between the if and else parts.
  • The else part is optional but can appear only once after if and all/any else if parts.

public class LetterGrade {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: LetterGrade <numeric-score>");
            return;
        }
        int score = Integer.parseInt(args[0]);
        char grade;

        if (score >= 90) {
            grade = 'A';
        } else if (score >= 80) {
            grade = 'B';
        } else if (score >= 70) {
            grade = 'C';
        } else if (score >= 60) {
            grade = 'D';
        } else {
            grade = 'F';
        }

        System.out.println("Grade = " + grade);
    }
}
[Note]Note

In this example, the numerical score is parsed from a string (the first and only command-line argument) into an integer data type.

5.6. The switch Statement

  • The switch statement is a special-purpose if-else-if-else construct that is easier and shorter to write:

    switch(expression) {
        case const1:
            /* do X */
            break;
        case const2:
            /* do Y */
            break;
        default:
            /* do something else */
    }
    • The switch expression must be an integral type other than long: byte, short, char, or int.
    • case values must be constants (not variables).
    • case values are tested for equality only (==).

public class MonthFromNumber {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: MonthFromNumber <month>");
            return;
        }
        int month = Integer.parseInt(args[0]);
        switch (month) {
            case 1:  System.out.println("January"); break;
            case 2:  System.out.println("February"); break;
            case 3:  System.out.println("March"); break;
            case 4:  System.out.println("April"); break;
            case 5:  System.out.println("May"); break;
            case 6:  System.out.println("June"); break;
            case 7:  System.out.println("July"); break;
            case 8:  System.out.println("August"); break;
            case 9:  System.out.println("September"); break;
            case 10: System.out.println("October"); break;
            case 11: System.out.println("November"); break;
            case 12: System.out.println("December"); break;
            default: System.out.println("Invalid month: " + month);
        }
    }
}
[Note]Note

You can also switch on enumerated values defined by enum, which is discussed in Typesafe Enums.

5.7. The switch Statement (cont.)

  • A case is an entry point into a switch statement, whereas a break acts as an exit point.

    • Typically each case has a trailing break.
    • Without a break, execution automatically “falls through” to the statements in the following case.

      [Tip]Tip

      If you intentionally fall through to the following case, include a comment to alert maintenance programmers of your intent. Otherwise, they might think you simply forgot the break and add it in later.

  • The optional default case cannot break and is executed if no other cases match and break prior to it.
  • To exit both the switch statement as well as the method in which it is defined, use a return in place of a break.

public class DaysInMonth {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.err.println("Usage: DaysInMonth <year> <month>");
            return;
        }
        int year = Integer.parseInt(args[0]);
        int month = Integer.parseInt(args[1]);
        int numDays = 0;

        switch (month) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                numDays = 31;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                numDays = 30;
                break;
            case 2:
                numDays =  ( ( (year % 4 == 0) && !(year % 100 == 0))
                            || (year % 400 == 0) )? 29 : 28;
                break;
            default:
                System.err.println("Invalid month " + month);
                return;
        }
        System.out.println("Number of Days = " + numDays);
    }
}

5.8. The while Loop Statement

  • The while loop statement executes a block of statements as long as a condition remains true:

    while (boolean-expression) {
        // Do something repeatedly
        // Update condition
    }
  • To ensure the loop body runs at least once, a do-while variant executes the statement block before evaluating the condition:

    do {
        // Do something at least once
    } while (boolean-expression);
[Note]Note

Remember to update the condition to avoid infinite loops.

public class WhileArguments {
    public static void main (String[] args) {
        int i = 0;
        while (i < args.length) {
            System.out.println(args[i]);
            i += 1;
        }
    }
}

5.9. The for Loop Statement

  • The for loop statement is similar to the while loop, but allows compact initialization of an iterator variable and its increment/decrement:

    for (init; condition; update) {
        // Do something
    }
    • The initialization statement runs before anything else.
    • The condition is evaluated before each repetition of the loop body.
    • The update statement is run after each body repetition. This can consist of multiple expression statements separated by commas (,).
[Note]Note

You can declare a local variable in the for initialization statement, in which case its scope is the loop body.

for (int i = 1; i <= 10; i++) {
    // i is local to this scope
}

public class ForArguments {
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }
}
[Tip]Tip

You can omit the initialization and/or update statements from the for loop, but you must still include the ; separator characters:

// x has been declared and initialized elsewhere
for (; x < 5; x++) System.out.println(x);

5.10. Using for to Iterate over Arrays and Collections

  • Java 5 also introduced an advanced for syntax known as for-each.

    • It is designed specifically for iterating over collections of data, whether whose collections are arrays, or some predefined collection classes (discussed in the Java Collections Framework module).
    • The for-each statement has the following syntax:

      for (Type element: collectionOfType) {
          // Do something with element
      }
    • For example:

      public class ForEachArguments {
          public static void main(String[] args) {
              for (String arg: args) {
                  System.out.println(arg);
              }
          }
      }

5.11. The break Statement

  • In addition to defining the exit points from cases in a switch statement, the break statement stops the execution of a loop:

    while (someCondition) {
        // Do something
        if (someOtherCondition) {
            break;
        }
        // Do something else
    }

public class SearchForNumber {
    public static void main (String[] args) {
        int[] nums = {1, 5, 4, 43, -2, 6, 4, 9 };
        int search = 4;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == search) {
                System.out.println("Found " + search + " at position " + i);
                break;
            }
        }
    }
}

5.12. The continue Statement

  • Skips one execution of a loop’s body
  • With continue, this:

    while (someCondition) {
        if (someOtherCondition) {
            continue;
        }
        // Do something
    }

    works the same as this:

    while (someCondition) {
        if (!someOtherCondition) {
            // Do something
        }
    }

public class SkipOddNumbers {
    public static void main(String[] args) {
        int[] nums = { 0, 3, 4, 5, 7, 2, 6, 9, 8, 7, 1 };
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] % 2 != 0) {
                continue;
            }
            System.out.println(nums[i] + " is even");
        }
    }
}

5.13. Nested Loops and Labels

  • Looping structures can be nested.

    • By default, a break or continue statement affects the innermost loop in which it is located.
  • You can apply an optional label to a looping structure.

    • The label is an identifier followed by a : placed before the looping structure.
  • Both the break and the continue statement accept an optional label argument.

    • In that case, the break or continue statement affects the looping structure with that label.
OUTER:
for (int i = 1; i <= 10; i++) {
    for (int j = 1; j <= 10; j++) {
        if (i == j) continue OUTER;
    }
}

public class SearchForNumber2D {
    public static void main (String[] args) {
        int[][] nums = { {1, 3, 7, 5},
                         {5, 8, 4, 6},
                         {7, 4, 2, 9} };
        int search = 4;
        foundNumber:
        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < nums[i].length; j++) {
                if (nums[i][j] == search) {
                    System.out.println(
                        "Found " + search + " at position " + i + "," + j);
                    break foundNumber;
                }
            }
        }
    }
}