Chapter 5

Using Loops in Java Code

In Chapter 4, “Using Java Arrays,” we used the while construct to populate an array with a section of repeating code. Without the means to loop through code the way we did, a task like populating an array is still possible, just far more cumbersome to implement. Loops can make any tedious, repetitive task easier to express in code. In this chapter, we’ll look at ways to use loops to make such tasks simpler and more economical.

Java supports four loop constructs. We’ll look at each one, compare them, and consider how best to apply each one. We’ll also look at how to break a loop when we arrive at odd conditions we can’t handle nicely in repeating code.

In this chapter, we’ll cover the following topics:

  • Applying while loops
  • Applying do/while loops
  • Applying for and for-each loops
  • Comparing loop constructs
  • Using continue and break statements

Applying while Loops

certobjective.eps

Looping is a staple of everyday programming. Sooner or later, every task in processing data boils down to iterating over multiple data in a routine format or object type. Sometimes you know how many items you’ve got and can supply a hard counting limit in your logic, but just as often you need a way to repeat code until some arbitrary condition is met. The while statement handles those situations.


A while statement is more or less an if statement that repeats, so it makes sense that they have a common syntax.

It is the easiest one to learn because it has the same syntax as an if statement:

while (boolean_test)
statementA;

The while keyword introduces a test expression, contained in parentheses, that evaluates to true or false. If true, then statementA executes. If false, control transfers to the end of the statement and the program continues. Just as with an if construct, if there is more than one statement you want to execute, you use code block separators:

while (boolean_test) {
statementA;
statementB;
// as many statements as you need
statementN;
}

Unlike an if test expression, a while test expression should have the potential to evaluate to true more than once. Otherwise you might as well use if. In fact, you can just replace if with while in a statement if your test condition changes from a one-time test to a recurring one or vice versa.

If you also have an else clause attached to the if statement, however, this drop-in replacement trick doesn’t apply. It’s a nice idea, but it has limited potential.

Don’t be lulled into thinking simple loops can create only simple problems. You can get your programs into trouble very quickly if you don’t observe two potential hazards. The first has to do with expressing your test condition in a clear way, ideally one that helps express the containing method’s context. The second has to do with creating an unintentional infinite loop.

Choosing a Useful while Test

The while statement is a bare-bones construct. As Figure 5-1 illustrates, it either executes a body of statements or it doesn’t.

Figure 5-1: Flow diagram of a while statement

c05f001.eps

What’s not evident in the construct is how the test expression changes from true to false. Without some forethought, you may not see how to do it without defeating the purpose of the loop. Consider the following piece of code:

public static void main(String args[]) {
boolean officeHours = true;
while (officeHours) {
// meet student;
// review paper;
officeHours = false;
   }
}

We avoided this problem in the last chapter by using an incrementing counter and testing for the length of the array.

This code initializes officeHours, starts the loop, executes its statements, and returns to the top to test. See the problem? This loop can execute only once. So how can we change the test condition to allow the loop multiple runs? Nesting in the while loop a condition that can change the test condition doesn’t cut it, and it just clutters the code anyway, as you see here:

public static void main(String args[]) {
boolean officeHours = true;
boolean studentsWaiting = true;
while (officeHours) {
// meet student
// review paper
if (!studentsWaiting)
officeHours = false;
   }
}

In order for studentsWaiting to become false, we have to change it somewhere in the body of the while statement. To do that, we need another loop to decrement a different value for the number of waiting students, and so it goes. The logic becomes tangled in referencing its own test variable in the right way under the right conditions, and the original intent gets buried.

We need some outside help to manage a terminal condition for the loop. In Java, the usual way to handle this is by calling a method that checks a flag for us. Let’s model the idea with pseudocode:

public class OfficeHours {
   public boolean officesOpen() {
      // if it's after 3pm or before 10am, return false
   }
   public void holdOfficeHours() {
      while (officesOpen()) {
         // meet student
         // review paper
      }
   }
   public static void main(String args[]) {
      OfficeHours oh = new OfficeHours();
      oh.holdOfficeHours();
   }
}

