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

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 Boolean-expression 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

//: 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.

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

//: 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.

//: 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

//: 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: \n\t" +
      "RandomBounds lower\n\t" +
      "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).



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • The first phase of API management was about realizing the business value of APIs. This next wave of API management enables the hyper-connected enterprise to drive and scale their businesses as API models become more complex and sophisticated. Today, real world product launches begin with an API program and strategy in mind. This API-first approach to development will only continue to increase, driven by an increasingly interconnected web of devices, organizations, and people. To support this rapid growth, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds