In 1968 industry luminary Edsgar Dijkstra wrote a now famous article entitled "GOTO Statement
Considered Harmful"[1] in which he made a case for programming without branching constructs. He and
many others have commented over the years that you can express algorithms more clearly without them,
and educators and language designers have labored to usher in a goto-less programming world.
Have they succeeded? It depends. Their efforts have certainly raised the bar of structured programming,
with goto-filled languages like FORTRAN and BASIC giving way to better structured languages such as
Fortran-77, Pascal, Modula, C, and Visual BASIC. More programmers certainly think structured nowadays.
When is the last time you saw a goto in a technical article (besides this one, of course :-)?. Yet all popular
languages have always had goto as trap door, just in case you "needed" it.
Until now, that is. Java has no goto and does very well, thank you very much. In this article I'll explain
why, as well as look at all the issues pertaining to controlling program flow, including exceptions.
goto? Like anything else in life, the problem is not in the construct itself, but
rather in how it is used/abused. My first language was FORTRAN-IV, which had no else nor the notion
of a compound statement. Here's a sample:
IF (X .LT. 0) GOTO 10
IF (X .EQ. 0) GOTO 20
N = 1
Y = F(N)
GOTO 30
10 N = -1
Y = H(N)
GOTO 30
20 N = 0
Y = G(N)
30 CONTINUE
Okay, quick! What does this do? Can't you see the logic at a glance? If you can, I don't know if that's a
good thing or not! Here's how you might write it in C or Java:
if (x < 0)
{
n = -1;
y = f(n);
}
else if (x == 0)
{
n = 0;
y = g(n);
}
else
{
n = 1;
y = h(n);
}
Ah, much better! Of course seasoned C hackers might get carried away and do the following:
n = (x < 0) ? (f(-1), -1) : (x == 0) ? (f(0), 0) : (f(-1), -1);
which is not pretty, I'll admit, but even this atrocity is easier to follow than the FORTRAN version because
you don't have to jump all over the place. Don't try the line above in Java, though: it doesn't have a comma
operator.
I tend to liken moving from branching to structured logic to the jump from assembly language to a high
level language. You can do anything in assembler, but programming in C is clearer and less error prone.
Likewise, you can express any logic by littering a sequence of statements with gotos, but higher-level
constructs make your code more readable and easier to get right the first time.
I realize that in 1999 I might be preaching to the proverbial choir, but let's look at one more example to prove the point. What does the following BASIC program do?
140 lo = 1 150 hi = 100 160 if lo > hi then print "You cheated!" : goto 240 170 g = int((lo + hi) / 2) 180 print "Is it";g;" (L/H/Y)?" 190 input r$ 200 if r$ = "L" then lo = g+1 : goto 160 210 if r$ = "H" then hi = g-1 : goto 160 220 if r$ <> "Y" then print "What? Try again..." : goto 190 230 print "What fun!" 240 print "Wanna play again?" 250 input r$ 260 if r$ = "Y" then 140Since I used reasonably named variables, you probably guessed that this program plays the game of "Hi- Lo": it uses binary search to guess a number between 1 and 100. The user responds to each guess by telling whether it is too high or too low. If the variables
lo and hi ever cross (i.e., lo > hi), then the user
gave erroneous input. But again, it is difficult to infer the logic without careful study. Can you readily see
how many loops there are, and where they begin and end?
done for the outer loop, and found for the inner.
When it's time to terminate a loop, I just change the state of its control. This is the type of programming
style Bohm and Jacopini had in mind.
But what if you need to terminate a loop from within, i.e., before the last statement of its body? Somehow
you need to skip the statements that follow. Following the rules of structured programming you'd need to
nest the remainder of the loop body in an if statement, like this:
boolean done = false;
while (!done)
{
// <a bunch of statements here.>
if (<you DON'T need to exit the loop now>)
{
// <the rest of the loop body goes here>
}
else
done = true;
}
If you need to exit the loop in more than spot, you have a whole lot of nesting going on! To reflect the logic
more directly, Java, like C, has the break, continue, and return statements, which are just a
restricted form of goto. The break statement exits the immediately enclosing loop or switch, whereas
continue iterates on the enclosing loop. Using break in the loop above obviates the need for the
control variable and makes the logic more self-evident:
for (;;)
{
//
if ()
break;
//
}
So a little bit of goto ain't so bad. This is especially true with nested loops. The structured program in
Listing 2 has three loops, nested sequentially, and it wants to break out of all three loops when k becomes 1
in the innermost loop. To make this happen, it needs to set all loop control variables false. Java provides
a better way via the labeled break, which allows you to say, in effect, "I want to break out of the loop at
such and such a level of nesting." As the program in Listing 3 illustrates, you place a label (an identifier
followed by a colon, as in C) immediately before the loop(s) you want to directly break out of, and then
make that label the target of a break statement. Isn't it nice not to have to use extraneous boolean flags
that have no direct bearing on the meaning of your program? Listing 4 has a version of Hi-Lo that uses a
labeled break to allow the user to quit the game prematurely by typing the letter 'Q'. (In case you're
wondering what a BufferedReader is, I'm not going to explain the I/O in this article. Trust me and stay
tuned.)
Java also supports a labeled continue, which breaks out of any intermediate loops to iterate on the loop
specified by the label. For example, if you replace break with continue in Listing 3, the output is
0,0,0 0,0,1 1,0,0 1,0,1The branching constructs,
break, continue, and return, along with labeled break and continue,
make an unbridled goto capability unnecessary, so Java doesn't support it.
As an illustration, suppose you have functions f, g, and h, which execute in a nested fashion (see
Listing
5). These functions produce side effects and do not need to return any value. Suppose further that during
the execution of h a particular error might occur, and you want to return to the main program and start over.
The return-value technique requires h to return a code to g, then g to f and f to main. In this case you
have to alter your functions' signatures to accommodate the error handling, and error handling code is
scattered throughout your program (see Listing 6). To simulate errors in this example, I use Java's random
number generator (class Random) in h to return a 1 to indicate an error condition, 0 otherwise. The static
variable seed holds a number you type on the command line to seed the random number generator.
What a mess! Why not just "jump" from h to main? With exceptions you can. In Listing 7 I restored f and
g to their original form. Now if an error occurs in h, I throw an exception of type MyError, which is
caught in main. As you can see, exception-handling syntax in Java is virtually identical to C++: you wrap
code to be exception-tested in a try block at whatever level suits you, followed by one or more exception
handlers that catch objects of a specified class. To raise an exception you use the throw keyword. When
an exception is thrown, execution retraces its way back up the stack until it finds a handler that takes a
parameter of the same type (or of a supertype). The key differences between Java and C++ exception
handling are:
finally clause that facilitates program cleanup in the presence of exceptions (see below).
readLine as follows:
char r;
try {
r = in.readLine().toUpperCase().charAt(0);
}
catch (IOException x) {
// Abort after read error:
System.out.println("read error " + x);
System.exit(-1);
}
Unchecked exceptions include things that are difficult to detect at compile-time, such as an array index out
of bounds. These exceptions can occur almost anywhere, and it would be ridiculous to force the developer
to specify all such exceptions in all method specifications. Unchecked exceptions derive from either
RuntimeException or Error.
static void copy(String file) {
FileReader r = new FileReader(file);
int c;
while ((c = r.read()) != -1)
System.out.write(c);
r.close();
}
This won't compile because the FileRead constructor as well as read, write, and close throw
checked exceptions. The easy way to make the compiler happy is to add IOException to copy's
specification:
static void copy(String file)
throws IOException {
FileReader r = new FileReader(file);
This way the caller will get an exception so s/he knows something went wrong. So the compiler is happy,
but if read or write throw an exception the file doesn't get closed. One solution is to catch the exception
and close the file, but you have to rethrow the exception so the caller still gets the exception, like this:
static void copy(String file)
throws IOException {
FileReader r = new FileReader(file);
int c;
try {
while ((c = r.read()) != -1)
System.out.write(c);
}
catch (IOException x) {
r.close();
throw x; // rethrow
}
if (open)
r.close();
}
It's a pain to have to have two calls to close, and in a complicated program where many exceptions can
be thrown this technique is too tedious and error-prone to be acceptable. The C++ solution is to wrap the
lifetime of the file in an object and have the destructor close the file. Well, Java doesn't have destructors,
but it does has the finally clause, which is an even better solution in this case:
static void copy(String file)
throws IOException {
FileReader r = new FileReader(file);
int c;
try {
while ((c = r.read()) != -1)
System.out.write(c);
}
finally {
r.close();
}
}
Any code in a finally clause is executed no matter what, whether an exception occurred or not, or even
if a return statement occurs within the try block or any of its handlers. As the example above shows, you
don't need to have a catch clause to use finally. Since all I want to do in this case is to close the file
and let the exception pass back to the caller, I didn't need one. A complete program that uses the copy
method to print a file you specify on the command line is in Listing 8. Note that when you print an
exception object with System.out.println, it gives the type of the exception with an explanatory
message.
goto, but gives the programmer
enough flexibility to write readable and convenient structured code. Java's enforced exception
specifications finesse surprise exceptions, and the finally clause helps guarantee proper resource
management with minimum hassle. Nice.
[LISTING 1 - Hi-Lo in Java]
import java.io.*;
public class Hilo {
public static void main (String[] args)
throws IOException {
BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
boolean done = false;
while (!done) {
boolean found = false;
int lo = 1, hi = 100, guess = 0;
while (!found && lo <= hi) {
guess = (lo + hi) / 2;
System.out.println("Is it " + guess + "?");
char r = in.readLine().toUpperCase().charAt(0);
if (r == 'L')
lo = guess + 1;
else if (r == 'H')
hi = guess - 1;
else if (r != 'Y')
System.out.println("Try again...");
else
found = true;
}
if (lo > hi)
System.out.println("You cheated!");
else
System.out.println("Your number was " + guess);
System.out.println("Want to play again?");
done = in.readLine().toUpperCase().charAt(0) != 'Y';
}
}
}
[LISTING 2 - Exits a nested loop via boolean flags]
public class Nested {
public static void main(String[] args) {
boolean done1 = false;
for (int i = 0; !done1 && i < 2; ++i) {
boolean done2 = false;
for (int j = 0; !done2 && j < 2; ++j) {
boolean done3 = false;
for (int k = 0; !done3 && k < 2; ++k) {
System.out.println(i + "," + j + "," + k);
if (k == 1)
done3 = done2 = done1 = true;
}
}
}
}
}
/* Output:
0,0,0
0,0,1
*/
[LISTING 3 - Exits a nested loop with labeled break
public class Nested2 {
public static void main(String[] args) {
loop1:
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
for (int k = 0; k < 2; ++k) {
System.out.println(i + "," + j + "," + k);
if (k == 1)
break loop1;
}
}
}
}
}
[LISTING 4 - Hi-Lo with breaks]
import java.io.*;
public class Hilo2
{
public static void main (String[] args)
throws IOException {
BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
outer:
for (;;) {
int lo = 1, hi = 100, guess = 0;
while (lo <= hi) {
guess = (lo + hi) / 2;
System.out.println("Is it " + guess + "?");
char r = in.readLine().toUpperCase().charAt(0);
if (r == 'L')
lo = guess + 1;
else if (r == 'H')
hi = guess - 1;
else if (r == 'Q')
break outer;
else if (r != 'Y')
System.out.println("Try again...");
else
break;
}
if (lo > hi)
System.out.println("You cheated!");
else
System.out.println("Your number was " + guess);
System.out.println("Want to play again?");
if (in.readLine().toUpperCase().charAt(0) != 'Y')
break;
}
}
}
[LISTING 5 - Illustrates Nested method calls]
public class Deep {
static void f() {
System.out.println("doing f...");
g();
}
static void g() {
System.out.println("doing g...");
h();
}
static void h() {
System.out.println("doing h...");
}
public static void main(String[] args) {
f();
System.out.println("back in main");
}
}
/* Output:
doing f...
doing g...
doing h...
back in main
*/
[LISTING 6 - Uses return codes for errors]
import java.util.*; // For class Random
public class Deep2 {
static long seed;
static int f() {
System.out.println("doing f...");
return g();
}
static int g() {
System.out.println("doing g...");
return h();
}
static int h() {
Random r = new Random(seed);
int code = r.nextInt(2);
if (code == 0)
System.out.println("doing h...");
return code;
}
public static void main(String[] args) {
seed = Long.parseLong(args[0]);
int code = f();
if (code != 0)
System.out.println("f() returned " + code);
System.out.println("back in main");
}
}
/* Output of "java Deep2 0":
doing f...
doing g...
doing h...
back in main
Output of "java Deep2 1":
doing f...
doing g...
f() returned 1
back in main
*/
[LISTING 7 - Illustrates Exceptions]
import java.util.*;
class MyError extends Exception {}
public class Deep3 {
static long seed;
static void f() throws MyError {
System.out.println("doing f...");
g();
}
static void g() throws MyError {
System.out.println("doing g...");
h();
}
static void h() throws MyError {
Random r = new Random(seed);
if (r.nextInt(2) != 0)
throw new MyError();
System.out.println("doing h...");
}
public static void main(String[] args) {
seed = Long.parseLong(args[0]);
try {
f();
}
catch (MyError x) {
System.out.println("MyError occurred");
}
System.out.println("back in main");
}
}
/* Output of "java Deep3 1":
doing f...
doing g...
MyError occurred
back in main
*/
[LISTING 8 - Illustrates the finally clause]
import java.io.*;
public class Copy {
static void copy(String file)
throws IOException {
FileReader r = new FileReader(file);
int c;
try {
while ((c = r.read()) != -1)
System.out.write(c);
}
finally {
r.close();
}
}
public static void main(String[] args) {
try {
copy(args[0]);
}
catch (IOException x) {
System.out.println(x);
}
}
}
/* Output with a non-existent file:
java.io.FileNotFoundException: foo (The system cannot find the file
specified)
*/