The logic and code here aren’t complete, but the comments give you the idea. Once a meeting concludes, the loop calls officesOpen(). If the building has closed, office hours are over. I rely on some arbitrary, external condition to indicate when the test should terminate.

Notice also that whoever calls the holdOfficeHours() method has no idea or signal how long this call should remain open. So far we’ve written programs that access fields or call methods and return very quickly. What’s your experience when you start a new program on your machine and nothing seems to happen? What expectations did you have when you started the program? When you’re adding loops of this sort to a program, you can expect that any user of your program may have the same questions.

In most cases, we choose a while statement because we don’t know or don’t want to be held to a specific number of counts. Some value way closer to two iterations than, say, infinity iterations would be best for most cases. It’s program logic that determines how long a loop might take, so it is the programmer who has to make sure infinity isn’t a possible outcome.

There’s nothing wrong with hard limits to a loop. The for loop is better designed for those cases and we’ll discuss it shortly. But it’s worth noting that no looping construct is immune from going off into forever. You always have to check that you’ll reach your intended limit or that your test condition will change.

Watching Out for Infinite Loops

When using a while loop, make sure there’s a distinction between an indefinite change and one that never occurs. A while loop, given a test condition that never changes, has no reason to stop. That’s not something you want in most programs.

There are some designed to run this way: An HTTP server, for example, or any program that hangs around waiting for client requests, uses what’s called a service loop. In such programs, there is a flag condition in the code that represents a life cycle change, like telling the program it has to restart or shut down.

A truly infinite loop is one that should have a changeable flag condition but doesn’t. It’s easy to write one:

public final class Infinite {
public static void main(String args[]) {
while (true)
       ;
   }
}
$ javac Infinite.java
$ ptime java Infinite
^C
real       11.474990632
user       10.915129410
sys         0.107862133

Notice the semicolon in the method body. This is an empty statement; it’s a way to express “do nothing” in your code. This seemingly simple bit of code can be quite nasty. To illustrate, I’ve used a command available in the Solaris operating system: ptime, which breaks down the time the program used. The real time is “wall-clock time,” or the duration of the program from start to finish. The user and sys times show how much CPU time was consumed by the program’s code and by requesting kernel services, such as allocating memory and opening files, respectively.

Most programs show a sizable gap between real time and the sum of user and sys time. The difference is called service time. It’s the sum of time your program had to wait on the system to get around to helping it. This program spends almost 96 percent of its time on the CPU testing whether true means true! The very low sys time means it has very few reasons to stop and ask the system for any help, so it’s all program logic, just about all the time.

Without any reporting, this program appears to do nothing. In fact, it runs its test condition as often (and as fast) as the JVM and hardware resources will allow. The only way to stop it is to kill the program.

How can this happen in your code? Take the idea of some logical test you’ve devised, and implement it in such a way that it looks right, so long as you don’t look too hard. For logic that manages what office hours are, it’s pretty easy. Let’s try writing some code for the officesOpen() method described earlier:

   public boolean officesOpen() {
      // after 10am and before 3pm, return true
      // otherwise return false
      if (hour < 1500) return true;
      if (hour > 1000) return true;
      return false;
   }

I want to say offices are open any hour before 3 p.m. and any hour after 10 a.m., where the field hour stores its value in military time format. The comment included with the code is a defensive statement, one that a reviewer can use to test the code’s intention against its implementation. Perhaps in the name of avoiding nested logic, the programmer uses the return keyword after each decision, hoping to filter out each true case so that only the false case remains at the end.

There’s a flaw in this implementation, however, that ensures officesOpen()will always return true. (Don’t read further until you can explain what it is!) Any caller that uses this method in its while loop will never terminate.

The more complex a while loop’s test expression gets, or the harder it is to read the implemented logic, the more vulnerable you become to this possibility. The time you spend isolating and debugging this behavior also tends to increase with the number of code lines that depend on it.

As program users, we quickly grow accustomed to letting error messages tell us something is wrong. As looping troubleshooters, we must learn to distinguish between a good kind of quiet (a busy but productive system) and a bad kind of quiet (a system happily spinning its wheels). Infinite loops will survive in your programs for as long as it takes you to recognize the difference.

