Execution control

Bruce Eckel’s Thinking in Java Contents | Prev | Next

Java
uses all of C’s execution control statements, so if you’ve
programmed with C or C++ then most of what you see will be familiar. Most
procedural programming languages have some kind of control statements, and
there is often overlap among languages. In Java, the keywords include
if-else,
while,
do-while,
for,
and a selection statement called
switch.
Java does not, however, support the much-maligned
goto
(which can still be the most expedient way to solve certain types of problems).
You can still do a goto-like jump, but it is much more constrained than a
typical
goto.

true
and false

if-else

The
if-else
statement is probably the most basic way to control program flow. The
else
is optional, so you can use
if
in two forms:

if(Boolean-expression)

statement

or

if(Boolean-expression)

statement

else

statement

The
conditional must produce a Boolean result. The
statement
means either a simple statement terminated by a semicolon or a compound
statement, which is a group of simple statements enclosed in braces. Anytime
the word “
statement
is used, it always implies that the statement can be simple or compound.

As
an example of
if-else,
here is a

test( )
method
that will tell you whether a guess is above, below, or equivalent to a target
number:

static int test(int testval) {
  int result = 0;
  if(testval > target)
    result = -1;
  else if(testval < target)
    result = +1;
  else
    result = 0; // match
  return result;
}

It
is conventional to indent the body of a control flow statement so the reader
might easily determine where it begins and ends.


return

The
return
keyword has two purposes: it specifies what value a method will return (if it
doesn’t have a
void
return value) and it causes that value to be returned immediately. The
test( )
method above can be rewritten to take advantage of this:

static int test2(int testval) {
  if(testval > target)
    return -1;
  if(testval < target)
    return +1;
  return 0; // match
}

Iteration

while,
do-while
and
for
control looping and are sometimes classified as
iteration
statements
.
A
statement
repeats until the controlling
Booleanexpression
evaluates to false. The form for a
while
loop is

while(Boolean-expression)

statement

The
Boolean-expression
is evaluated once at the beginning of the loop and again before each further
iteration of the
statement.

Here’s
a simple example that generates random numbers until a particular condition is
met:

//: WhileTest.java
// Demonstrates the while loop
 
public class WhileTest {
  public static void main(String[] args) {
    double r = 0;
    while(r < 0.99d) {
      r = Math.random();
      System.out.println(r);
    }
  }
} ///:~ 

This
uses the
static
method
random( )
in the
Math
library, which generates a
double
value
between 0 and 1. (It includes 0, but not 1.) The conditional expression for the
while
says “keep doing this loop until the number is 0.99 or greater.”
Each time you run this program you’ll get a different-sized list of
numbers.

do-while

The
form for
do-while
is

do

statement

while(Boolean-expression);

for

A
for
loop performs initialization before the first iteration. Then it performs
conditional testing and, at the end of each iteration, some form of
“stepping.” The form of the
for
loop is:

for(initialization; Boolean-expression; step)

statement

Any
of the expressions
initialization,
Boolean-expression
or
step
can be empty. The expression is tested before each iteration, and as soon as it
evaluates to
false
execution will continue at the line following the
for
statement. At the end of each loop, the
step
executes.

for
loops are usually used for “counting” tasks:

//: ListCharacters.java
// Demonstrates "for" loop by listing
// all the ASCII characters.
 
public class ListCharacters {
  public static void main(String[] args) {
  for( char c = 0; c < 128; c++)
    if (c != 26 )  // ANSI Clear screen
      System.out.println(
        "value: " + (int)c +
        " character: " + c);
  }
} ///:~ 

Note
that the variable
c
is defined at the point where it is used, inside the control expression of the
for
loop, rather than at the beginning of the block denoted by the open curly
brace. The scope of
c
is
the expression controlled by the
for.

You
can define multiple variables within a
for
statement, but they must be of the same type:

for(int i = 0, j = 1;
    i < 10 && j != 11;
    i++, j++)
 /* body of for loop */;


The
comma operator

//: CommaOperator.java
 
