Java Building Blocks: Statements

Empty, expression, compound, declaration, and basic flow-control statements

Overview

Simply put, a statement can be viewed as a command executed by the JVM. By default, execution of statements in a method or constructor runs in linear, top-to-bottom order; however, we can use flow-control statements to alter this sequence—executing some statements conditionally, some statements iteratively, some statements when an exception occurs, etc.

Syntax notes

Empty

An empty statement is just that: zero or more whitespace characters, followed by a semicolon. It might be surprising, but this is sometimes useful; however, it is often accidental, causing the code to execute in a manner that can confuse developers and testers (and, in the worst case, users). When used deliberately, it should be accompanied by code comments to indicate the intent.

Expression

An expression statement “does” something; that is, its purpose is to produce some side-effect of interest, modifying the state of the system in some way. These statements are always terminated by a semicolon, except when multiple expression statements are included in a for statement.

Expression statements include:

Declaration

A declaration statement declares a variable—optionally assigning it a value at the same time (declaration with assignment). A local variable must be assigned a value before it can be referenced. In the example that follows, local variables a, b and c are all declared, but because c is referenced before it is assigned a value, compilation fails at that point.

{
  int a = 1;             // Declaration with assignment.
  int b;                 // Declaration.
  int c;                 // Declaration.
  b = 10;                // Assignment.
  System.out.println(a);     
  System.out.println(b);     
  System.out.println(c); // Compilation failure.
}

Try executing the above code in JShell (there’s no need to include the comments), to see what happens.

Compound

A compound statement is simply a pair of braces enclosing zero or more statements (of any of these types). It is most often used as the body of a conditional or iteration statement, but a compound statement can be used virtually anywhere an expression or empty statement can be used.

Flow control

Within a method, or within a compound statement, flow proceeds in a linear fashion, from top to bottom. Flow control statements are used to alter that flow, by controlling the execution of one or more statements conditionally or iteratively.

if/if-else

This is the most common form of conditional statement in Java. Its syntax is

if (<condition>) 
    <statement>

where <condition> is a placeholder for a boolean-valued expression, and <statement> (often referred to as the body of the if) may be any of the types of statements listed here (except for a declaration statement—though a compound statement can be be used, with a conditional statement contained within that compound statement). If, at runtime, the value of <condition> is true, <statement> is executed.

In many style guides, <statement> is required to be a compound statement.

Optionally, else can be included:

if (<condition>) 
    <statement1>
else 
    <statement2>

Here, <statement2> (following else, and referred to as the body of the else) is executed if <condition> evaluates to false.

This example prints either “Weekend” or “Weekday”, depending on the current day of the week:

if (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) % 7 - 1 > 0) {
  System.out.println("Weekday");
} else {
  System.out.println("Weekend");
}

Many style guides require <statement2> to be a compound statement, unless it is itself an if or if-else statement. This leads to the common use of the if-else “ladder”:

if (<condition1>) 
    <statement1>
else if (<condition2>) 
    <statement2>
...
else 
    <statementN>

switch

The switch statement—inherited from C, and only incrementally improved since Java’s inception, until Java 14—evaluates an expression, and transfers execution to a case specifying a value equal to the evaluated expression:

switch (<expression>) {
  case <value1>:
    [statements1]
    ...
  case <value2>:
    [statements2]
    ... 
  ...
  case <valueN>:
    [statementsN]
    ...
 [default:]
    [statementsDefault]
    ...
}

Here, <expression is the expression evaluated, to determine to which (if any) of the case or default labels execution should jump. The type of <expression> must be one of these:

<value1><valueN> must be compile-time constant expressions—that is, they must be expressions that can be fully evaluated on compilation. They must be compatible in type with the value of <expression>, and they must all be distinct values.

Each of [statements1][statementsN] and [statementsDefault] can be zero or more statements of any of the statement types described here.

At runtime, if <expression> evaluates to a value equal to one of the case values, execution jumps to that point. If no matching case value is found, and there is a default label, then execution jumps to the default label.

Note that this jump mechanism is fairly rudimentary; in particular, execution continues from the matching case value until a break statement is encountered of the end of the switch is reached. This means that if a break is inadvertently omitted, execution will continue through statements associated with the cases below the matching value. Sometimes this is deliberate, particularly when we want the same statements to be executed for multiple values of <expression>; when not intentional, it can result in very confusing behavior.

There is no required order of case values, nor is default required to be at the end of the switch—if it’s present at all! (Many style guides dictate that default must be present in most cases—and when present, it must be at the end of the switch.)

The following example code fragment uses switch for a variation of the weekend/weekday example used above. Note that a case value need not be followed immediately by one or more statements; this can be used to execute the same code for multiple values.

switch (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) {
  case 1:
  case 7:
    System.out.println("Weekend");
    break;
  case 6:
    System.out.println("Almost the weekend");
    break;
  default:
    System.out.println("Weekday");
    break;  
}

while

This is the basic iteration statement in Java; the other iteration statements (for and do-while) can also be expressed in terms of while. The syntax is:

while (<condition>)
    <statement>

Here, <condition> is a boolean-valued expression, and <statement> (the body of the while) is any of the statement types described here, other than a declaration statement. (However, if a compound statement is used, a declaration can be included as part of that compound statement.)