In case you missed the flaw: any valid values for the hour field is either less than 1500 or more than 1000—some are both. There’s no case where this implementation should return false.

Applying do/while Loops

Java supports an alternate form of the while statement called a do/while loop. The do/while loop inverts the while statement’s flow of control, executing its code body first and its test expression second. A do/while loop therefore runs at least once. If the test evaluates to true, control returns to the top and the loop repeats. If it evaluates to false, program control escapes this statement to execute the next statement in the method. Figure 5-2 illustrates the flow.

Figure 5-2: Flow diagram of a do/while statement

c05f002.eps

The structure of the statement is accordingly simple:

do {
statementA;
statementB;
statementC;
} while (boolean_condition);

Because this form will execute its statement body at least once, it simplifies the case a while statement cannot handle on its own. A while statement will run zero times if its test evaluates to false on the first try. The only way around that without using do/while is by writing the loop statements twice, once before the test expression and once after:

statementA;
statementB;
statementC;
while (boolean_expression) {
statementA;
statementB;
statementC;
}

You probably won’t use the do/while statement a lot. When you do, however, it will save you from this unsightly workaround. Using our office hours example, let’s say the school administration issues a new rule requiring all faculty to see at least one student a day, even if they show up late. A programmer could implement that rule in code very simply:

do {
// meet student
// review paper
} while (officesOpen());

What happens to the administrators, once the faculty catch up with them, could be messy, but the code looks great.

The same cautions still apply. If the test condition never evaluates to true, a do/while loop can run only once. If that’s the case, all you’ve really produced is a trumped-up if statement. If the test condition never evaluates to false, you’ll again have the much-dreaded infinite loop on your hands.

Applying for and For-Each Loops

A counting variable provides a concrete way to manage a loop’s bounds. It’s an honorable technique, and you will see often it applied in tutorials, blogs, magazine articles, and everywhere else, for the very same reason we used it in Chapter 4—it’s easy to interpret:

int counter = 0;
while (counter < 10) {
//do something ten times
counter++;
}

So what’s wrong with it? Not anything, really. The counter variable is the simplest possible condition flag. It is incremented in the loop’s statement body, but it’s easy enough to understand why. It’s usually placed at the bottom so it’s easy to find, but it can occur wherever you need it to. The counter variable itself might be declared and initialized hundreds of lines away from the loop where it’s used, but searching for it through class code should be simple enough. If doing that proves tedious, Java allows you to declare variables anywhere inside the class body, so you can move it closest to where it’s used if that suits your taste.

Using for Loops

certobjective.eps

The for loop operates on this kind of iteration, but in a tighter fashion. The syntax seems more complicated at first sight:

for (initializers; test_condition; step_instructions)
statement;

Or you can use this syntax:

for (initializers; test_condition; step_instructions) {
statementA;
statementB;
// additional statements
statementN;
}

This syntax normalizes the use of counting variables (initializers), a test condition, and step instructions (which typically handle incrementing or decrementing). Now when we want to repeat a given step 10 times, say, the for statement puts that intention front and center:

for (int counter = 0; counter < 10; counter++)
   System.out.println("Counter: " + counter);

This example has all the elements of our last while statement, but it separates the mechanics of iteration from the statements we want to iterate. Notice that we declared and initialized the counter variable inside the for loop: perfectly legal. This liberty lets you constrain the scope of your counter variable to the for statement. The for statement does not, however, have its own namespace. If you declared counter already in the containing method, the compiler complains if you redeclare it here.

Visually, the flow of control matches Figure 5-3.

Figure 5-3: Flow diagram of a for statement

c05f003.eps

The for statement also allows for multiple initializations and step instructions. Just separate them with a comma, as shown in Listing 5-1.

Listing 5-1: Multiple initializations in a for loop

public final class ForLoop {
public static void main(String args[]) {
for (int i = 0, j = 29; j > 0; i++, j—)
         System.out.println("i * j = " + i*j);
    }
}
$ javac ForLoop.java
$ java ForLoop
i * j = 0
i * j = 28
i * j = 54
(output elided)
i * j = 54
i * j = 28

