2.5. Statements
A
statement is a single command executed by the
Java interpreter. By default, the Java interpreter runs one statement
after another, in the order they are written. Many of the statements
defined by Java, however, are flow-control statements, such as
conditionals and loops, that alter this default order of execution in
well-defined ways. Table 2-5 summarizes the
statements defined by
Java.
Statement | Purpose | Syntax |
---|---|---|
expression | side effects | var = expr; expr++; method( ); new Type( ); |
compound | group statements | { statements } |
empty | do nothing | ; |
labeled | name a statement | label : statement |
variable | declare a variable | [final] type name [= value] [, name [= value]] ...; |
if | conditional | if ( expr ) statement [ else statement] |
switch | conditional | switch ( expr ) { [ case expr : statements ] ... [ default: statements ] } |
while | loop | while ( expr ) statement |
do | loop | do statement while ( expr ); |
for | simplified loop | for ( init ; test ; increment ) statement |
for/in | collection iteration | for ( variable : iterable ) statementJava 5.0 and later; also called "foreach" |
break | exit block | break [ label ] ; |
continue | restart loop | continue [ label ] ; |
return | end method | return [ expr ] ; |
synchronized | critical section | synchronized ( expr ) { statements } |
throw | throw exception | throw expr ; |
try | handle exception | try { statements } [ catch ( type name ) { statements } ] ... [ finally { statements } ] |
assert | verify invariant | assert invariant [ : error] ;Java 1.4 and later. |
2.5.1. Expression Statements
As we saw earlier in the chapter, certain
types of Java expressions have side effects. In other words, they do
not simply evaluate to some value; they also change the program state
in some way. Any expression with side effects can be used as a
statement simply by following it with a semicolon. The legal types of
expression statements are assignments, increments and decrements,
method calls, and object creation. For example:
a = 1; // Assignment
x *= 2; // Assignment with operation
i++; // Post-increment
--c; // Pre-decrement
System.out.println("statement"); // Method invocation
2.5.2. Compound Statements
A
compound statement is any number and kind of
statements grouped together within curly braces. You can use a
compound statement anywhere a statement is required by Java syntax:
for(int i = 0; i < 10; i++) {
a[i]++; // Body of this loop is a compound statement.
b[i]--; // It consists of two expression statements
} // within curly braces.
2.5.3. The Empty Statement
An empty statement
in Java is written as a single semicolon. The empty statement
doesn't do anything, but the syntax is occasionally
useful. For example, you can use it to indicate an empty loop body in
a for loop:
for(int i = 0; i < 10; a[i++]++) // Increment array elements
/* empty */; // Loop body is empty statement
2.5.4. Labeled Statements
A labeled
statement is simply a statement that has been given a name
by prepending an identifier and a colon to it. Labels are used by the
break and continue statements.
For example:
rowLoop: for(int r = 0; r < rows.length; r++) { // A labeled loop
colLoop: for(int c = 0; c < columns.length; c++) { // Another one
break rowLoop; // Use a label
}
}
2.5.5. Local Variable Declaration Statements
A local
variable , often simply called a variable, is a symbolic
name for a location to store a value that is defined within a method
or compound statement. All variables must be declared before they can
be used; this is done with a variable declaration statement. Because
Java is a strongly typed language, a variable declaration specifies
the type of the variable, and only values of that type can be stored
in the variable.In its simplest form, a variable
declaration specifies a variable's type and name:
int counter;A variable declaration can also
String s;
include an initializer : an expression that
specifies an initial value for the variable. For example:
int i = 0;The Java compiler does not allow you to use a local variable that has
String s = readLine( );
int[] data = {x+1, x+2, x+3}; // Array initializers are documented later
not been initialized, so it is usually convenient to combine variable
declaration and initialization into a single statement. The
initializer expression need not be a literal value or a constant
expression that can be evaluated by the compiler; it can be an
arbitrarily complex expression whose value is computed when the
program is run.A single variable
declaration statement can declare and initialize more than one
variable, but all variables must be of the same type. Variable names
and optional initializers are separated from each other with commas:
int i, j, k;In
float x = 1.0, y = 1.0;
String question = "Really Quit?", response;
Java 1.1 and later, variable declaration statements can begin with
the final keyword. This modifier specifies that
once an initial value is specified for the variable, that value is
never allowed to change:
final String greeting = getLocalLanguageGreeting( );C programmers should note that Java variable declaration statements
can appear anywhere in Java code; they are not restricted to the
beginning of a method or block of code. Local variable declarations
can also be integrated with the initialize
portion of a for loop, as we'll
discuss shortly.Local variables can be used only within the
method or block of code in which they are defined. This is called
their scope or lexical
scope :
void method( ) { // A method definition
int i = 0; // Declare variable i
while (i < 10) { // i is in scope here
int j = 0; // Declare j; the scope of j begins here
i++; // i is in scope here; increment it
} // j is no longer in scope; can't use it anymore
System.out.println(i); // i is still in scope here
} // The scope of i ends here
2.5.6. The if/else Statement
The if statement
is the fundamental control statement that allows Java to make
decisions or, more precisely, to execute statements conditionally.
The if statement has an associated expression and
statement. If the expression evaluates to true,
the interpreter executes the statement. If the expression evaluates
to false the interpreter skips the statement. In
Java 5.0, the expression may be of the wrapper type
Boolean instead of the primitive type
boolean. In this case, the wrapper object is
automatically unboxed.Here is an example if statement:
if (username == null) // If username is null,Although they look extraneous, the parentheses around the expression
username = "John Doe"; // use a default value
are a required part of the syntax for the if
statement.As I already mentioned, a block of statements enclosed in curly
braces is itself a statement, so we can also write
if statements that look like this:
if ((address == null) || (address.equals("))) {An if statement
address = "[undefined]";
System.out.println("WARNING: no address specified.");
}
can include an optional else keyword that is
followed by a second statement. In this form of the statement, the
expression is evaluated, and, if it is TRue, the
first statement is executed. Otherwise, the second statement is
executed. For example:
if (username != null)When you use nested if/else statements, some
System.out.println("Hello " + username);
else {
username = askQuestion("What is your name?");
System.out.println("Hello " + username + ". Welcome!");
}
caution is required to ensure that the else clause
goes with the appropriate if statement. Consider
the following lines:
if (i == j)In this example, the inner
if (j == k)
System.out.println("i equals k");
else
System.out.println("i doesn't equal j"); // WRONG!!
if statement forms the single statement allowed by
the syntax of the outer if statement.
Unfortunately, it is not clear (except from the hint given by the
indentation) which if the else
goes with. And in this example, the indentation hint is wrong. The
rule is that an else clause like this is
associated with the nearest if statement. Properly
indented, this code looks like this:
if (i == j)This is legal code, but
if (j == k)
System.out.println("i equals k");
else
System.out.println("i doesn't equal j"); // WRONG!!
it is clearly not what the programmer had in mind. When working with
nested if statements, you should use curly braces
to make your code easier to read. Here is a better way to write the
code:
if (i == j) {
if (j == k)
System.out.println("i equals k");
}
else {
System.out.println("i doesn't equal j");
}
2.5.6.1 The else if clause
The if/else
statement is useful for testing a condition and choosing between two
statements or blocks of code to execute. But what about when you need
to choose between several blocks of code? This is typically done with
an else if clause, which is not
really new syntax, but a common idiomatic usage of the standard
if/else statement. It looks like this:
if (n == 1) {There is nothing special about this code. It is just a series of
// Execute code block #1
}
else if (n == 2) {
// Execute code block #2
}
else if (n == 3) {
// Execute code block #3
}
else {
// If all else fails, execute block #4
}
if statements, where each if is
part of the else clause of the previous statement.
Using the else if idiom is
preferable to, and more legible than, writing these statements out in
their fully nested form:
if (n = = 1) {
// Execute code block #1
}
else {
if (n = = 2) {
// Execute code block #2
}
else {
if (n = = 3) {
// Execute code block #3
}
else {
// If all else fails, execute block #4
}
}
}
2.5.7. The switch Statement
An if statement
causes a branch in the flow of a program's
execution. You can use multiple if statements, as
shown in the previous section, to perform a multiway branch. This is
not always the best solution, however, especially when all of the
branches depend on the value of a single variable. In this case, it
is inefficient to repeatedly check the value of the same variable in
multiple if statements.A better solution is to use a switch statement,
which is inherited from the C programming language. Although the
syntax of this statement is not nearly as elegant as other parts of
Java, the brute practicality of the construct makes it worthwhile. If
you are not familiar with the switch statement
itself, you may at least be familiar with the basic concept, under
the name computed goto or jump table. A
switch statement starts with an expression whose
type is an int, short,
char, or byte. In Java 5.0
Integer, Short,
Character and Byte wrapper
types are allowed, as are enumerated types. (Enums are new in Java
5.0; see Chapter 4 for details on enumerated
types and their use in switch statements.) This
expression is followed by a block of code in curly braces that
contains various entry points that correspond to possible values for
the expression. For example, the following switch
statement is equivalent to the repeated if and
else/if statements shown in the previous section:
switch(n) {As
case 1: // Start here if n = = 1
// Execute code block #1
break; // Stop here
case 2: // Start here if n = = 2
// Execute code block #2
break; // Stop here
case 3: // Start here if n = = 3
// Execute code block #3
break; // Stop here
default: // If all else fails...
// Execute code block #4
break; // Stop here
}
you can see from the example, the various entry points into a
switch statement are labeled either with the
keyword case, followed by an integer value and a
colon, or with the special default keyword,
followed by a colon. When a switch statement
executes, the interpreter computes the value of the expression in
parentheses and then looks for a case label that
matches that value. If it finds one, the interpreter starts executing
the block of code at the first statement following the
case label. If it does not find a
case label with a matching value, the interpreter
starts execution at the first statement following a special-case
default: label. Or, if there is no
default: label, the interpreter skips the body of
the switch statement altogether.Note the use of the
break keyword at the end of each
case in the previous code. The
break statement is described later in this
chapter, but, in this case, it causes the interpreter to exit the
body of the switch statement. The
case clauses in a switch
statement specify only the starting point of the desired code. The
individual cases are not independent blocks of code, and they do not
have any implicit ending point. Therefore, you must explicitly
specify the end of each case with a break or
related statement. In the absence of break
statements, a switch statement begins executing
code at the first statement after the matching
case label and continues executing statements
until it reaches the end of the block. On rare occasions, it is
useful to write code like this that falls through from one
case label to the next, but 99% of the time you
should be careful to end every case and
default section with a statement that causes the
switch statement to stop executing. Normally you
use a break statement, but
return and throw also
work.A switch statement can have more than one
case clause labeling the same statement. Consider
the switch statement in the following method:
boolean parseYesOrNoResponse(char response) {The
switch(response) {
case 'y':
case 'Y': return true;
case 'n':
case 'N': return false;
default: throw new IllegalArgumentException("Response must be Y or N");
}
}
switch statement and its case
labels have some important restrictions. First, the expression
associated with a switch statement must have a
byte, char,
short, or int value. The
floating-point and boolean types are not
supported, and neither is long, even though
long is an integer type. Second, the value
associated with each case label must be a constant
value or a constant expression the compiler can evaluate. A
case label cannot contain a runtime expression
involving variables or method calls, for example. Third, the
case label values must be within the range of the
data type used for the switch expression. And
finally, it is obviously not legal to have two or more
case labels with the same value or more than one
default label.
2.5.8. The while Statement
Just as the
if statement is the basic control statement that
allows Java to make decisions, the while statement
is the basic statement that allows Java to perform repetitive
actions. It has the following syntax:
while (expression)The while statement
statement
works by first evaluating the expression,
which must result in a boolean (or, in Java 5.0, a
Boolean) value. If the value is
false, the interpreter skips the
statement associated with the loop and
moves to the next statement in the program. If it is
true, however, the
statement that forms the body of the loop
is executed, and the expression is
reevaluated. Again, if the value of
expression is false,
the interpreter moves on to the next statement in the program;
otherwise it executes the statement again.
This cycle continues while the expression
remains true (i.e., until it evaluates to
false), at which point the
while statement ends, and the interpreter moves on
to the next statement. You can create an
infinite loop
with the syntax while(true).Here is an example while loop that prints the
numbers 0 to 9:
int count = 0;As you can see, the
while (count < 10) {
System.out.println(count);
count++;
}
variable count starts off at 0 in this example and
is incremented each time the body of the loop runs. Once the loop has
executed 10 times, the expression becomes false
(i.e., count is no longer less than 10), the
while statement finishes, and the Java interpreter
can move to the next statement in the program. Most loops have a
counter variable like count. The variable names
i, j, and k
are commonly used as loop counters, although you should use more
descriptive names if it makes your code easier to understand.
2.5.9. The do Statement
A do loop is much
like a while loop, except that the loop expression
is tested at the bottom of the loop rather than at the top. This
means that the body of the loop is always executed at least once. The
syntax is:
doNotice a couple of differences between the do loop
statement
while ( expression ) ;
and the more ordinary while loop. First, the
do loop requires both the do
keyword to mark the beginning of the loop and the
while keyword to mark the end and introduce the
loop condition. Also, unlike the while loop, the
do loop is terminated with a semicolon. This is because the
do loop ends with the loop condition rather than
simply ending with a curly brace that marks the end of the loop body.
The following do loop prints the same output as
the while loop just discussed:
int count = 0;The do loop is much less commonly used than its
do {
System.out.println(count);
count++;
} while(count < 10);
while cousin because, in practice, it is unusual
to encounter a situation where you are sure you always want a loop to
execute at least once.
2.5.10. The for Statement
The
for statement provides a looping construct that is
often more convenient than the while and
do loops. The for statement
takes advantage of a common looping pattern. Most loops have a
counter, or state variable of some kind, that is initialized before
the loop starts, tested to determine whether to execute the loop
body, and then incremented or updated somehow at the end of the loop
body before the test expression is evaluated again. The
initialization,
test, and update steps are the three
crucial manipulations of a loop variable, and the
for statement makes these three steps an explicit
part of the loop syntax:
for(initialize ; test ; update)This for loop is basically equivalent to the
statement
following while loop:[2]
[2] As
you'll see when we consider the
continue statement, this while
loop is not exactly equivalent to the for
loop.
initialize;Placing the initialize,
while(test) {
statement;
update;
}
test, and
update
expressions at the top of a
for loop makes it especially easy to understand
what the loop is doing, and it prevents mistakes such as forgetting
to initialize or update the loop variable. The interpreter discards
the values of the initialize and
update expressions, so in order to be
useful, these expressions must have side effects.
initialize is typically an assignment
expression while update is usually an
increment, decrement, or some other assignment.The following for loop prints the numbers 0 to 9,
just as the previous while and
do loops have done:
int count;Notice how this syntax places all the important information about the
for(count = 0 ; count < 10 ; count++)
System.out.println(count);
loop variable on a single line, making it very clear how the loop
executes. Placing the update expression in the for
statement itself also simplifies the body of the loop to a single
statement; we don't even need to use curly braces to
produce a statement block.The for loop supports some additional syntax that
makes it even more convenient to use. Because many loops use their
loop variables only within the loop, the for loop
allows the initialize expression to be a
full variable declaration, so that the variable is scoped to the body
of the loop and is not visible outside of it. For example:
for(int count = 0 ; count < 10 ; count++)Furthermore, the for loop syntax does not restrict
System.out.println(count);
you to writing loops that use only a single variable. Both the
initialize and
update expressions of a
for loop can use a comma to separate multiple
initializations and update expressions. For example:
for(int i = 0, j = 10 ; i < 10 ; i++, j--)Even though all the examples so far have counted numbers,
sum += i * j;
for loops are not restricted to loops that count
numbers. For example, you might use a for loop to
iterate through the elements of a linked list:
for(Node n = listHead; n != null; n = n.nextNode( ))The initialize,
process(n);
test, and
update expressions of a
for loop are all optional; only the semicolons
that separate the expressions are required. If the
test expression is omitted, it is assumed
to be true. Thus, you can write an infinite loop
as for(;;).
2.5.11. The for/in Statement
The for/in
statement is a powerful new loop
that was added to the language in Java 5.0. It iterates through the
elements of an array or collection or any object that implements
java.lang.Iterable (we'll see
more about this new interface in a moment). On each iteration it
assigns an element of the array or Iterable object
to the loop variable you declare and then executes the loop body,
which typically uses the loop variable to operate on the element. No
loop counter or Iterator object is involved; the
for/in loop performs the iteration automatically,
and you need not concern yourself with correct initialization or
termination of the loop.A for/in loop
is written as the keyword for followed by an open
parenthesis, a variable declaration (without initializer), a colon,
an expression, a close parenthesis, and finally the statement (or
block) that forms the body of the loop.
for( declaration : expression )Despite its name, the for/in loop does not use the
statement
keyword in. It is common to read the colon as
"in," however. Because this
statement does not have a keyword of its own, it does not have an
unambiguous name. You may also see it called
"
enhanced for" or
"foreach."For the while, do, and
for loops, we've shown an example
that prints ten numbers. The for/in loop can do
this too, but not on its own. for/in is not a
general-purpose loop like the others. It is a specialized loop that
executes its body once for each element in an
array or collection. So, in order to loop ten times (to print out ten
numbers), we need an array or other collection with ten elements.
Here's code we can use:
// These are the numbers we want to printHere are some more things you should know about the syntax of the
int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
// This is the loop that prints them
for(int n : primes)
System.out.println(n);
for/in loop:
- As noted earlier, expression must be
either an array or an object that implements the
java.lang.Iterable
interface. This type must be known at
compile-time so that the compiler can generate appropriate looping
code. For example, you can't use this loop with an
array or List that you have cast to an
Object. - The type of the array or Iterable elements must be
assignment-compatible with the type of the
variable declared in the
declaration. If you use an
Iterable object that is not parameterized with an
element type, the variable must be declared as an
Object. (Parameterized types are also new in Java
5.0; they are covered in Chapter 4.) - The
declaration usually consists of just a
type and a variable name, but it may include a
final modifier and any appropriate
annotations (see Chapter 4). Using
final prevents the loop variable from taking on
any value other than the array or collection element the loop assigns
it and serves to emphasize that the array or collection cannot be
altered through the loop variable. - The loop variable of the for/in loop must be
declared as part of the loop, with both a type and a variable name.
You cannot use a variable declared outside the loop as you can with
the for loop.
The following class further illustrates the use of the
for/in
statement. It relies on parameterized types, which are covered in
Chapter 4, and you may want to return to this
section after reading that chapter.
import java.util.*;
public class ForInDemo {
public static void main(String[] args) {
// This is a collection we'll iterate over below.
Set<String> wordset = new HashSet<String>( );
// We start with a basic loop over the elements of an array.
// The body of the loop is executed once for each element of args[].
// Each time through one element is assigned to the variable word.
for(String word : args) {
System.out.print(word + " ");
wordset.add(word);
}
System.out.println( );
// Now iterate through the elements of the Set.
for(String word : wordset) System.out.print(word + " ");
}
}
2.5.11.1 Iterable and iterator
To understand how the for/in loop works with
collections, we need to consider
two interfaces,
java.lang.Iterable , introduced in Java 5.0, and
java.util.Iterator, introduced in Java 1.2, but
parameterized
with the rest of the Collections Framework in Java 5.0.[3] The APIs of both
interfaces are reproduced here for
convenience:
[3] If you are not already familiar with parameterized types, you
may want to skip this section now and return to it after reading
Chapter 4.
public interface Iterator<E> {Iterator defines a way to iterate through the
boolean hasNext( );
E next( );
void remove( );
}
elements of a collection or other data structure. It works like this:
while there are more elements in the collection (hasNext(
) returns true), call next(
) to obtain the next element of the collection. Ordered
collections, such as lists, typically have iterators that guarantee
that they'll return elements in order. Unordered
collections like Set simply guarantee that
repeated calls to next( ) return all elements of
the set without omissions or duplications but do not specify an
ordering.
public interface Iterable<E> {The Iterable interface was introduced to make the
java.util.Iterator<E> iterator( );
}
for/in loop work. A class implements this
interface in order to advertise that it is able to provide an
Iterator to anyone interested. (This can be useful
in its own right, even when you are not using the
for/in loop). If an object is
Iterable<E>, that means that that it has an
iterator( )
method that returns an Iterator<E>, which
has a next( ) method that returns an object of
type E. If you implement
Iterable and provide an
Iterator for your own classes,
you'll be able to iterate over those classes with
the for/in loop.Remember that if you use the for/in loop with an
Iterable<E>, the loop variable must be of
type E or a superclass or interface. For example,
to iterate through the elements of a
List<String>, the variable must be declared
String or its superclass
Object, or one of its interfaces
CharSequence, Comparable, or
Serializable.If you use for/in to iterate through the elements
of a raw List with no type parameter, the
Iterable and Iterator also have
no type parameter, and the type returned by the next(
) method of the raw Iterator is
Object. In this case, you have no choice but to
declare the loop variable to be an Object.
2.5.11.2 What for/in cannot do
for/in
is a specialized loop that can simplify your code and reduce the
possibility of looping errors in many circumstances. It is not a
general replacement for the while,
for, or do loops, however,
because it hides the loop counter or Iterator from
you. This means that some algorithms simply cannot be expressed with
a for/in loop.Suppose you want to print the elements of an array as a
comma-separated list. To do this, you need to print a comma after
every element of the array except the last, or equivalently, before
every element of the array except the first. With a traditional
for loop, the code might look like this:
for(int i = 0; i < words.length; i++) {This is a very straightforward task, but you simply cannot do it with
if (i > 0) System.out.print(", ");
System.out.print(words[i]);
}
for/in. The problem is that the
for/in loop doesn't give you a
loop counter or any other way to tell if you're on
the first iteration, the last iteration, or somewhere in between.
Here are two other simple loops that can't be
converted to use for/in, for the same basic
reason:
String[] args; // Initialized elsewhereA similar issue exists when using for/in to
for(int i = 0; i < args.length; i++)
System.out.println(i + ": " + args[i]);
// Map words to the position at which they occur.
List<String> words; // Initialized elsewhere
Map<String,Integer> map = new HashMap<String,Integer>( );
for(int i = 0, n = words.size( ); i < n; i++) map.put(words.get(i), i);
iterate through the elements of the collection. Just as a
for/in loop over an array has no way to obtain the
array index of the current element, a for/in loop
over a collection has no way to obtain the
Iterator object that is being used to itemize the
elements of the collection. This means, for example, that you cannot
use the remove( ) method of the iterator (or any
of the additional methods defined by
java.util.ListIterator) as you could if you used
the Iterator explicitly yourself.Here are some other things you cannot do with
for/in:
- Iterate backwards through the elements of an array or
List. - Use a single loop counter to access the same-numbered elements of two
distinct arrays. - Iterate through the elements of a List using calls
to its get( ) method rather than calls to its
iterator.
2.5.12. The break Statement
A
break statement causes the
Java interpreter to skip
immediately to the end of a containing statement. We have already
seen the break statement used with the
switch statement. The break
statement is most often written as simply the keyword
break followed by a semicolon:
break;When used in this form, it causes the Java interpreter to immediately
exit the innermost containing while,
do, for, or
switch statement. For example:
The break statement can also be followed by the
for(int i = 0; i < data.length; i++) { // Loop through the data array.
if (data[i] = = target) { // When we find what we're looking for,
index = i; // remember where we found it
break; // and stop looking!
}
} // The Java interpreter goes here after executing break
name of a containing labeled statement. When used in this
form, break causes the Java interpreter to
immediately exit the named block, which can be any kind of statement,
not just a loop or switch. For example:
testfornull: if (data != null) { // If the array is defined,
for(int row = 0; row < numrows; row++) { // loop through one dimension,
for(int col = 0; col < numcols; col++) { // then loop through the other.
if (data[row][col] = = null) // If the array is missing data,
break testfornull; // treat the array as undefined.
}
}
} // Java interpreter goes here after executing break testfornull
2.5.13. The continue Statement
While a break
statement exits a loop, a continue statement quits
the current iteration of a loop and starts the next one.
continue, in both its unlabeled and labeled forms,
can be used only within a while,
do, or for loop. When used
without a label, continue causes the innermost
loop to start a new iteration. When used with a label that is the
name of a containing loop, it causes the named loop to start a new
iteration. For example:
for(int i = 0; i < data.length; i++) { // Loop through data.while,
if (data[i] = = -1) // If a data value is missing,
continue; // skip to the next iteration.
process(data[i]); // Process the data value.
}
do, and for loops differ
slightly in the way that continue starts a new
iteration:
- With a while loop, the Java interpreter simply
returns to the top of the loop, tests the loop condition again, and,
if it evaluates to true, executes the body of the
loop again. - With a do loop, the interpreter jumps to the
bottom of the loop, where it tests the loop condition to decide
whether to perform another iteration of the loop. - With a for loop, the interpreter jumps to the top
of the loop, where it first evaluates the
update expression and then evaluates the
test expression to decide whether to loop
again. As you can see, the behavior of a for loop
with a continue statement is different from the
behavior of the "basically
equivalent" while loop presented
earlier; update gets evaluated in the
for loop but not in the equivalent
while loop.
2.5.14. The return Statement
A
return statement tells the Java interpreter to
stop executing the current method. If the method is declared to
return a value, the return statement is followed
by an expression. The value of the expression becomes the return
value of the method. For example, the following method computes and
returns the square of a number:
double square(double x) { // A method to compute x squaredSome
return x * x; // Compute and return a value
}
methods are declared void to indicate that they do
not return any value. The Java interpreter runs methods like this by
executing their statements one by one until it reaches the end of the
method. After executing the last statement, the interpreter returns
implicitly. Sometimes, however, a void method has
to return explicitly before reaching the last statement. In this
case, it can use the return statement by itself,
without any expression. For example, the following method prints, but
does not return, the square root of its argument. If the argument is
a negative number, it returns without printing anything:
void printSquareRoot(double x) { // A method to print square root of x
if (x < 0) return; // If x is negative, return explicitly
System.out.println(Math.sqrt(x)); // Print the square root of x
} // End of method: return implicitly
2.5.15. The synchronized Statement
Java
makes it easy to write multithreaded programs (see Chapter 5 for examples). When working with multiple
threads, you must often take care to prevent multiple threads from
modifying an object simultaneously in a way that might corrupt the
object's state. Sections of code that must not be
executed simultaneously are known as
critical
sections . Java provides the
synchronized statement to protect these critical
sections. The syntax is:
synchronized ( expression ) {expression
statements
}
is an expression that must evaluate to an object or an array. The
statements constitute the code of the
critical section and must be enclosed in curly braces. Before
executing the critical section, the Java interpreter first obtains an
exclusive lock on the object or array specified by
expression. It holds the lock until it is
finished running the critical section, then releases it. While a
thread holds the lock on an object, no other thread can obtain that
lock. Therefore, no other thread can execute this or any other
critical sections that require a lock on the same object. If a thread
cannot immediately obtain the lock required to execute a critical
section, it simply waits until the lock becomes available.Note that you do not have to use the synchronized
statement unless your program creates multiple threads that share
data. If only one thread ever accesses a data structure, there is no
need to protect it with synchronized. When you do
have to use synchronized, it might be in code like
the following:
public static void SortIntArray(int[] a) {The
// Sort the array a. This is synchronized so that some other thread
// cannot change elements of the array while we're sorting it (at
// least not other threads that protect their changes to the array
// with synchronized).
synchronized (a) {
// Do the array sort here
}
}
synchronized keyword is also available as a
modifier in Java and is more commonly used in this form than as a
statement. When applied to a method, the
synchronized keyword indicates that the entire
method is a critical section. For a synchronized
class method (a static method), Java obtains an exclusive lock on the
class before executing the method. For a
synchronized instance method, Java obtains an
exclusive lock on the class instance. (Class and instance methods are
discussed in Chapter 3.)
2.5.16. The throw Statement
An exception is
a signal that indicates some sort of exceptional condition or error
has occurred. To throw an exception is to signal
an exceptional condition. To catch an exception
is to handle itto take whatever actions are necessary to
recover from it.In Java, the throw statement is used to throw an
exception:
throw expression ;The expression must evaluate to an
exception object that describes the exception or error that has
occurred. We'll talk more about types of exceptions
shortly; for now, all you need to know is that an exception is
represented by an object. Here is some example code that throws an
exception:
public static double factorial(int x) {When the Java interpreter executes a
if (x < 0)
throw new IllegalArgumentException("x must be >= 0");
double fact;
for(fact=1.0; x > 1; fact *= x, x--)
/* empty */ ; // Note use of the empty statement
return fact;
}
throw statement, it immediately stops normal
program execution and starts looking for an exception handler that
can catch, or handle, the exception. Exception handlers are written
with the try/catch/finally statement, which is
described in the next section. The Java interpreter first looks at
the enclosing block of code to see if it has an associated exception
handler. If so, it exits that block of code and starts running the
exception-handling code associated with the block. After running the
exception handler, the interpreter continues execution at the
statement immediately following the handler code.If the enclosing block of code does
not have an appropriate exception handler, the interpreter checks the
next higher enclosing block of code in the method. This continues
until a handler is found. If the method does not contain an exception
handler that can handle the exception thrown by the
throw statement, the interpreter stops running the
current method and returns to the caller. Now the interpreter starts
looking for an exception handler in the blocks of code of the calling
method. In this way, exceptions propagate up through the lexical
structure of Java methods, up the call stack of the Java interpreter.
If the exception is never caught, it propagates all the way up to the
main( ) method of the program. If it is not
handled in that method, the Java interpreter prints an error message,
prints a stack trace to indicate where the exception occurred, and
then exits.
2.5.16.1 Exception types
An
exception in Java is an object. The type of this object is
java.lang.Throwable, or more commonly, some
subclass[4] of
Throwable that more specifically describes the
type of exception that occurred. Throwable has two
standard subclasses:
java.lang.Error and
java.lang.Exception. Exceptions that are
subclasses of Error
generally indicate unrecoverable problems: the virtual machine has
run out of memory, or a class file is corrupted and cannot be read,
for example. Exceptions of this sort can be caught and handled, but
it is rare to do so.
Exceptions that are subclasses of
Exception, on the other hand, indicate less severe
conditions. These exceptions can be reasonably caught and handled.
They include such exceptions as
java.io.EOFException,
which signals the end of a file, and
java.lang.ArrayIndexOutOfBoundsException,
which indicates that a program has tried to read past the end of an
array. In this book, I use the term
"exception" to refer to any
exception object, regardless of whether the type of that exception is
Exception or Error.
[4] We haven't talked about
subclasses yet; they are covered in detail in Chapter 3.
Since
an exception is an object, it can contain data, and its class can
define methods that operate on that data. The
Throwable class and all its subclasses include a
String field that stores a human-readable
error message
that describes the exceptional condition. It's set
when the exception object is created and can be read from the
exception with the getMessage( ) method. Most
exceptions contain only this single message, but a few add other
data. The java.io.InterruptedIOException, for
example, adds a field named bytesTransferred that
specifies how much input or output was completed before the
exceptional condition interrupted it.
2.5.17. The try/catch/finally Statement
The
try/catch/finally statement is
Java's exception-handling mechanism. The
TRy clause of this statement establishes a block
of code for exception handling. This try block is
followed by zero or more catch clauses, each of
which is a block of statements designed to handle a specific type of
exception. The catch clauses are followed by an
optional finally block that contains cleanup code
guaranteed to be executed regardless of what happens in the
try block. Both the catch and
finally clauses are optional, but every
try block must be accompanied by at least one or
the other. The TRy, catch, and
finally blocks all begin and end with curly
braces. These are a required part of the syntax and cannot be
omitted, even if the clause contains only a single statement.The following code illustrates the syntax and purpose of the
try/catch/finally statement:
try {
// Normally this code runs from the top of the block to the bottom
// without problems. But it can sometimes throw an exception,
// either directly with a throw statement or indirectly by calling
// a method that throws an exception.
}
catch (SomeException e1) {
// This block contains statements that handle an exception object
// of type SomeException or a subclass of that type. Statements in
// this block can refer to that exception object by the name e1.
}
catch (AnotherException e2) {
// This block contains statements that handle an exception object
// of type AnotherException or a subclass of that type. Statements
// in this block can refer to that exception object by the name e2.
}
finally {
// This block contains statements that are always executed
// after we leave the try clause, regardless of whether we leave it:
// 1) normally, after reaching the bottom of the block;
// 2) because of a break, continue, or return statement;
// 3) with an exception that is handled by a catch clause above; or
// 4) with an uncaught exception that has not been handled.
// If the try clause calls System.exit( ), however, the interpreter
// exits before the finally clause can be run.
}
2.5.17.1 try
The
TRy clause simply establishes a block of code that
either has its exceptions handled or needs special cleanup code to be
run when it terminates for any reason. The try
clause by itself doesn't do anything interesting; it
is the catch and finally
clauses that do the exception-handling and cleanup operations.
2.5.17.2 catch
A
try block can be followed by zero or more
catch clauses that specify code to handle various
types of exceptions. Each catch clause is declared
with a single argument that specifies the type of exceptions the
clause can handle and also provides a name the clause can use to
refer to the exception object it is currently handling. The type and
name of an exception handled by a catch clause are
exactly like the type and name of an argument passed to a method,
except that for a catch clause, the argument type
must be Throwable or one of its subclasses.When an exception is thrown, the Java interpreter looks for a
catch clause with an argument of the same type as
the exception object or a superclass of that type. The interpreter
invokes the first such catch clause it finds. The
code within a catch block should take whatever
action is necessary to cope with the exceptional condition. If the
exception is a java.io.FileNotFoundException
exception, for example, you might handle it by asking the user to
check his spelling and try again. It is not required to have a
catch clause for every possible exception; in some
cases the correct response is to allow the exception to propagate up
and be caught by the invoking method. In other cases, such as a
programming error signaled by
NullPointerException, the correct response is
probably not to catch the exception at all, but allow it to propagate
and have the Java interpreter exit with a stack trace and an error
message.
2.5.17.3 finally
The
finally clause is generally used to clean up after
the code in the TRy clause (e.g., close files and
shut down network connections). What is useful about the
finally clause is that it is guaranteed to be
executed if any portion of the try block is
executed, regardless of how the code in the try
block completes. In fact, the only way a try
clause can exit without allowing the finally
clause to be executed is by invoking the System.exit(
) method,
which causes the Java interpreter to stop running.In the normal case, control reaches the end of the
TRy block and then proceeds to the
finally block, which performs any necessary
cleanup. If control leaves the try block because
of a return, continue, or
break statement, the finally
block is executed before control transfers to its new destination.If an exception occurs in the try block and there
is an associated catch block to handle the
exception, control transfers first to the catch
block and then to the finally block. If there is
no local catch block to handle the exception,
control transfers first to the finally block, and
then propagates up to the nearest containing catch
clause that can handle the exception.If a finally block itself transfers control with a
return, continue,
break, or throw statement or by
calling a method that throws an exception, the pending control
transfer is abandoned, and this new transfer is processed. For
example, if a finally clause throws an exception,
that exception replaces any exception that was in the process of
being thrown. If a finally clause issues a
return statement, the method returns normally,
even if an exception has been thrown and has not yet been handled.try and finally can be used
together without exceptions or any catch clauses.
In this case, the finally block is simply cleanup
code that is guaranteed to be executed, regardless of any
break, continue, or
return statements within the
try clause.In previous discussions of the for and
continue statements, we've seen
that a for loop cannot be naively translated into
a while loop because the
continue
statement behaves slightly differently when used in a
for loop than it does when used in a
while loop. The finally clause
gives us a way to write a while loop that handles
the continue statement in the same way that a
for loop does. Consider the following generalized
for loop:
for( initialize ; test ; update )The following while loop behaves the same, even if
statement
the statement block contains a
continue statement:
initialize ;Note, however, that placing the update statement within a
while ( test ) {
TRy { statement }
finally { update ; }
}
finally block causes this while
loop to respond to break statements differently
than the for loop does.
2.5.18. The assert Statement
An
assert statement is used to document and verify
design assumptions in Java code. This statement was added in Java 1.4
and cannot be used with previous versions of the language. An
assertion consists of the
assert
keyword
followed by a boolean
expression
that the programmer believes should always evaluate to
TRue. By default, assertions are not enabled, and
the assert statement does not actually do
anything. It is possible to enable assertions as a
debugging and
testing tool, however; when this is done,
the assert statement evaluates the expression. If
it is indeed TRue, assert does
nothing. On the other hand, if the expression evaluates to
false, the assertion fails, and the
assert statement throws a
java.lang.AssertionError.The
assert statement may include an optional second
expression, separated from the first by a colon. When assertions are enabled
and the first expression evaluates to false, the
value of the second expression is taken as an error code or error
message and is passed to the AssertionError( )
constructor. The full syntax of the statement is:
assert assertion ;or:
assert assertion : errorcode ;It is important to remember that the
assertion must be a
boolean expression, which typically means that it contains a
comparison operator or invokes a boolean-valued method.
2.5.18.1 Compiling assertions
Because the
assert statement was added in Java 1.4, and
because
assert
was not a reserved word prior to Java 1.4, the introduction of this
new statement can cause code that uses
"assert" as an identifier to break.
For this reason, the javac compiler does not
recognize the assert statement by default. To
compile Java code that uses the assert statement,
you must use the command-line argument -source
1.4. For example:
javac -source 1.4 ClassWithAssertions.javaIn
Java 1.4, the javac compiler allows
"assert" to be used as an
identifier unless -source 1.4 is specified. If it
finds assert used as an identifier, it issues an
incompatibility warning to encourage you to modify your code.In Java 5.0, the javac compiler recognizes the
assert statement (as well as all the new Java 5.0
syntax) by default, and no special compiler arguments are required to
compile code that contains assertions. If you have legacy code that
still uses assert as an identifier, it will no
longer compile by default in Java 5.0. If you can't
fix it, you can compile it in Java 5.0 using the -source
1.3 option.
2.5.18.2 Enabling assertions
assert
statements encode assumptions that should always be true. For
efficiency, it does not make sense to test assertions each time code
is executed. Thus, by default, assertions are disabled, and
assert statements have no effect. The assertion
code remains compiled in the class files, however, so it can always
be enabled for testing, diagnostic, and debugging purposes. You can
enable assertions, either across the board or selectively, with
command-line arguments to the Java interpreter. To enable
assertions in all
classes
except for system classes, use the -ea argument.
To enable assertions in system classes, use -esa.
To enable assertions within a specific class, use
-ea followed by a colon and the classname:
java -ea:com.example.sorters.MergeSort com.example.sorters.TestTo enable assertions for all classes in a
package and in all of its subpackages, follow the
-ea argument with a colon, the package name, and
three dots:
java -ea:com.example.sorters... com.example.sorters.TestYou
can disable assertions in the same way, using the
-da argument. For example, to enable assertions
throughout a package and then disable them in a specific class or
subpackage, use:
java -ea:com.example.sorters... -da:com.example.sorters.QuickSortIf you prefer verbose command-line arguments, you can use
java -ea:com.example.sorters... -da:com.example.sorters.plugins.. .
-enableassertions and
-disableassertions instead of
-ea and -da and
-enablesystemassertions instead of
-esa.Java 1.4 added to
java.lang.ClassLoader methods for enabling and disabling
the assertions for classes loaded through that
ClassLoader. If you use a custom class loader in
your program and want to turn on assertions, you may be interested in
these methods. See ClassLoader in the reference
section.
2.5.18.3 Using assertions
Because
assertions are disabled by default and impose no performance penalty
on your code, you can use them liberally to document any assumptions
you make while programming. It may take some time to get used to
this, but as you do, you'll find more and more uses
for the assert statement. Suppose, for example,
that you're writing a method in such a way that you
know that the variable x is either 0 or 1. Without
assertions, you might code an if statement that
looks like this:
if (x = = 0) {The comment in this code is an informal assertion indicating that you
...
}
else { // x is 1
...
}
believe that within the body of the else clause,
x will always equal 1.Now suppose your code is later modified in
such a way that x can take on a value other than 0
and 1. The comment and the assumption that go along with it are no
longer valid, and this may cause a bug that is not immediately
apparent or is difficult to localize. The solution in this situation
is to convert your comment into an assert
statement. The code becomes:
if (x = = 0) {Now, if x somehow ends up holding an unexpected
...
}
else {
assert x = = 1 : x // x must be 0 or 1
...
}
value, an AssertionError is thrown, which makes
the bug immediately apparent and easy to pinpoint. Furthermore, the
second expression (following the colon) in the
assert statement includes the unexpected value of
x as the "error
message" of the AssertionError.
This message is not intended to mean anything to an end user, but to
provide enough information so that you know not just that an
assertion failed but also what caused it to fail.A similar technique is useful with
switch statements. If you write a
switch statement without a
default clause, you make an assumption about the
set of possible values for the switch expression.
If you believe that no other value is possible, you can add an
assert statement to document and validate that
fact. For example:
switch(x) {Note that the form assert false; always fails. It
case -1: return LESS;
case 0: return EQUALS;
case 1: return GREATER;
default: assert false:x; // Throw AssertionError if x is not -1, 0, or 1.
}
is a useful "dead-end" statement
when you believe that the statement can never be reached.Another
common use of the assert statement is to test
whether the arguments passed to a method all have values that are
legal for that method; this is also known as enforcing method
preconditions. For example:
private static Object[] subArray(Object[] a, int x, int y) {Note that this is a private method. The programmer has used an
assert x <= y : "subArray: x > y"; // Precondition: x must be <= y
// Now go on to create and return a subarray of a...
}
assert statement to document a precondition of the
subArray( ) method and state that she believes
that all methods that invoke this private method do in fact honor
that precondition. She can state this because she has control over
all the methods that invoke subArray( ). She can
verify her belief by enabling assertions while testing the code. But
once the code is tested, if assertions are left disabled, the method
does not suffer the overhead of testing its arguments each time it is
called. Note that the programmer did not use an
assert statement to test that argument
a is non-null and that the
x and y
arguments were legal indexes into that array. These implicit
preconditions are always tested by Java at runtime, and a failure
results in an unchecked NullPointerException or an
ArrayIndexOutOfBoundsException, so an assertion is
not required for them.It is important to understand that the assert
statement is not suitable for enforcing preconditions on public
methods. A public method can be called from anywhere, and the
programmer cannot assert in advance that it will be invoked
correctly. To be robust, a public API must explicitly test its
arguments and enforce its preconditions each time it is called,
whether or not assertions are enabled.A related use of the assert statement is to verify
a class invariant. Suppose you are creating a class that represents a
list of objects and allows objects to be inserted and deleted but
always maintains the list in sorted order. You believe that your
implementation is correct and that the insertion methods always leave
the list in sorted order, but you want to test this to be sure. You
might write a method that tests whether the list is actually sorted,
then use an assert statement to invoke the method
at the end of each method that modifies the list. For example:
public void insert(Object o) {When writing code that must be
... // Do the insertion here
assert isSorted( ); // Assert the class invariant here
}
threadsafe, you must obtain locks (using a
synchronized
method or statement) when required.
One common use of the assert statement in this
situation is to verify that the current thread holds the lock it
requires:
assert Thread.holdsLock(data);The Thread.holdsLock( )
method was added in Java 1.4 primarily for use with the
assert statement.
To use assertions effectively, you
must be aware of a couple of fine points. First, remember that your
programs will sometimes run with assertions enabled and sometimes
with assertions disabled. This means that you should be careful not
to write assertion expressions that contain side effects. If you do,
your code will run differently when assertions are enabled than it
will when they are disabled. There are a few exceptions to this rule,
of course. For example, if a method contains two
assert statements, the first can include a side
effect that affects only the second assertion. Another use of side
effects in assertions is the following idiom that determines whether
assertions are enabled (which is not something that your code should
ever really need to do):
boolean assertions = false; // Whether assertions are enabledNote that the expression in the assert statement
assert assertions = true; // This assert never fails but has a side effect
is an assignment, not a comparison. The value of an assignment
expression is always the value assigned, so this expression always
evaluates to TRue, and the assertion never fails.
Because this assignment expression is part of an
assert statement, the
assertions variable is set to
true only if assertions are enabled.In addition to avoiding side effects in
your assertions, another rule for working with the
assert statement is that you should never try to
catch an AssertionError (unless you catch it at
the top level simply so that you can display the error in a more
user-friendly fashion). If an AssertionError is
thrown, it indicates that one of the programmer's
assumptions has not held up. This means that the code is being used
outside of the parameters for which it was designed, and it cannot be
expected to work correctly. In short, there is no plausible way to
recover from an AssertionError, and you should not
attempt
to
catch it.