CHAPTER 14
The sequence of execution of a Java program is controlled by statements, which are executed for their effect and do not have values.
Some statements contain other statements as part of their structure; such other statements are substatements of the statement. We say that statement S immediately contains statement U if there is no statement T different from S and U
such that S contains T and T contains U. In the same manner, some statements contain expressions (§15) as part of their structure.
The first section of this chapter discusses the distinction between normal and abrupt completion of statements (§14.1). Most of the remaining sections explain the various kinds of statements, describing in detail both their normal behavior and any special treatment of abrupt completion.
Blocks are explained first (§14.2), because they can appear in certain places where other kinds of statements are not allowed, and because one other kind of statement, a local variable declaration statement (§14.3), must be immediately contained within a block.
Next a grammatical maneuver is explained that sidesteps the familiar "dangling else
" problem (§14.4).
Statements that will be familiar to C and C++ programmers are the empty (§14.5), labeled (§14.6), expression (§14.7), if
(§14.8), switch
(§14.9), while
(§14.10), do
(§14.11), for
(§14.12), break
(§14.13), continue
(§14.14), and return
(§14.15) statements.
Unlike C and C++, Java has no goto
statement. However, the break
and continue
statements are extended in Java to allow them to mention statement labels.
The Java statements that are not in the C language are the throw
(§14.16), synchronized
(§14.17), and try
(§14.18) statements.
The last section (§14.19) of this chapter addresses the requirement that every statement be reachable in a certain technical sense.
break
(§14.13), continue
(§14.14), and return
(§14.15) statements cause a transfer of control that may prevent normal completion of statements that contain them.
throw
(§14.16) statement also results in an exception. An exception causes a transfer of control that may prevent normal completion of statements.
break
with no label
break
with a given label
continue
with no label
continue
with a given label
return
with no value
return
with a given value
throw
with a given value, including exceptions thrown by the Java Virtual Machine
throw
with a given value (§14.16) or a run-time exception or error (§11, §15.5).If a statement evaluates an expression, abrupt completion of the expression always causes the immediate abrupt completion of the statement, with the same reason. All succeeding steps in the normal mode of execution are not performed.
Unless otherwise specified in this chapter, abrupt completion of a substatement causes the immediate abrupt completion of the statement itself, with the same reason, and all succeeding steps in the normal mode of execution of the statement are not performed.
Unless otherwise specified, a statement completes normally if all expressions it evaluates and all substatements it executes complete normally.
Block:A block is executed by executing each of the local variable declaration statements and other statements in order from first to last (left to right). If all of these block statements complete normally, then the block completes normally. If any of these block statements complete abruptly for any reason, then the block completes abruptly for the same reason.
{
BlockStatementsopt}
BlockStatements:
BlockStatement
BlockStatements
BlockStatement BlockStatement:
LocalVariableDeclarationStatement
Statement
LocalVariableDeclarationStatement:The following are repeated from §8.3 to make the presentation here clearer:
LocalVariableDeclaration;
LocalVariableDeclaration:
TypeVariableDeclarators
VariableDeclarators:Every local variable declaration statement is immediately contained by a block. Local variable declaration statements may be intermixed freely with other kinds of statements in the block.
VariableDeclarator
VariableDeclarators,
VariableDeclarator VariableDeclarator:
VariableDeclaratorId
VariableDeclaratorId=
VariableInitializer VariableDeclaratorId:
Identifier
VariableDeclaratorId[ ]
VariableInitializer:
Expression
ArrayInitializer
A local variable declaration can also appear in the header of a for
statement (§14.12). In this case it is executed in the same manner as if it were part of a local variable declaration statement.
The type of the variable is denoted by the Type that appears at the start of the local variable declaration, followed by any bracket pairs that follow the Identifier in the declarator. Thus, the local variable declaration:
int a, b[], c[][];is equivalent to the series of declarations:
int a; int[] b; int[][] c;Brackets are allowed in declarators as a nod to the tradition of C and C++. The general rule, however, also means that the local variable declaration:
float[][] f[][], g[][][], h[]; // Yechh!is equivalent to the series of declarations:
float[][][][] f; float[][][][][] g; float[][][] h;We do not recommend such "mixed notation" for array declarations.
A local variable cannot be referred to using a qualified name (§6.6), only a simple name.
class Test { static int x; public static void main(String[] args) { int x = x; } }causes a compile-time error because the initialization of
x
is within the scope of
the declaration of x
as a local variable, and the local x
does not yet have a value
and cannot be used.
The following program does compile:
class Test { static int x; public static void main(String[] args) { int x = (x=2)*2; System.out.println(x); } }because the local variable
x
is definitely assigned (§16) before it is used. It prints:
4Here is another example:
class Test { public static void main(String[] args) { System.out.print("2+1="); int two = 2, three = two + 1; System.out.println(three); } }which compiles correctly and produces the output:
2+1=3The initializer for
three
can correctly refer to the variable two
declared in an earlier
declarator, and the method invocation in the next line can correctly refer to the
variable three
declared earlier in the block.
The scope of a local variable declared in a for
statement is the rest of the for
statement, including its own initializer.
If a declaration of an identifier as a local variable appears within the scope of a parameter or local variable of the same name, a compile-time error occurs. Thus the following example does not compile:
class Test { public static void main(String[] args) { int i; for (int i = 0; i < 10; i++) System.out.println(i); } }This restriction helps to detect some otherwise very obscure bugs. (A similar restriction on hiding of members by local variables was judged impractical, because the addition of a member in a superclass could cause subclasses to have to rename local variables.)
On the other hand, local variables with the same name may be declared in two separate blocks or for
statements neither of which contains the other. Thus:
class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) System.out.print(i + " "); for (int i = 10; i > 0; i--) System.out.print(i + " "); System.out.println(); } }compiles without error and, when executed, produces the output:
0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1
this
can be used to access a
hidden field x
, using the form this.x
. Indeed, this idiom typically appears in constructors
(§8.6):
class Pair { Object first, second; public Pair(Object first, Object second) { this.first = first; this.second = second; } }In this example, the constructor takes parameters having the same names as the fields to be initialized. This is simpler than having to invent different names for the parameters and is not too confusing in this stylized context. In general, however, it is considered poor style to have local variables with the same names as fields.
Each initialization (except the first) is executed only if the evaluation of the preceding initialization expression completes normally. Execution of the local variable declaration completes normally only if evaluation of the last initialization expression completes normally; if the local variable declaration contains no initialization expressions, then executing it always completes normally.
As in C and C++, the Java if
statement suffers from the so-called "dangling else
problem," illustrated by this misleadingly formatted example:
if (door.isOpen()) if (resident.isVisible()) resident.greet("Hello!"); else door.bell.ring(); // A "dangling else"The problem is that both the outer
if
statement and the inner if
statement might
conceivably own the else
clause. In this example, one might surmise that the programmer
intended the else
clause to belong to the outer if
statement. The Java
language, like C and C++ and many languages before them, arbitrarily decree that
an else
clause belongs to the innermost if
to which it might possibly belong.
This rule is captured by the following grammar:
Statement:The following are repeated from §14.8 to make the presentation here clearer:
StatementWithoutTrailingSubstatement
LabeledStatement
IfThenStatement
IfThenElseStatement
WhileStatement
ForStatement StatementNoShortIf:
StatementWithoutTrailingSubstatement
LabeledStatementNoShortIf
IfThenElseStatementNoShortIf
WhileStatementNoShortIf
ForStatementNoShortIf StatementWithoutTrailingSubstatement:
Block
EmptyStatement
ExpressionStatement
SwitchStatement
DoStatement
BreakStatement
ContinueStatement
ReturnStatement
SynchronizedStatement
ThrowStatement
TryStatement
IfThenStatement:Statements are thus grammatically divided into two categories: those that might end in an
if (
Expression)
Statement IfThenElseStatement:
if (
Expression)
StatementNoShortIfelse
Statement IfThenElseStatementNoShortIf:
if (
Expression)
StatementNoShortIfelse
StatementNoShortIf
if
statement that has no else
clause (a "short if
statement") and those that definitely do not. Only statements that definitely do not end in a short if
statement may appear as an immediate substatement before the keyword else
in an if
statement that does have an else
clause. This simple rule prevents the "dangling else
" problem. The execution behavior of a statement with the "no short if
" restriction is identical to the execution behavior of the same kind of statement without the "no short if
" restriction; the distinction is drawn purely to resolve the syntactic difficulty.EmptyStatement:Execution of an empty statement always completes normally.
;
LabeledStatement:The Identifier is declared to be the label of the immediately contained Statement.
Identifier:
Statement LabeledStatementNoShortIf:
Identifier:
StatementNoShortIf
Unlike C and C++, the Java language has no goto
statement; identifier statement labels are used with break
(§14.13) or continue
(§14.14) statements appearing anywhere within the labeled statement.
A statement labeled by an identifier must not appear anywhere within another statement labeled by the same identifier, or a compile-time error will occur. Two statements can be labeled by the same identifier only if neither statement contains the other.
There is no restriction against using the same identifier as a label and as the name of a package, class, interface, method, field, parameter, or local variable. Use of an identifier to label a statement does not hide a package, class, interface, method, field, parameter, or local variable with the same name. Use of an identifier as a local variable or as the parameter of an exception handler (§14.18) does not hide a statement label with the same name.
A labeled statement is executed by executing the immediately contained Statement. If the statement is labeled by an Identifier and the contained Statement completes abruptly because of a break
with the same Identifier, then the labeled statement completes normally. In all other cases of abrupt completion of the Statement, the labeled statement completes abruptly for the same reason.
ExpressionStatement:An expression statement is executed by evaluating the expression; if the expression has a value, the value is discarded. Execution of the expression statement completes normally if and only if evaluation of the expression completes normally.
StatementExpression;
StatementExpression:
Assignment
PreIncrementExpression
PreDecrementExpression
PostIncrementExpression
PostDecrementExpression
MethodInvocation
ClassInstanceCreationExpression
Unlike C and C++, the Java language allows only certain forms of expressions to be used as expression statements. Note that Java does not allow a "cast to void
"-void
is not a type in Java-so the traditional C trick of writing an expression statement such as:
(void) ... ; // This idiom belongs to C, not to Java!does not work in Java. On the other hand, Java allows all the most useful kinds of expressions in expressions statements, and Java does not require a method invocation used as an expression statement to invoke a
void
method, so such a trick is
almost never needed. If a trick is needed, either an assignment statement (§15.25)
or a local variable declaration statement (§14.3) can be used instead.
if
Statementif
statement allows conditional execution of a statement or a conditional
choice of two statements, executing one or the other but not both.
IfThenStatement:The Expression must have type
if (
Expression)
Statement IfThenElseStatement:
if (
Expression)
StatementNoShortIfelse
Statement IfThenElseStatementNoShortIf:
if (
Expression)
StatementNoShortIfelse
StatementNoShortIf
boolean
, or a compile-time error occurs.
if-then
Statementif
-then
statement is executed by first evaluating the Expression. If evaluation
of the Expression completes abruptly for some reason, the if
-then
statement
completes abruptly for the same reason. Otherwise, execution continues by making
a choice based on the resulting value:
true
, then the contained Statement is executed; the if
-then
statement completes normally only if execution of the Statement completes normally.
false
, no further action is taken and the if
-then
statement completes normally.
if-then-else
Statementif
-then
-else
statement is executed by first evaluating the Expression. If
evaluation of the Expression completes abruptly for some reason, then the if
-
then
-else
statement completes abruptly for the same reason. Otherwise, execution
continues by making a choice based on the resulting value:
true
, then the first contained Statement (the one before the else
keyword) is executed; the if
-then
-else
statement completes normally only if execution of that statement completes normally.
false
, then the second contained Statement (the one after the else
keyword) is executed; the if
-then
-else
statement completes normally only if execution of that statement completes normally.
switch
Statementswitch
statement transfers control to one of several statements depending on
the value of an expression.
SwitchStatement:The type of the Expression must be
switch (
Expression)
SwitchBlock SwitchBlock:
{
SwitchBlockStatementGroupsoptSwitchLabelsopt
}
SwitchBlockStatementGroups:
SwitchBlockStatementGroup
SwitchBlockStatementGroups
SwitchBlockStatementGroup SwitchBlockStatementGroup:
SwitchLabelsBlockStatements SwitchLabels:
SwitchLabel
SwitchLabelsSwitchLabel SwitchLabel:
case
ConstantExpression:
default :
char
, byte
, short
, or int
, or a compile-time error occurs.
The body of a switch
statement must be a block. Any statement immediately contained by the block may be labeled with one or more case
or default
labels. These labels are said to be associated with the switch
statement, as are the values of the constant expressions (§15.27) in the case
labels.
All of the following must be true, or a compile-time error will result:
case
constant expression associated with a switch
statement must be assignable (§5.2) to the type of the switch
Expression.
case
constant expressions associated with a switch
statement may have the same value.
default
label may be associated with the same switch
statement.
switch
statement can be a statement and statements with case
labels do not have to be immediately contained by that statement. Consider the simple loop:
for (i = 0; i < n; ++i) foo();where
n
is known to be positive. A trick known as Duff's device can be used in C
or C++ to unroll the loop, but this is not valid Java code:
int q = (n+7)/8; switch (n%8) { case 0: do { foo(); // Great C hack, Tom, case 7: foo(); // but it's not valid in Java. case 6: foo(); case 5: foo(); case 4: foo(); case 3: foo(); case 2: foo(); case 1: foo(); } while (--q >= 0); }Fortunately, this trick does not seem to be widely known or used. Moreover, it is less needed nowadays; this sort of code transformation is properly in the province of state-of-the-art optimizing compilers.
When the switch
statement is executed, first the Expression is evaluated. If evaluation of the Expression completes abruptly for some reason, the switch
statement completes abruptly for the same reason. Otherwise, execution continues by comparing the value of the Expression with each case
constant. Then there is a choice:
case
constants is equal to the value of the expression, then we say that the case
matches, and all statements after the matching case
label in the switch block, if any, are executed in sequence. If all these statements complete normally, or if there are no statements after the matching case
label, then the entire switch
statement completes normally.
case
matches but there is a default
label, then all statements after the matching default
label in the switch block, if any, are executed in sequence. If all these statements complete normally, or if there are no statements after the default
label, then the entire switch
statement completes normally.
case
matches and there is no default
label, then no further action is taken and the switch
statement completes normally.
switch
statement completes abruptly, it is handled as follows:
break
with no label, no further action is taken and the switch
statement completes normally.
switch
statement completes abruptly for the same reason. The case of abrupt completion because of a break
with a label is handled by the general rule for labeled statements (§14.6).
class Toomany { static void howMany(int k) { switch (k) { case 1: System.out.print("one "); case 2: System.out.print("too "); case 3: System.out.println("many"); } }contains a switch block in which the code for each case falls through into the code for the next case. As a result, the program prints:
public static void main(String[] args) { howMany(3); howMany(2); howMany(1); }
}
many too many one too manyIf code is not to fall through case to case in this manner, then
break
statements
should be used, as in this example:
class Twomany { static void howMany(int k) { switch (k) { case 1: System.out.println("one"); break; // exit the switch case 2: System.out.println("two"); break; // exit the switch case 3: System.out.println("many"); break; // not needed, but good style } }This program prints:
public static void main(String[] args) { howMany(1); howMany(2); howMany(3); }
}
one two many
while
Statementwhile
statement executes an Expression and a Statement repeatedly until the
value of the Expression is false
.
WhileStatement:The Expression must have type
while (
Expression)
Statement WhileStatementNoShortIf:
while (
Expression)
StatementNoShortIf
boolean
, or a compile-time error occurs.
A while
statement is executed by first evaluating the Expression. If evaluation of the Expression completes abruptly for some reason, the while
statement completes abruptly for the same reason. Otherwise, execution continues by making a choice based on the resulting value:
true
, then the contained Statement is executed. Then there is a choice:
while
statement is executed again, beginning by re-evaluating the Expression.
false
, no further action is taken and the while
statement completes normally.
false
the first time it is evaluated, then the
Statement is not executed.
break
with no label, no further action is taken and the while
statement completes normally.
continue
with no label, then the entire while
statement is executed again.
continue
with label L, then there is a choice:
while
statement has label L, then the entire while
statement is executed again.
while
statement does not have label L, the while
statement completes abruptly because of a continue
with label L.
while
statement completes abruptly for the same reason. Note that the case of abrupt completion because of a break
with a label is handled by the general rule for labeled statements (§14.6).
do
Statementdo
statement executes a Statement and an Expression repeatedly until the
value of the Expression is false
.
DoStatement:The Expression must have type
do
Statementwhile (
Expression) ;
boolean
, or a compile-time error occurs.
A do
statement is executed by first executing the Statement. Then there is a choice:
do
statement completes abruptly for the same reason. Otherwise, there is a choice based on the resulting value:
true
, then the entire do
statement is executed again.
false
, no further action is taken and the do
statement completes normally.
do
statement always executes the contained Statement at least once.
break
with no label, then no further action is taken and the do
statement completes normally.
continue
with no label, then the Expression is evaluated. Then there is a choice based on the resulting value:
true
, then the entire do
statement is executed again.
false
, no further action is taken and the do
statement completes normally.
continue
with label L, then there is a choice:
do
statement has label L, then the Expression is evaluated. Then there is a choice:
true
, then the entire do
statement is executed again.
false
, no further action is taken and the do
statement completes normally.
do
statement does not have label L, the do
statement completes abruptly because of a continue
with label L.
do
statement completes abruptly for the same reason. The case of abrupt completion because of a break
with a label is handled by the general rule (§14.6).
do
statementtoHexString
method
(§20.7.14) of class Integer
:
public static String toHexString(int i) { StringBuffer buf = new StringBuffer(8); do { buf.append(Character.forDigit(i & 0xF, 16)); i >>>= 4; } while (i != 0); return buf.reverse().toString(); }Because at least one digit must be generated, the
do
statement is an appropriate
control structure.
for
Statementfor
statement executes some initialization code, then executes an Expression,
a Statement, and some update code repeatedly until the value of the Expression is
false
.
ForStatement:The Expression must have type
for (
ForInitopt;
Expressionopt;
ForUpdateopt)
Statement ForStatementNoShortIf:
for (
ForInitopt;
Expressionopt;
ForUpdateopt)
StatementNoShortIf ForInit:
StatementExpressionList
LocalVariableDeclaration ForUpdate:
StatementExpressionList StatementExpressionList:
StatementExpression
StatementExpressionList,
StatementExpression
boolean
, or a compile-time error occurs.
for
statementfor
statement is executed by first executing the ForInit code:
for
statement completes abruptly for the same reason; any ForInit statement expressions to the right of the one that completed abruptly are not evaluated.
for
statement. If execution of the local variable declaration completes abruptly for any reason, the for
statement completes abruptly for the same reason.
for
statementfor
iteration step is performed, as follows:
for
statement completes abruptly for the same reason. Otherwise, there is then a choice based on the presence or absence of the Expression and the resulting value if the Expression is present:
true
, then the contained Statement is executed. Then there is a choice:
for
statement completes abruptly for the same reason; any ForUpdate statement expressions to the right of the one that completed abruptly are not evaluated. If the ForUpdate part is not present, no action is taken.
for
iteration step is performed.
false
, no further action is taken and the for
statement completes normally.
false
the first time it is evaluated, then the Statement is not executed.
If the Expression is not present, then the only way a for
statement can complete normally is by use of a break
statement.
for
statementbreak
with no label, no further action is taken and the for
statement completes normally.
continue
with no label, then the following two steps are performed in sequence:
for
iteration step is performed.
continue
with label L, then there is a choice:
for
statement has label L, then the following two steps are performed in sequence:
for
iteration step is performed.
for
statement does not have label L, the for
statement completes abruptly because of a continue
with label L.
for
statement completes abruptly for the same reason. Note that the case of abrupt completion because of a break
with a label is handled by the general rule for labeled statements (§14.6).
break
StatementBreakStatement:A
break
Identifieropt;
break
statement with no label attempts to transfer control to the innermost enclosing switch
, while
, do
, or for
statement; this statement, which is called the break target, then immediately completes normally. To be precise, a break
statement with no label always completes abruptly, the reason being a break
with no label. If no switch
, while
, do
, or for
statement encloses the break
statement, a compile-time error occurs.
A break
statement with label Identifier attempts to transfer control to the enclosing labeled statement (§14.6) that has the same Identifier as its label; this statement, which is called the break target, then immediately completes normally. In this case, the break
target need not be a while
, do
, for
, or switch
statement. To be precise, a break
statement with label Identifier always completes abruptly, the reason being a break
with label Identifier. If no labeled statement with Identifier as its label encloses the break
statement, a compile-time error occurs.
It can be seen, then, that a break
statement always completes abruptly.
The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try
statements (§14.18) within the break target whose try
blocks contain the break
statement, then any finally
clauses of those try
statements are executed, in order, innermost to outermost, before control is transferred to the break target. Abrupt completion of a finally
clause can disrupt the transfer of control initiated by a break
statement.
In the following example, a mathematical graph is represented by an array of arrays. A graph consists of a set of nodes and a set of edges; each edge is an arrow that points from some node to some other node, or from a node to itself. In this example it is assumed that there are no redundant edges; that is, for any two nodes P and Q, where Q may be the same as P, there is at most one edge from P to Q. Nodes are represented by integers, and there is an edge from node i to node edges[
i][
j]
for every i and j for which the array reference edges[
i][
j]
does not throw an IndexOutOfBoundsException
.
The task of the method loseEdges
, given integers i and j, is to construct a new graph by copying a given graph but omitting the edge from node i to node j, if any, and the edge from node j to node i, if any:
class Graph { int edges[][]; public Graph(int[][] edges) { this.edges = edges; } public Graph loseEdges(int i, int j) { int n = edges.length; int[][] newedges = new int[n][]; for (int k = 0; k < n; ++k) { edgelist: { int z; search: { if (k == i) { for (z = 0; z < edges[k].length; ++z) if (edges[k][z] == j) break search; } else if (k == j) { for (z = 0; z < edges[k].length; ++z) if (edges[k][z] == i) break search; } // No edge to be deleted; share this list. newedges[k] = edges[k]; break edgelist; }//search // Copy the list, omitting the edge at position z. int m = edges[k].length - 1; int ne[] = new int[m]; System.arraycopy(edges[k], 0, ne, 0, z); System.arraycopy(edges[k], z+1, ne, z, m-z); newedges[k] = ne; }//edgelist } return new Graph(newedges); } }Note the use of two statement labels,
edgelist
and search
, and the use of break
statements. This allows the code that copies a list, omitting one edge, to be shared
between two separate tests, the test for an edge from node i to node j, and the test
for an edge from node j to node i.
continue
Statementcontinue
statement may occur only in a while
, do
, or for
statement; statements
of these three kinds are called iteration statements. Control passes to the
loop-continuation point of an iteration statement.
ContinueStatement:A
continue
Identifieropt;
continue
statement with no label attempts to transfer control to the innermost enclosing while
, do
, or for
statement; this statement, which is called the continue target, then immediately ends the current iteration and begins a new one. To be precise, such a continue
statement always completes abruptly, the reason being a continue
with no label. If no while
, do
, or for
statement encloses the continue
statement, a compile-time error occurs.
A continue
statement with label Identifier attempts to transfer control to the enclosing labeled statement (§14.6) that has the same Identifier as its label; that statement, which is called the continue target, then immediately ends the current iteration and begins a new one. The continue target must be a while
, do
, or for
statement or a compile-time error occurs. More precisely, a continue
statement with label Identifier always completes abruptly, the reason being a continue
with label Identifier. If no labeled statement with Identifier as its label contains the continue
statement, a compile-time error occurs.
It can be seen, then, that a continue
statement always completes abruptly.
See the descriptions of the while
statement (§14.10), do
statement (§14.11), and for
statement (§14.12) for a discussion of the handling of abrupt termination because of continue
.
The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try
statements (§14.18) within the continue target whose try
blocks contain the continue
statement, then any finally
clauses of those try
statements are executed, in order, innermost to outermost, before control is transferred to the continue target. Abrupt completion of a finally
clause can disrupt the transfer of control initiated by a continue
statement.
In the Graph
example in the preceding section, one of the break
statements is used to finish execution of the entire body of the outermost for
loop. This break
can be replaced by a continue
if the for
loop itself is labeled:
class Graph { . . . public Graph loseEdges(int i, int j) { int n = edges.length; int[][] newedges = new int[n][]; edgelists: for (int k = 0; k < n; ++k) { int z; search: { if (k == i) { . . . } else if (k == j) { . . . } newedges[k] = edges[k]; continue edgelists; }//search . . . }//edgelists return new Graph(newedges); } }Which to use, if either, is largely a matter of programming style.
return
Statementreturn
statement returns control to the invoker of a method (§8.4, §15.11) or
constructor (§8.6, §15.8).
ReturnStatement:A
return
Expressionopt;
return
statement with no Expression must be contained in the body of a method that is declared, using the keyword void
, not to return any value (§8.4), or in the body of a constructor (§8.6). A compile-time error occurs if a return
statement appears within a static initializer (§8.5). A return
statement with no Expression attempts to transfer control to the invoker of the method or constructor that contains it. To be precise, a return
statement with no Expression always completes abruptly, the reason being a return
with no value.
A return
statement with an Expression must be contained in a method declaration that is declared to return a value (§8.4) or a compile-time error occurs. The Expression must denote a variable or value of some type T, or a compile-time error occurs. The type T must be assignable (§5.2) to the declared result type of the method, or a compile-time error occurs.
A return
statement with an Expression attempts to transfer control to the invoker of the method that contains it; the value of the Expression becomes the value of the method invocation. More precisely, execution of such a return
statement first evaluates the Expression. If the evaluation of the Expression completes abruptly for some reason, then the return
statement completes abruptly for that reason. If evaluation of the Expression completes normally, producing a value V, then the return
statement completes abruptly, the reason being a return
with value V.
It can be seen, then, that a return
statement always completes abruptly.
The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try
statements (§14.18) within the method or constructor whose try
blocks contain the return
statement, then any finally
clauses of those try
statements will be executed, in order, innermost to outermost, before control is transferred to the invoker of the method or constructor. Abrupt completion of a finally
clause can disrupt the transfer of control initiated by a return
statement.
throw
Statementthrow
statement causes an exception (§11) to be thrown. The result is an immediate
transfer of control (§11.3) that may exit multiple statements and multiple
constructor, static and field initializer evaluations, and method invocations until a
try
statement (§14.18) is found that catches the thrown value. If no such try
statement is found, then execution of the thread (§17, §20.20) that executed the
throw
is terminated (§11.3) after invocation of the UncaughtException
method
(§20.21.31) for the thread group to which the thread belongs.
ThrowStatement:The Expression in a throw statement must denote a variable or value of a reference type which is assignable (§5.2) to the type
throw
Expression;
Throwable
, or a compile-time error occurs. Moreover, at least one of the following three conditions must be true, or a compile-time error occurs:
RuntimeException
or a subclass of RuntimeException
.
Error
or a subclass of Error
.
throw
statement is contained in the try
block of a try
statement (§14.18) and the type of the Expression is assignable (§5.2) to the type of the parameter of at least one catch
clause of the try
statement. (In this case we say the thrown value is caught by the try
statement.)
throw
statement is contained in a method or constructor declaration and the type of the Expression is assignable (§5.2) to at least one type listed in the throws
clause (§8.4.4, §8.6.4) of the declaration.
throw
statement first evaluates the Expression. If the evaluation of the Expression completes abruptly for some reason, then the throw
completes abruptly for that reason. If evaluation of the Expression completes normally, producing a value V, then the throw
statement completes abruptly, the reason being a throw
with value V.
It can be seen, then, that a throw
statement always completes abruptly.
If there are any enclosing try
statements (§14.18) whose try
blocks contain the throw
statement, then any finally
clauses of those try
statements are executed as control is transferred outward, until the thrown value is caught. Note that abrupt completion of a finally
clause can disrupt the transfer of control initiated by a throw
statement.
If a throw
statement is contained in a method declaration, but its value is not caught by some try
statement that contains it, then the invocation of the method completes abruptly because of the throw
.
If a throw
statement is contained in a constructor declaration, but its value is not caught by some try
statement that contains it, then the class instance creation expression (or the method invocation of method newInstance
of class Class
) that invoked the constructor will complete abruptly because of the throw
.
If a throw
statement is contained in a static initializer (§8.5), then a compile-time check ensures that either its value is always an unchecked exception or its value is always caught by some try
statement that contains it. If, despite this check, the value is not caught by some try
statement that contains the throw
statement, then the value is rethrown if it is an instance of class Error
or one of its subclasses; otherwise, it is wrapped in an ExceptionInInitializerError
object, which is then thrown (§12.4.2).
By convention, user-declared throwable types should usually be declared to be subclasses of class Exception
, which is a subclass of class Throwable
(§11.5, §20.22).
synchronized
Statementsynchronized
statement acquires a mutual-exclusion lock (§17.13) on behalf
of the executing thread, executes a block, then releases the lock. While the executing
thread owns the lock, no other thread may acquire the lock.
SynchronizedStatement:The type of Expression must be a reference type, or a compile-time error occurs.
synchronized (
Expression)
Block
A synchronized
statement is executed by first evaluating the Expression.
If evaluation of the Expression completes abruptly for some reason, then the synchronized
statement completes abruptly for the same reason.
Otherwise, if the value of the Expression is null
, a NullPointerException
is thrown.
Otherwise, let the non-null
value of the Expression be V. The executing thread locks the lock associated with V. Then the Block is executed. If execution of the Block completes normally, then the lock is unlocked and the synchronized
statement completes normally. If execution of the Block completes abruptly for any reason, then the lock is unlocked and the synchronized
statement then completes abruptly for the same reason.
Acquiring the lock associated with an object does not of itself prevent other threads from accessing fields of the object or invoking unsynchronized methods on the object. Other threads can also use synchronized
methods or the synchronized
statement in a conventional manner to achieve mutual exclusion.
The locks acquired by synchronized
statements are the same as the locks that are acquired implicitly by synchronized
methods; see §8.4.3.5. A single thread may hold a lock more than once. The example:
class Test { public static void main(String[] args) { Test t = new Test(); synchronized(t) { synchronized(t) { System.out.println("made it!"); } } } }prints:
made it!This example would deadlock if a single thread were not permitted to lock a lock more than once.
try
statementtry
statement executes a block. If a value is thrown and the try
statement has
one or more catch
clauses that can catch it, then control will be transferred to the
first such catch
clause. If the try
statement has a finally
clause, then another
block of code is executed, no matter whether the try
block completes normally or
abruptly, and no matter whether a catch
clause is first given control.
TryStatement:The following is repeated from §8.4.1 to make the presentation here clearer:
try
BlockCatches
try
BlockCatchesopt
Finally Catches:
CatchClause
CatchesCatchClause CatchClause:
catch (
FormalParameter)
Block Finally:
finally
Block
FormalParameter:The following is repeated from §8.3 to make the presentation here clearer:
TypeVariableDeclaratorId
VariableDeclaratorId:The Block immediately after the keyword
Identifier
VariableDeclaratorId[ ]
try
is called the try
block of the try
statement. The Block immediately after the keyword finally
is called the finally
block of the try
statement.
A try
statement may have catch
clauses (also called exception handlers). A catch
clause must have exactly one parameter (which is called an exception parameter); the declared type of the exception parameter must be the class Throwable
or a subclass of Throwable
, or a compile-time error occurs. The scope of the parameter variable is the Block of the catch
clause. An exception parameter must not have the same name as a local variable or parameter in whose scope it is declared, or a compile-time error occurs.
The scope of the name of an exception parameter is the Block of the catch
clause. The name of the parameter may not be redeclared as a local variable or exception parameter within the Block of the catch
clause; that is, hiding the name of an exception parameter is not permitted.
Exception parameters cannot be referred to using qualified names (§6.6), only by simple names.
Exception handlers are considered in left-to-right order: the earliest possible catch
clause accepts the exception, receiving as its actual argument the thrown exception object.
A finally
clause ensures that the finally
block is executed after the try
block and any catch
block that might be executed, no matter how control leaves the try
block or catch
block.
Handling of the finally
block is rather complex, so the two cases of a try
statement with and without a finally
block are described separately.
try-catch
try
statement without a finally
block is executed by first executing the try
block. Then there is a choice:
try
block completes normally, then no further action is taken and the try
statement completes normally.
try
block completes abruptly because of a throw
of a value V, then there is a choice:
catch
clause of the try
statement, then the first (leftmost) such catch
clause is selected. The value V is assigned to the parameter of the selected catch
clause, and the Block of that catch
clause is executed. If that block completes normally, then the try
statement completes normally; if that block completes abruptly for any reason, then the try
statement completes abruptly for the same reason.
catch
clause of the try
statement, then the try
statement completes abruptly because of a throw
of the value V.
try
block completes abruptly for any other reason, then the try
statement completes abruptly for the same reason.
class BlewIt extends Exception { BlewIt() { } BlewIt(String s) { super(s); } } class Test { static void blowUp() throws BlewIt { throw new BlewIt(); } public static void main(String[] args) {
try { blowUp(); } catch (RuntimeException r) { System.out.println("RuntimeException:" + r); } catch (BlewIt b) { System.out.println("BlewIt"); } }the exception
}
BlewIt
is thrown by the method blowUp
. The try
-catch
statement
in the body of main
has two catch
clauses. The run-time type of the exception is
BlewIt
which is not assignable to a variable of type RuntimeException
, but is
assignable to a variable of type BlewIt
, so the output of the example is:
BlewIt
try-catch-finally
try
statement with a finally
block is executed by first executing the try
block. Then there is a choice:
try
block completes normally, then the finally
block is executed, and then there is a choice:
finally
block completes normally, then the try
statement completes normally.
finally
block completes abruptly for reason S, then the try
statement completes abruptly for reason S.
try
block completes abruptly because of a throw
of a value V, then there is a choice:
catch
clause of the try
statement, then the first (leftmost) such catch
clause is selected. The value V is assigned to the parameter of the selected catch
clause, and the Block of that catch
clause is executed. Then there is a choice:
catch
block completes normally, then the finally
block is executed. Then there is a choice:
finally
block completes normally, then the try
statement completes normally.
finally
block completes abruptly for any reason, then the try
statement completes abruptly for the same reason.
catch
block completes abruptly for reason R, then the finally
block is executed. Then there is a choice:
catch
clause of the try
statement, then the finally
block is executed. Then there is a choice:
try
block completes abruptly for any other reason R, then the finally
block is executed. Then there is a choice:
class BlewIt extends Exception { BlewIt() { } BlewIt(String s) { super(s); } }
class Test { static void blowUp() throws BlewIt {produces the output:
throw new NullPointerException();
} public static void main(String[] args) { try { blowUp(); } catch (BlewIt b) { System.out.println("BlewIt"); } finally { System.out.println("Uncaught Exception"); } }
}
Uncaught Exception java.lang.NullPointerException at Test.blowUp(Test.java:7) at Test.main(Test.java:11)The
NullPointerException
(which is a kind of RuntimeException
) that is
thrown by method blowUp
is not caught by the try
statement in main
, because a
NullPointerException
is not assignable to a variable of type BlewIt
. This
causes the finally
clause to execute, after which the thread executing main
,
which is the only thread of the test program, terminates because of an uncaught
exception (§20.21.31), which results in printing the exception name and a simple
backtrace.
This section is devoted to a precise explanation of the word "reachable." The idea is that there must be some possible execution path from the beginning of the constructor, method, or static initializer that contains the statement to the statement itself. The analysis takes into account the structure of statements. Except for the special treatment of while
, do
, and for
statements whose condition expression has the constant value true
, the values of expressions are not taken into account in the flow analysis. For example, a Java compiler will accept the code:
{ int n = 5; while (n > 7) n = 2; }even though the value of
n
is known at compile time and in principle it can be
known at compile time that the assignment to k
can never be executed. A Java
compiler must operate according to the rules laid out in this section.
The rules in this section define two technical terms:
The definitions here allow a statement to complete normally only if it is reachable.
To shorten the description of the rules, the customary abbreviation "iff" is used to mean "if and only if."
if
statement, whether or not it has an else
part, is handled in an unusual manner. For this reason, it is discussed separately at the end of this section.
switch
statement can complete normally iff at least one of the following is true:
break
statement that exits the switch
statement.
switch
statement is reachable.
switch
statement is reachable and at least one of the following is true:
case
or default
label.
switch
block and that preceding statement can complete normally.
while
statement can complete normally iff at least one of the following is true:
while
statement is reachable and the condition expression is not a constant expression whose value is false
.
do
statement can complete normally iff at least one of the following is true:
do
statement is reachable.
for
statement can complete normally iff at least one of the following is true:
for
statement is reachable and the condition expression is not a constant expression whose value is false
.
break
, continue
, return
, or throw
statement cannot complete normally.
synchronized
statement can complete normally iff the contained statement can complete normally. The contained statement is reachable iff the synchronized
statement is reachable.
try
statement can complete normally iff both of the following are true:
try
block can complete normally or any catch
block can complete normally
.
try
statement has a finally
block, then the finally
block can complete normally.
try
block is reachable iff the try
statement is reachable.
catch
block C is reachable iff both of the following are true:
throw
statement in the try
block is reachable and can throw an exception whose type is assignable to the parameter of the catch
clause C. (An expression is considered reachable iff the innermost statement containing it is reachable.)
catch
block A in the try
statement such that the type of C's parameter is the same as or a subclass of the type of A's parameter.
finally
block is present, it is reachable iff the try
statement is reachable.
if
statement to be handled in the following manner, but these are not the rules that Java actually uses:
if-then
statement can complete normally iff at least one of the following is true
:
then
-statement is reachable iff the if
-then
statement is reachable and the condition expression is not a constant expression whose value is false
.
if
-then
-else
statement can complete normally iff the then
-statement can complete normally or the else
-statement can complete normally. The then
-statement is reachable iff the if
-then
-else
statement is reachable and the condition expression is not a constant expression whose value is false
. The else
statement is reachable iff the if
-then
-else
statement is reachable and the condition expression is not a constant expression whose value is true
.
if
-then
statement can complete normally iff it is reachable. The then
-statement is reachable iff the if
-then
statement is reachable.
if
-then
-else
statement can complete normally iff the then
-statement can complete normally or the else
-statement can complete normally. The then
-statement is reachable iff the if
-then
-else
statement is reachable. The else
-statement is reachable iff the if
-then
-else
statement is reachable.
while (false) { x=3; }because the statement
x=3;
is not reachable; but the superficially similar case:
if (false) { x=3; }does not result in a compile-time error. An optimizing compiler may realize that the statement
x=3;
will never be executed and may choose to omit the code for
that statement from the generated class
file, but the statement x=3;
is not
regarded as "unreachable" in the technical sense specified here.
The rationale for this differing treatment is to allow programmers to define "flag variables" such as:
static final boolean DEBUG = false;and then write code such as:
if (DEBUG) { x=3; }The idea is that it should be possible to change the value of
DEBUG
from false
to
true
or from true
to false
and then compile the code correctly with no other
changes to the program text.
This ability to "conditionally compile" has a significant impact on, and relationship to, binary compatibility (§13). If a set of classes that use such a "flag" variable are compiled and conditional code is omitted, it does not suffice later to distribute just a new version of the class or interface that contains the definition of the flag. A change to the value of a flag is, therefore, not binary compatible with preexisting binaries (§13.4.8). (There are other reasons for such incompatibility as well, such as the use of constants in case
labels in switch
statements; see §13.4.8.)
Contents | Prev | Next | Index
Java Language Specification (HTML generated by Suzette Pelouch on February 24, 1998)
Copyright © 1996 Sun Microsystems, Inc.
All rights reserved
Please send any comments or corrections to [email protected]