The for statement uses semicolons to delimit its three elements. I use the term step to refer to any action that advances the iteration. As Figure 5-3 suggests, the step should influence the test. You can step by incrementing, decrementing, or even calling a method that could change the outcome of the test expression.

An array’s length property provides a natural fit to a for statement. You can use it in place of a literal value for the number of elements in an array, as follows:

public final class ArrayLengthFor {
public static void main(String args[]) {
int [] squares = new int[7];
for (int i = 0; i < squares.length; i++) {
squares[i] = i * i;
         System.out.println("Square of " + i + " is " + squares[i]);
      }
   }
}
$ javac ArrayLengthFor.java
$ java ArrayLengthFor
Square of 0 is 0
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
Square of 6 is 36

Remember that arrays use an index; component addresses start at zero instead of one. It’s common to test for a value less than length because the last component position is always length-1.

We can now address an open question from the last chapter: how to initialize a multi-dimensional array in an efficient manner. To do it, we used what’s called a nested loop, placing one repeating body of code inside another.

Nesting Loops

You nest loop statements the same way you nest decision statements. You can also nest decisions inside of loops and loops inside of decisions. As far as the compiler is concerned, nesting to any level is fine so long as it’s syntactically correct. The practical limit for nesting, ideally, is how readable the structure is to someone unfamiliar with the program logic.

Let’s start with a simple example. I want to store a multiplication table of all the products for the numbers 1 through 12 using a two-dimensional array that provides 144 component positions. A nested loop is useful for this kind of work, but it’s not without a kink or two.

The outer loop operates in the first dimension; that is, the array that holds the other arrays. The inner loop operates on the integer components of each array component. That’s the easy part. The hard part is deciding how to relate the array positions themselves, which range from 0 to 11, to the numbers we want to store, which range from 1 to 12. In Listing 5-2, we adjust our counters by one.

Listing 5-2: Populating a two-dimensional array

public final class MultTable {
public static void main(String args[]) {
int products[][] = new int[12][12];
for (int i = 0; i < products.length; i++) {
for (int j = 0; j < products[i].length; j++) {
products[i][j] = (i + 1) * (j + 1);
            System.out.print("i: " + (i + 1) + "  ");
            System.out.print("j: " + (j + 1) + " =");
            System.out.println(" " + products[i][j]);
         }
         System.out.println();
      }
   }
}

The program starts out simply enough, creating a 12×12 array. The outer loop iterates through the first array. The inner loop populates each component array. Because we want products[0][0] to hold the result of 1×1, not zero times zero, we offset the counter value by one. We also use the offset to print index values to match the user’s expectations.

It may seem clumsy, but what are the alternatives? We could create a 13×13 matrix and disregard the components at position zero. Each array access would then correspond directly to its product value: products[11][11] would store 121, and so on.

Ignoring the zero index values, however, comes at a cost of 25 component spaces we’d create and never use. It’s not a big deal in a small array, but it’s not a small deal in a big array, either. It’s a trade-off between an efficient use of space and simpler program logic for array access.

Familiarize yourself with the offset technique in the MultTable class. It’s not rocket science, but whatever you store in a complex structure and however you choose to store it, you’ll have to make it relate your storage scheme to your presentation scheme. This task is a significant part of any programmer’s job, whether you’re deciding how to represent an integer in bits or deciding how to unroll a multi-dimensional array.

Avoiding Useless and Infinite Loops

You may be less likely to create useless or infinite loops with for statements, but you’re still free to do it. While the for statement normalizes the iteration setup, it doesn’t make the compiler any better at detecting common mistakes. We’re still concerned with making sure of the following:

  • The test condition evaluates to true at least once.
  • The loop can run multiple times.
  • The test condition can evaluate to false.

All other things being equal, it’s easier to spot some problems in a for statement. Programmers in search of adventure occasionally find ways to hide them anyway. Here are some fundamental bits of advice