public class CommaOperator {
  public static void main(String[] args) {
    for(int i = 1, j = i + 10; i < 5;
        i++, j = i * 2) {
      System.out.println("i= " + i + " j= " + j);
    }
  }
} ///:~ 

Here’s
the output:

i= 1 j= 11
i= 2 j= 4
i= 3 j= 6
i= 4 j= 8

You
can see that in both the initialization and step portions the statements are
evaluated in sequential order. Also, the initialization portion can have any
number of definitions
of
one type
.

break
and continue

Inside
the body of any of the iteration statements you can also control the flow of
the loop by using
break
and
continue.
break
quits the loop without executing the rest of the statements in the loop.
continue
stops the execution of the current iteration and goes back to the beginning of
the loop to begin a new iteration.

This
program shows examples of
break
and
continue
within
for
and
while
loops:

//: BreakAndContinue.java
// Demonstrates break and continue keywords
 
public class BreakAndContinue {
  public static void main(String[] args) {
    for(int i = 0; i < 100; i++) {
      if(i == 74) break; // Out of for loop
      if(i % 9 != 0) continue; // Next iteration
      System.out.println(i);
    }
    int i = 0;
    // An "infinite loop":
    while(true) {
      i++;
      int j = i * 27;
      if(j == 1269) break; // Out of loop
      if(i % 10 != 0) continue; // Top of loop
      System.out.println(i);
    }
  }
} ///:~ 

In
the
for
loop the value of
i
never gets to 100 because the
break
statement breaks out of the loop when
i
is 74. Normally, you’d use a
break
like this only if you didn’t know when the terminating condition was
going to occur. The
continue
statement causes execution to go back to the top of the iteration loop (thus
incrementing
i)
whenever
i
is not evenly divisible by 9. When it is, the value is printed.

The
second portion shows an “infinite loop” that would, in theory,
continue forever. However, inside the loop there is a
break
statement that will break out of the loop. In addition, you’ll see that
the
continue
moves back to the top of the loop without completing the remainder. (Thus
printing happens only when the value of
i
is divisible by 9.) The output is:

0
9
18
27
36
45
54
63
72
10
20
30
40

The
value 0 is printed because 0 % 9 produces 0.

A
second form of the infinite loop is
for(;;).
The compiler treats both
while(true)
and
for(;;)
in the same way so whichever one you use is a matter of programming taste.


The
infamous “goto”

The
goto
keyword
has been present in programming languages from the beginning. Indeed,
goto
was the genesis of program control in assembly language: “if condition A,
then jump here, otherwise jump there.” If you read the assembly code that
is ultimately generated by virtually any compiler, you’ll see that
program control contains many jumps. However,
goto
jumps
at the source-code level, and that’s what brought it into disrepute. If a
program will always jump from one point to another, isn’t there some way
to reorganize the code so the flow of control is not so jumpy?
goto
fell
into true disfavor with the publication of the famous “Goto considered
harmful” paper by Edsger Dijkstra, and since then goto-bashing has been a
popular sport, with advocates of the cast-out keyword scurrying for cover.

As
is typical in situations like this, the middle ground is the most fruitful. The
problem is not the use of
goto
but the overuse of
goto,
and in rare situations
goto
is the best way to structure control flow.

Although
goto
is
a reserved word in Java, it is not used in the language; Java has no
goto.
However, it does have something that looks a bit like a jump tied in with the
break
and
continue
keywords. It’s not a jump but rather a way to break out of an iteration
statement.
The
reason it’s often thrown in with discussions of
goto
is because it uses the same mechanism: a label.

label1:

label1:

outer-iteration
{

inner-iteration
{


//…


break; // 1


//…


continue; // 2


//…


continue label1; // 3


//…


break label1; // 4


}

}

In
case 1, the
break
breaks out of the inner iteration and you end up in the outer iteration. In
case 2, the
continue
moves back to the beginning of the inner iteration. But in case 3, the
continue
label1

breaks out of the inner iteration
and
the outer iteration, all the way back to
label1.
Then it does in fact continue the iteration, but starting at the outer
iteration. In case 4, the
break
label1