When while is executed, these steps are followed:

  1. If the value of <condition> is true,

    • Execute <statement>.
    • Return to step 1.

    otherwise, proceed with execution of the next statement following the while statement.

Here, we have a while-based example of a simple counting loop. Try it in JShell, to see if it does what you expect.

int i = 0;
while (i < 10) {
  System.out.println(i);
  i++;
}

do-while

This iteration statement is very similar to while, but with one significant change: <condition> is evaluated after <statement> is executed. This is reflected in the “flipped” syntax:

do 
    <statement>
while (<condition>);

The execution steps here are:

  1. Execute <statement>.

  2. If <condition> is true, return to step 1.

  3. Proceed with execution of the next statement after the do-while statement.

In practice, the do-while is used when execution of <statement> is required to happen at least once before evaluating <condition>.

for (basic)

This is the traditional form of the for loop; it looks almost identical in all languages derived from C, though many have—like Java—incorporated a more advanced form over the years.

The syntax here combines a number of operations we might otherwise perform before entering a while loop, and in the body of the loop:

for ([initializer]; [condition]; [updater]) 
    <statement>

In this syntax, the placeholders are:

The flow of execution is as follows:

  1. Execute [initializer] (if present).

  2. If [condition] is present and true, or if it is absent,

    • Execute <statement>.
    • Execute [updater], if present.
    • Return to step 2.

    otherwise, proceed with execution of the next statement after the for statement.

The same counting example from while statement above can be r-ewritten to use for. Run it in JShell, to verify that you get the same output.

for (int i = 0; i < 10; i++) {
  System.out.println(i);
}

for (enhanced)

This is also known as the for-each loop, and exists in most languages, in one form or another.

Consider the use of a basic for statement to iterate over the elements of an array. This time let’s write the code in a method in JShell, and then invoke that method (as before, there’s no need to type or copy & paste the comment lines—but doing so won’t cause any errors).

// Define the method.

void printContents(int[] data) {
  System.out.printf("Array has %d elements:%n", data.length);
  for (int i = 0; i < data.length; i++) {
    System.out.println(data[i]);
  }
}

// Create an array

int[] numbers = {1, 11, 21, 1211, 111221, 312211};

// Invoke the method, passing the numbers array as an argument.

printContents(numbers);

When you execute the above, you should see this output:

Array has 6 elements:
1
11
21
1211
111221
312211

Iterating over the elements of an array in the way that the printContents method does is straightforward, and widely used in C-derived languages. However, it is prone to some kinds of errors—most notably the dreaded “off by one” error, where a programmer accidentally types <= instead of < in the [condition] part of the for statement, or types int i = 1 instead of int i = 0 in the [initializer] part.

When it comes down to it, the task of iterating over the contents of an array is so recurrent, and yet still requires such attention to small details when using the above syntax, that many languages have introduced alternative syntaxes that are less prone to error. In Java’s case, this led to the Java 5 introduction of the enhanced for.

The syntax of this form of the for statement is:

for (<current value declaration> : <array or Iterable>) 
    <statement>

Note that the placeholders in the parentheses aren’t optional anymore, and there are two of them, separated by a colon (vs. three separated by semicolons):

Again, <statement> is the body of the for statement, and it can be a statement of any type described here, other than a declaration statement.

At runtime, the flow proceeds as follows:

  1. Advance to the next element (the first element, on the initial iteration) element of <array or Iterable>, if it exists; otherwise, jump to step 5.

  2. Assign the value of the current element to the variable declared in <current value declaration>.

  3. Execute <statement>

  4. Return to step 1.

  5. Proceed with execution of the next statement after the for.

The printContents method we defined previously can now be rewritten this way:

void printContents(int[] data) {
  System.out.printf("Array has %d elements:%n", data.length);
  for (int value : data) {
    System.out.println(value);
  }
}

If we test it again,

int[] numbers = {1, 11, 21, 1211, 111221, 312211};
printContents(numbers);

we’ll see the same output as before.

break

The break statement can be used in the body of an iteration statement (for, while, or do-while) or a switch statement to jump to the end of the iteration or switch statement.

For example (a rather silly one), consider a variation of the counting example from above:

for (int i = 0; i < 10; i++) {
  System.out.println(i);
  if (i > 5) {
    break;
  }
}

Note that the break causes immediate termination of the for statement when i reaches a value greater than 5.

(There is also a variant of break, called a labeled break that can be used to break out of multiple nested iteration statements; however, this is beyond the scope of this introduction.)

continue

Within an iteration statement (for, while, or do-while), the continue statement is used to jump immediately to the next iteration.

For example, try executing this code fragment:

for (int i = 0; i < 10; i++) {
  if (i % 3 == 0) {
    continue;
  }
  System.out.println(i);
}

Note that the continue causes printing to be skipped for all i values that are multiples of 3 (including 0), since execution jumps immediately to the next iteration (and the next value of i) in those cases.

(There is also a variant of continue, called a labeled continue, that can be used with multiple nested iteration statements, to jump to the next iteration of a statement that encloses the current iteration statement; however, this is beyond the scope of this introduction.)