Review any method call used in a step operation. A method call lets you supply complex logic for a step process without muddling the appearance of a for statement. That effort goes a long way to maintaining readable code. As the office-hours examples demonstrate, method calls are also great hiding places for infinity. Don’t assume such a method just works; check the code.

Read every test with a relational operator carefully. It’s very easy to gloss over one several times before you realize its logic is flawed; very easy. In preparing this chapter, as a matter of fact, my technical editor pointed out one problem in Listing 5-1 that I “corrected” twice before getting it right:

      for (int i = 0, j = 29; j > 0; i++, j—)
         System.out.println("i * j = " + i*j);

Using this part of the code, change the operator in the test expression to less than (<) and equals (==) and see what results you get. Then explain the outcome to demonstrate your understanding. Might be as plain as day to you; might not.

Beware of unreachable code in a for statement. It’s a less common occurrence than an infinite loop or a wrong-way relational test, and perhaps for that reason it’s harder to spot. Let’s look at the simplest possible example:

for (; false; );

Ugly for statement secret: The compiler doesn’t care if you omit all three elements between the parentheses.

The compiler will notice that the test condition never evaluates to true. In the name of optimizing your code, it takes advantage of every literal expression that it can. It will tell you the next statement can’t be reached, even if it’s empty.

If you remove the second semicolon in an attempt to remove the appearance of a third statement, the compiler will complain that there’s no longer a legal for statement. If instead you remove the false literal, leaving the code to look like this

for (;; );

the code will now compile. And now you’ve discovered the for statement version of an infinite loop. Which brings us to the final moral of this story: Using compiler errors to lead you to useful code is like following seagulls to dry land. You could get lucky; you could also end up sailing toward a trash barge.

Using For-Each Loops

Isn’t it great when a lot of low-level, detail-oriented work is already done for you? You bet it is. When it comes to arrays and similar data structures called Collections types, there’s some very good news. Java supports a version of the for loop specifically made for them, called a for-each loop.

The for-each loop is one kind of syntactic sugar that just seems to know how to iterate over arrays and other objects that contain data components. It works by inferring the details you would normally supply in a for or while statement. In a for statement, there’s no distinction between an arbitrary list of values, an array, or a Collections type. You have to tell it when iteration is finished:

for (int i = 0; i < products.length; i++) {
int row = products[i];
// do something with the row variable
}

The for-each construct, on the other hand, knows an array when it sees one and can find the length value on its own. (Well, okay, you have to give it an array [or Collections type] and it knows what to do with it, but still it’s pretty cool.) It can also infer the type of the array’s components on its own, leaving you a lot less to write:

for (int row: products) {
// do something with the row variable
}

The for-each construct applies the inversion-of-control principle. It infers the information it needs from the data so you don’t have to write it out.

Notice there is no counting variable or length property; it’s all determined by the construct by reading properties it expects the products object to include. You only have to supply a variable (row). The construct makes sure the variable is assigned the referent of each component when its iteration comes around. The variable type has to match the array component type, of course. If the products reference pointed to an array of Product referents, we’d simply change the expression to this:

for (Product prod: products) {
// do something with each Product component
}

It won’t take long to get accustomed to it. The for-each construct is far less cumbersome than a for loop and is designed specifically to make iterating over arrays and Collections types terse and elegant. You’ll still need the for statement to work with primitives and objects that are neither arrays nor a Collections-compliant type.

Using the For-Each Loop with a Collections Type

Using a for statement with a Collections type (all of which are kept in the java.util package) requires more coverage of the Collections API than falls within the scope of this book. But it’s hard to appreciate the full power of the for-each construct without seeing what it replaced, so we’ll take a peek at it.

All Collections types subscribe to a framework of common properties and behaviors. One of those behaviors is iterability, or the means all Collections types have to let the caller traverse its components. The importance of iterability is its guarantee that you see each object in the collection only once in a traversal. That guarantee is something you might take for granted with an ordered list, such as an array. With other types of collections, the rules for traversing them are neither intuitive nor self-evident, so iterability is not a minor promise.