also breaks all the way out to
label1,
but it does not re-enter the iteration. It actually does break out of both
iterations.

Here
is an example using
for
loops:

//: LabeledFor.java
// Java’s "labeled for loop"
 
public class LabeledFor {
  public static void main(String[] args) {
    int i = 0;
    outer: // Can't have statements here
    for(; true ;) { // infinite loop
      inner: // Can't have statements here
      for(; i < 10; i++) {
        prt("i = " + i);
        if(i == 2) {
          prt("continue");
          continue;
        }
        if(i == 3) {
          prt("break");
          i++; // Otherwise i never
               // gets incremented.
          break;
        }
        if(i == 7) {
          prt("continue outer");
          i++; // Otherwise i never
               // gets incremented.
          continue outer;
        }
        if(i == 8) {
          prt("break outer");
          break outer;
        }
        for(int k = 0; k < 5; k++) {
          if(k == 3) {
            prt("continue inner");
            continue inner;
          }
        }
      }
    }
    // Can't break or continue
    // to labels here
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~ 

This
uses the
prt( )
method that has been defined in the other examples.

Note
that
break
breaks out of the
for
loop, and that the increment-expression doesn’t occur until the end of
the pass through the
for
loop. Since
break
skips the increment expression, the increment is performed directly in the case
of
i
== 3
.
The
continue
outer

statement in the case of
I
== 7
also
goes to the top of the loop and also skips the increment, so it too is
incremented directly.

Here
is the output:

i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer

If
not for the
break
outer

statement, there would be no way to get out of the outer loop from within an
inner loop, since
break
by itself can break out of only the innermost loop. (The same is true for
continue.)

Of
course, in the cases where breaking out of a loop will also exit the method,
you can simply use a
return.

Here
is a demonstration of labeled
break
and
continue
statements with
while
loops:

//: LabeledWhile.java
// Java's "labeled while" loop
 
public class LabeledWhile {
  public static void main(String[] args) {
    int i = 0;
    outer:
    while(true) {
      prt("Outer while loop");
      while(true) {
        i++;
        prt("i = " + i);
        if(i == 1) {
          prt("continue");
          continue;
        }
        if(i == 3) {
          prt("continue outer");
          continue outer;
        }
        if(i == 5) {
          prt("break");
          break;
        }
        if(i == 7) {
          prt("break outer");
          break outer;
        }
      }
    }
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~ 

The
same rules hold true for
while:

  1. A
    plain
    continue
    goes to the top of the innermost loop and continues.
  2. A
    labeled
    continue
    goes to the label and re-enters the loop right after that label.
  3. A
    break
    “drops out of the bottom” of the loop.
  4. A
    labeled
    break
    drops out of the bottom of the end of the loop denoted by the label.
The
output of this method makes it clear:

Outer while loop
i = 1
continue
i = 2
i = 3
continue outer
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer

It’s
important to remember that the
only
reason to use labels in Java is when you have nested loops and you want to
break
or
continue
through more than one nested level.

In
Dijkstra’s “goto considered harmful” paper, what he
specifically objected to was the labels, not the goto. He observed that the
number of bugs seems to increase with the number of labels in a program. Labels
and gotos make programs difficult to analyze statically, since it introduces
cycles in the program execution graph. Note that Java labels don’t suffer
from this problem, since they are constrained in their placement and
can’t be used to transfer control in an ad hoc manner. It’s also
interesting to note that this is a case where a language feature is made more
useful by restricting the power of the statement.

switch

The
switch
is sometimes classified as a
selection
statement
.
The
switch
statement
selects from among pieces of code based on the value of an integral expression.
Its form is:

switch(integral-selector)
{

case
integral-value1
:

statement;
break;

case
integral-value2

:
statement;
break;

case
integral-value3
:
statement;
break;

case
integral-value4
:
statement;
break;

case
integral-value5

:
statement;
break;


// …

default:
statement;

}

The
switch
statement is a clean way to implement multi-way selection (i.e., selecting from
among a number of different execution paths), but it requires a selector that
evaluates to an integral value such as
int
or
char.
If you want to use, for example, a string or a floating-point number as a
selector, it won’t work in a
switch
statement. For non-integral types, you must use a series of
if
statements.

Here’s
an example that creates letters randomly and determines whether they’re
vowels or consonants:

//: VowelsAndConsonants.java
// Demonstrates the switch statement
 
public class VowelsAndConsonants {
  public static void main(String[] args) {
    for(int i = 0; i < 100; i++) {
      char c = (char)(Math.random() * 26 + 'a');
      System.out.print(c + ": ");
      switch(c) {
      case 'a':
      case 'e':
      case 'i':
      case 'o':
      case 'u':
                System.out.println("vowel");
                break;
      case 'y':
      case 'w':
                System.out.println(
                  "Sometimes a vowel");
                break;
      default:
                System.out.println("consonant");
      }
    }
  }
} ///:~ 

Since
Math.random( )
generates a value between 0 and 1, you need only multiply it by the upper bound
of the range of numbers you want to produce (26 for the letters in the
alphabet) and add an offset to establish the lower bound.

Although
it appears you’re switching on a character here, the
switch
statement is actually using the integral value of the character. The
singly-quoted characters in the
case
statements also produce integral values that are used for comparison.

Notice
how the
cases
can be “stacked” on top of each other to provide multiple matches
for a particular piece of code. You should also be aware that it’s
essential to put the
break
statement at the end of a particular case, otherwise control will simply drop
through and continue processing on the next case.


Calculation
details

The
statement:

char
c = (char)(Math.random() * 26 + 'a');

deserves
a closer look.
Math.random( )
produces a
double,
so the value 26 is converted to a
double
to perform the multiplication, which also produces a
double.
This means that
‘a’
must be converted to a
double
to perform the addition. The
double
result is turned back into a
char
with a cast.

First,
what does the cast to
char
do? That is, if you have the value 29.7 and you cast it to a
char,
is the resulting value 30 or 29? The answer to this can be seen in this example:

//: CastingNumbers.java
// What happens when you cast a float or double
// to an integral value?
 
public class CastingNumbers {
  public static void main(String[] args) {
    double
      above = 0.7,
      below = 0.4;
    System.out.println("above: " + above);
    System.out.println("below: " + below);
    System.out.println(
      "(int)above: " + (int)above);
    System.out.println(
      "(int)below: " + (int)below);
    System.out.println(
      "(char)('a' + above): " +
      (char)('a' + above));
    System.out.println(
      "(char)('a' + below): " +
      (char)('a' + below));
  }
} ///:~ 

The
output is:

above: 0.7
below: 0.4
(int)above: 0
(int)below: 0
(char)('a' + above): a
(char)('a' + below): a

//: RandomBounds.java
// Does Math.random() produce 0.0 and 1.0?
 
public class RandomBounds {
  static void usage() {
    System.err.println("Usage: nt" +
      "RandomBounds lowernt" +
      "RandomBounds upper");
    System.exit(1);
  }
  public static void main(String[] args) {
    if(args.length != 1) usage();
    if(args[0].equals("lower")) {
      while(Math.random() != 0.0)
        ; // Keep trying
      System.out.println("Produced 0.0!");
    }
    else if(args[0].equals("upper")) {
      while(Math.random() != 1.0)
        ; // Keep trying
      System.out.println("Produced 1.0!");
    }
    else
      usage();
  }
} ///:~ 

To
run the program, you type a command line of either:

java
RandomBounds lower

or

java
RandomBounds upper

In
both cases you are forced to break out of the program manually, so it would
appear
that
Math.random( )
never produces either 0.0 or 1.0. But this is where such an experiment can be
deceiving. If you consider that there are 2
128
different double fractions between 0 and 1, the likelihood of reaching any one
value experimentally might exceed the lifetime of one computer, or even one
experimenter. It turns out that 0.0
is
included in the output of
Math.random( ).
Or, in math lingo, it is [0,1).

More by Author

Must Read