A set, for example, doesn’t assign an order to its components. Sets are concerned with one thing: that each value in the collection is unique. Recall that a list uses its index scheme to disambiguate its components. From the list’s point of view, there can be no duplicate value once a component is associated with a position in the list. Fine. But how do you traverse a set of components if there’s no explicit order to them?


We use the term traverse to include types that let you walk through their components backward as well as forward.

Iterability tells us the underlying properties of any Collections type doesn’t have to matter to the caller if all they want is to see each component one time. If you call any Collections type’s iterator() method, you’ll receive an object (of type Iterator) that will iterate through its components for you. Depending on the type, some iterators will also traverse the collection.

The for-each loop understands the Iterator interface implicitly. A for statement doesn’t, although it can use the Iterator interface explicitly, as shown here:

for (Iterator<String> iter = collect.iterator(); iter.hasNext(); ) {
   String item = iter.next();
// do something stringy with item
}

Assume the collect variable refers to some Collections type; it doesn’t matter which one. Calling the variable’s iterator() method returns an Iterator reference; you can then use to iterate over collect’s components.

The Iterator interface includes two key methods: hasNext(), which returns true if there is at least one untouched object left in the Collections type, and next(), which returns a reference to the next available component. We have to leave the step instruction of the for statement empty; the next() method does the incrementing work behind the scenes.

In a for-each loop, by contrast, this work is understood. It looks the same for accessing a Collections type as it does for an array. The for-each equivalent to this for statement is as follows:

for (String item: collect) {
//do stuff with the item reference
}

We don’t need to manage the details of the Iterator, never mind the generics business. And while you will eventually need to learn generics, knowing you won’t have to refer to it just to iterate a Collections object is a big win. Enjoy it!

The convenience of this syntactic sugar, alas, comes at the expense of a subtle trade-off. The for-each construct is a build-up of existing services in the language that removes some of the tedium of everyday coding with arrays and collections. From the compiler’s point of view, it’s just a for loop with some extra information that lets it infer some looping information.

You can think of for-each as a student in a car designed for driver training. The student controls the vehicle to the degree the instructor allows it. Similarly, a for-each gets access to a collection’s components but only to the extent allowed by the underlying for construct.

To avoid confusion between the two constructs, the variable we use in a for-each construct gets a pseudoreference to each component. For-each iterations can’t change the referents they operate on in a way that persists when the iteration completes; they can only read them. Any change you make to the reference silently drops once the iteration is over. If it were strictly a read-only copy, you’d at least expect an error.

There’s also no explicit index value in a for-each construct, so you can’t determine the position of any component in an array or indexed collection. If you had to traverse two arrays in parallel, for example, you’d have to use the for statement to manage it. The for-each construct is a wonderful thing for simple cases, as intended, but it’s not a full, new feature to the language.

Comparing Loop Constructs

certobjective.eps

There isn’t much variety in loop constructs. The while and do/while statements differ in one respect only, and in my experience, that difference doesn’t come up very often. The for-each construct is just a smart version of the for statement, but it’s only smart about arrays and Collections types.

Comparing these constructs boils down to knowing the best use for each form. We have already covered the following points:

  • The while and for statements will run zero or more times.
  • The do/while statement will run one or more times.
  • The while and do/while statements work well with test expressions that change in an indefinite or program-specific manner.
  • The for statement works well with step-wise and counter-based iteration, such as populating arrays or repeating some code a fixed number of times.
  • The for-each construct is ideal for iterating one array or Collections type at a time.

Using continue and break Statements

In Chapter 3, “Using Java Operators and Conditional Logic,” we used the break statement in a switch statement. Recall that a switch statement uses case labels to separate a list of statements. It will transfer control to a case label if the case’s value matches the switch expression. This is one toned-down form of what we once called a goto statement.


In Java, the words goto and const are reserved, but given no meaning, so veteran programmers won’t try to use them.

Modern programming languages consider the goto statement a dangerous feature. In its original form, the goto statement let you transfer control from one point in your code to some arbitrary other point. In a language where the statements are numbered, like BASIC, you’d transfer control to the line number you want. Otherwise you’d use labels to name a point in the code to which you wanted to pass control. Of course it could work well, and of course it could go horribly wrong.

It is a powerful capability. Without restraint, however, it’s very easy for a codebase of steadily increasing size and/or complexity to transfer control into the weeds in such a way that no programmer knows how best to retrieve it. The consensus around this danger is strong enough that no modern programming language supports a goto statement.

Instead, Java has what some people call scope-based breaks. I think it’s simpler and more pointed to say you can use labels and breaks inside a method to achieve a goto-like effect. We’ll discuss how you can use these to simplify your code.

Using the continue Statement

certobjective.eps

The continue statement works only within a loop. You can use it to bypass the remaining statements in a loop’s body and begin the next iteration immediately.

The number of self-evident cases for this kind of use, I must admit, usually eludes me. If I have some code in which a continue statement would save me time and effort, I take it to mean the loop at hand could have been written to factor out the need. Then I want to rewrite the loop to do exactly that. As a result, I haven’t collected many cases where nothing will do a better job than a continue statement. Unfortunately for you, that means the following examples are artificial.

Let’s say we wanted to ignore every multiple of 10 in a for loop. We can test whether the counter’s current value is divisible by 10. If so, we just move on to the next iteration:

public final class Continue {
public static void main(String args[]) {
for (int i = 0; i < 31; i++) {
if ( i % 10 == 0 )
continue;
         System.out.print(i + " ");
      }
   }
}

$ javac Continue.java
$ java Continue
1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 18 19 21
       22 23 24 25 26 27 28 29

The term spaghetti code describes programs that have been modified so much it is a major, time-consuming task for anyone to read them.

Notice that no multiple of 10 appears in the output. The continue statement simply bypasses the print() method and gets on to the next iteration.

If instead you want to execute an alternate body of statements, you can also add an else statement inside the for loop; one branch uses continue, the other runs the alternate code. Be careful, though. A loop that keeps adding branch cases quickly turns into spaghetti code.

In a nested loop, a continue statement acts on the loop it’s in. A continue statement in an inner loop runs the next iteration of the inner loop only. What about a little more control? Say you’ve got some ugly triple-nested looping code you can’t rewrite without mortal consequences. At the same time, you need to break out from the innermost loop to the outermost loop. Can you do that?

Well, yes, if you must. It’s as simple as adding a label to a continue statement to specify the transfer of control. We cover that approach in a few more paragraphs.

Using the break Statement

certobjective.eps

The break statement works in a switch statement as well as in any loop. It transfers control to the end of the current statement, making it a “fall-through” actor in a statement body. What makes it safer than a goto, in the eyes of Java language designers, is that it transfers control in one direction only and never outside its containing method.

It should always be easy to discern where a break statement transfers control. Methods that have a switch or loop that runs dozens or even hundreds of lines long are, unfortunately, a common case where a break statement is used to patch some faulty logic instead of fixing the whole. A common technique I use when troubleshooting code I’ve received is just to search for the word break. If I see it more than half a dozen times, my hourly rate goes up. Or I don’t get the job, which, given the job at hand, is also okay.

Let’s say the faculty from our previous office hours example staged a successful coup. They wrote some new rules and decided office hours are over as soon as it can be determined the building has closed for the day:

public final class OfficeHours {
   public boolean officesOpen() {
      // if it's after 3pm or before 10am, return false
   }
   public void holdOfficeHours() {
      while (officesOpen()) {
         // meet student
         if (!officesOpen()) break;
         // review paper
         if (!officesOpen()) break;
      }
   }
   public static void main(String args[]) {
      OfficeHours oh = new OfficeHours();
      oh.holdOfficeHours();
   }
}

Since the holdOfficeHours() method contains just the while statement, breaking the loop causes the method to return control to the caller. In nested loops, the break statement also operates within the innermost loop that contains it.

Using Labeled Statements

In single loops it’s hard to find a meaningful case for using break or continue. It’s easier in simple cases to decompose a loop and manage odd cases with a separate if statement.

In a nested loop it’s a different story. One day, when you’ve become adept in Java, you’ll figure out how to untangle deeply nested loops, the hallmark of spaghetti code, altogether. Until then, however, you’re going to see quite a few of them, first on exams that want to see if your head will explode and then in business applications where it was someone’s challenge to bang out working code in half the time it should have taken.

One of those challenges will be how to escape some or all of the nested loops of a hopelessly complicated structure. For that, you need labels, similar to the ones you’ve already seen in a switch statement.

A label is just a named point in code. It can reside between any two statements that are contained by a loop. The name is followed by a colon. The label itself is not a statement. When control is transferred, the label does not execute; the statement that follows it does.

Let’s say we want to print our MultTable output but this time stop printing after the 11th row. We can place a label at the top of the outer loop, then call break once we’ve printed 11 × 11, or 121.

public final class MultTable {
public static void main(String args[]) {
int products[][] = new int[12][12];
// here's the label
      goestoeleven:
for (int i = 0; i < products.length; i++) {
for (int j = 0; j < products[i].length; j++) {
            products[i][j] = (i + 1) * (j + 1);
            System.out.print("i: " + (i + 1) + "  ");
            System.out.print("j: " + (j + 1) + " =");
            System.out.println(" " + products[i][j]);
if (products[i][j] == 121 ){
break goestoeleven;
            }
         }
        System.out.println();
      }
   }
}

A no-op statement doesn’t do anything. It’s useful when you want to say “do nothing” explicitly.

This action may appear counterintuitive at first. The label goestoeleven appears at the top of the outer loop. When we call the label with break, control goes to the top of the loop in order to transfer control to the bottom. This allows us to ignore the whole if statement and proceed. Without a statement following the if, even if it’s a no-op statement, this scheme would not compile.


The Essentials and Beyond
In this chapter we covered the four loop constructs that are available in Java: while, do/while, for, and for-each. We reviewed the uses for each form and compared them to consider which is best applied to a particular task. We also discussed the break and continue statements, which help circumvent normal iteration control, and you saw a way to use labeled forms of these statements to handle code that uses nested loops.
Additional Exercises
1. Write a loop that prints out a message once every million iterations. Compile and run the program. Let it run for about 10 seconds (clock time) to see how many statements get printed.
2. Write a program to calculate the sum of odd numbers from 0 to 150.
3. Using the last MultTable code presented in this chapter, change the break statement to continue. Before running it, write down what you expect to happen.
Review Questions
1. True or false: The statement while (true); will produce an infinite loop.
2. Select the illegal loop expressions from the following. (Choose all that apply.)
A. for (; false; );
B. for ( int i = -10; i < i; i++);
C. for (;; );
D. for (new Object(); 5 > 3; new Object());
3. What is the outcome for the following code?
do { } while (false);
A. It will not compile.
B. It will not run.
C. It will run infinitely.
D. It will run and finish.
4. How many times will the following loop execute?

int count = 0;
while (count <= 6) {
   count = count + 2;
}
A. Six
B. Five
C. Four
D. Three
5. Which term describes the process of looping through a statement body?
A. Stepping
B. Condition testing
C. Iterating
D. Enumerating
6. Consider the following code and select the correct analysis:

public final class ForEachLoop {
   public static void main(String args[]) {
      for (String arg: args)
         System.out.println(arg);
   }
}
A. The class will not compile.
B. The program will run the same with or without command-line arguments.
C. The program will throw errors when run without command-line arguments.
D. The program will throw an error once it runs out of command-line arguments.
7. Read the following code and select the correct analysis:

public final class ForLoop {
   public static void main(String args[]) {
      for (int i = (2 << 30—1); true; i++)
      System.out.println(i);
      break; 
   }
}
A. The code will not compile.
B. The code will run without error.
C. The code will run but bail out once it reaches the maximum positive value for Java integers.
D. The code will run but bail out when it reaches the break statement.
8. True or false: All other things being equal, a while statement is faster than a for statement because there’s less to set up.
9. Select the option that lists the three elements of a for loop declaration in the right order.
A. Counting, testing, iterating
B. Initial step, test step, loop instruction
C. Declare, test, execute
D. Initialize, test, step instruction
10. True or false: The for-each construct will work with an array or any kind of object that contains an array.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.78.83