Table of Contents
if
Statementassert
Statementswitch
Statementwhile
Statementdo
Statementfor
Statementbreak
Statementcontinue
Statementreturn
Statementthrow
Statementsynchronized
Statementtry
statementyield
StatementThe sequence of execution of a 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 (Expressions)) 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), both because they can appear in certain places where statements are not allowed and because one kind of statement, a local variable declaration statement (§14.4.2), must be immediately contained by a block. Local class and interface declarations (§14.3) are not statements, but must also be immediately contained by a block.
Next, a grammatical maneuver that sidesteps the familiar "dangling else" problem (§14.5) is explained.
Every statement must be reachable in a certain technical sense (§14.22).
Sections 14.23-14.29 are unused to allow for the introduction of new kinds of statements in future.
The last section of this chapter (§14.30) describes patterns, which are used within statements and expressions to conditionally declare and initialize local variables. A pattern gives a concise description of how one value, such as an object, could be composed from one or more other values, denoted by variable declarations. Pattern matching attempts to extract one or more values from a given value, as if to decompose it, and uses the extracted values to initialize the variables declared by the pattern.
Every statement has a normal mode of execution in which certain computational steps are carried out. The following sections describe the normal mode of execution for each kind of statement.
If all the steps are carried out as described, with no indication of abrupt completion, the statement is said to complete normally. However, certain events may prevent a statement from completing normally:
The break
, yield
, continue
, and return
statements
(§14.15, §14.21,
§14.16, §14.17)
cause a transfer of control that may prevent normal completion
of expressions, statements, and blocks that contain them.
Evaluation of certain expressions may throw exceptions from the
Java Virtual Machine (§15.6). An explicit throw
(§14.18) statement also results in an
exception. An exception causes a transfer of control that may
prevent normal completion of statements.
If such an event occurs, then execution of one or more statements may be terminated before all steps of their normal mode of execution have completed; such statements are said to complete abruptly.
An abrupt completion always has an associated reason, which is one of the following:
The terms "complete normally" and "complete abruptly" also apply to
the evaluation of expressions (§15.6). The only
reason an expression can complete abruptly is that an exception is
thrown, because of either a throw
with a given value (§14.18) or a run-time exception or error (§11 (Exceptions), §15.6).
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.
A block is a sequence of statements, local variable declaration statements, and local class and interface declarations within braces.
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.
A local class is a nested class (§8 (Classes)) whose declaration is immediately contained by a block (§14.2).
A local interface is a nested interface (§9 (Interfaces)) whose declaration is immediately contained by a block.
The following productions are shown here for convenience:
interface
TypeIdentifier
[TypeParameters]
[InterfaceExtends]
[InterfacePermits]
InterfaceBody
Local class and interface declarations may be intermixed freely with statements (including local variable declaration statements) in the containing block.
It is a compile-time error if a local class or interface declaration
has any of the access modifiers public
, protected
, or private
(§6.6).
It is a compile-time error if a local class or interface declaration
has the modifier static
(§8.1.1.4), sealed
,
or non-sealed
(§8.1.1.2, §9.1.1.4).
It is a compile-time error if the direct superclass or a direct
superinterface of a local class is sealed
.
It is a compile-time error if a direct superinterface of a
local interface is sealed
.
A local class may be a normal class (§8.1), an
enum class (§8.9), or a record class (§8.10). Every local normal class is an inner class
(§8.1.3). Every local enum class and local record
class is implicitly static
(§8.1.1.4), and
therefore not an inner class.
A local interface may be a normal interface (§9.1), but not an annotation interface (§9.6). Every local interface is implicitly static
(§9.1.1.3).
Like an anonymous class (§15.9.5), a local class or interface is not a member of any package, class, or interface (§7.1, §8.5). Unlike an anonymous class, a local class or interface has a simple name (§6.2, §6.7).
The scope and shadowing of a local class or interface declaration is specified in §6.3 and §6.4.
Example 14.3-1. Local Class Declarations
Here is an example that illustrates several aspects of the rules given above:
class Global { class Cyclic {} void foo() { new Cyclic(); // create a Global.Cyclic class Cyclic extends Cyclic {} // circular definition { class Local {} { class Local {} // compile-time error } class Local {} // compile-time error class AnotherLocal { void bar() { class Local {} // ok } } } class Local {} // ok, not in scope of prior Local } }
The first statement of method foo
creates an instance of the member
class Global.Cyclic
rather than an instance of the
local class Cyclic
, because the statement appears
prior to the scope of the local class declaration.
The fact that the scope of a local class declaration
encompasses its whole declaration (not only its body) means that the
definition of the local class Cyclic
is indeed
cyclic because it extends itself rather
than Global.Cyclic
. Consequently, the declaration
of the local class Cyclic
is rejected at compile
time.
Since local class names cannot be redeclared within
the same method (or constructor or initializer, as the case may be),
the second and third declarations of Local
result
in compile-time errors. However, Local
can be
redeclared in the context of another, more deeply nested, class such
as AnotherLocal
.
The final declaration of Local
is
legal, since it occurs outside the scope of any prior declaration
of Local
.
A local variable declaration declares and optionally initializes one or more local variables (§4.12.3).
See §8.3 for UnannType. The following productions from §4.3, §8.3, and §8.4.1 are shown here for convenience:
A local variable declaration can appear in the following locations:
The rules concerning annotation modifiers for a local variable declaration are specified in §9.7.4 and §9.7.5.
If the keyword final
appears as a modifier for a local variable
declaration, then the local variable is a final
variable
(§4.12.4).
It is a compile-time error if final
appears more than once as a
modifier for a local variable declaration.
It is a compile-time error if a local variable declaration that (i) does not include an Identifier and (ii) does not have an initializer, appears in any of the following locations:
It is a compile-time error if the LocalVariableType is var
and
any of the following are true:
Example 14.4-1. Local Variables Declared With var
The following code illustrates these rules
restricting the use of var
:
var a = 1; // Legal var b = 2, c = 3.0; // Illegal: multiple declarators var d[] = new int[4]; // Illegal: extra bracket pairs var e; // Illegal: no initializer var f = { 6 }; // Illegal: array initializer var g = (g = 7); // Illegal: self reference in initializer
These restrictions help to avoid confusion about the
type being represented by var
.
Each declarator in a local variable declaration declares one local variable. If the declarator includes an Identifier then this is the name of the local variable, otherwise the local variable is unnamed (§6.1).
If the optional keyword final
appears at the start of the
declaration, the variable being declared is a final variable
(§4.12.4).
The declared type of a local variable is determined as follows:
If the LocalVariableType is UnannType, and no bracket pairs appear in UnannType or VariableDeclaratorId, then the type of the local variable is denoted by UnannType.
If the LocalVariableType is UnannType, and bracket pairs appear in UnannType or VariableDeclaratorId, then the type of the local variable is specified by §10.2.
If the LocalVariableType is var
, then let T be the type of the
initializer expression when treated as if it did not appear in
an assignment context, and were thus a standalone expression
(§15.2). The type of the local variable is
the upward projection of T with respect to all synthetic type
variables mentioned by T (§4.10.5).
It is a compile-time error if T is the null type.
Because the initializer is treated as if it did not appear in an assignment context, an error occurs if it is a lambda expression (§15.27) or a method reference expression (§15.13).
The scope and shadowing of a local variable declaration is specified in §6.3 and §6.4.
References to a local variable from a nested class or interface, or a lambda expression, are restricted, as specified in §6.5.6.1.
Example 14.4.1-1. Type of Local Variables Declared With var
The following code illustrates the typing of
variables declared with var
:
var a = 1; // a has type 'int' var b = java.util.List.of(1, 2); // b has type 'List<Integer>' var c = "x".getClass(); // c has type 'Class<? extends String>' // (see JLS 15.12.2.6) var d = new Object() {}; // d has the type of the anonymous class var e = (CharSequence & Comparable<String>) "x"; // e has type CharSequence & Comparable<String> var f = () -> "hello"; // Illegal: lambda not in an assignment context var g = null; // Illegal: null type
Note that some variables declared with var
cannot
be declared with an explicit type, because the type of the variable is
not denotable.
Upward projection is applied to the type of the initializer when determining the type of the variable. If the type of the initializer contains capture variables, this projection maps the type of the initializer to a supertype that does not contain capture variables.
While it would be possible to allow the type of the variable to mention capture variables, by projecting them away we enforce an attractive invariant that the scope of a capture variable is never larger than the statement containing the expression whose type is captured. Informally, capture variables cannot "leak" into subsequent statements.
A local variable declaration statement consists of a local variable declaration.
Every local variable declaration statement is immediately contained by a block, whereas other kinds of statement (§14.5) may be immediately contained by either a block or another statement.
In the containing block, local variable declaration statements may be intermixed freely with other kinds of statements and with local class and interface declarations.
A local variable declaration statement is an executable statement. Every time it is executed, the declarators are processed in order from left to right. If a declarator has an initializer, the initializer is evaluated and its value is assigned to the variable.
If a declarator does not have an initializer, then every reference to the variable must be preceded by execution of an assignment to the variable, or a compile-time error occurs by the rules of §16 (Definite Assignment).
Each initializer (except the first) is evaluated only if evaluation of the preceding initializer completes normally.
Execution of the local variable declaration statement completes normally only if evaluation of the last initializer completes normally.
If none of the declarators in a local variable declaration statement have an initializer, then executing the statement always completes normally.
There are many kinds of statements in the Java programming language. Most correspond to statements in the C and C++ languages, but some are unique.
As in C and C++, the if
statement of the Java programming language 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 programming language, like C and C++
and many programming languages before them, arbitrarily decrees that
an else
clause belongs to the innermost if
to which it might
possibly belong. This rule is captured by the following
grammar:
The following productions from §14.9 are shown here for convenience:
Statements are thus
grammatically divided into two categories: those that might end in an
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.
Statements may have label prefixes.
The Identifier is declared to be the label of the immediately contained Statement.
Unlike C and C++, the
Java programming language has no goto
statement; identifier statement labels are
used with break
or continue
statements (§14.15, §14.16) appearing anywhere
within the labeled statement.
The scope of a label of a labeled statement is the immediately contained Statement.
It is a compile-time error if the name of a label of a labeled statement is used within the scope of the label as a label of another labeled statement.
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 obscure (§6.4.2) a package, class, interface, method, field, parameter, or local variable with the same name. Use of an identifier as a class, interface, method, field, local variable or as the parameter of an exception handler (§14.20) does not obscure 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.
Example 14.7-1. Labels and Identifiers
The following code was taken from a version of the
class String
and its method indexOf
, where the
label was originally called test
. Changing the
label to have the same name as the local variable i
does not obscure the label in the scope of the declaration
of i
. Thus, the code is valid.
class Test { char[] value; int offset, count; int indexOf(TestString str, int fromIndex) { char[] v1 = value, v2 = str.value; int max = offset + (count - str.count); int start = offset + ((fromIndex < 0) ? 0 : fromIndex); i: for (int i = start; i <= max; i++) { int n = str.count, j = i, k = str.offset; while (n-- != 0) { if (v1[j++] != v2[k++]) continue i; } return i - offset; } return -1; } }
The identifier max
could also
have been used as the statement label; the label would not obscure the
local variable max
within the labeled
statement.
Certain kinds of expressions may be used as statements by following them with semicolons.
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.
Unlike C and C++, the Java programming language allows only certain forms of expressions to be used as expression statements. For example, it is legal to use a method invocation expression (§15.12):
System.out.println("Hello world"); // OK
but it is not legal to use a parenthesized expression (§15.8.5):
(System.out.println("Hello world")); // illegal
Note that the Java programming language does not allow a "cast to
void
" - void
is not a type - so the traditional C trick of writing
an expression statement such as:
(void)... ; // incorrect!
does not work. On the other hand, the Java programming language
allows all the most useful kinds of expressions in expression
statements, and it 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.26) or a local variable
declaration statement (§14.4) can be used
instead.
The if
statement allows conditional execution of a statement or a conditional
choice of two statements, executing one or the other but not
both.
The
Expression must have type boolean
or Boolean
, or a compile-time
error occurs.
An
if
-then
statement is executed by first evaluating the
Expression. If the result is of type Boolean
, it is subjected to
unboxing conversion (§5.1.8).
If
evaluation of the Expression or the subsequent unboxing conversion
(if any) 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:
An
if
-then
-else
statement is executed by first evaluating the
Expression. If the result is of type Boolean
, it is subjected to
unboxing conversion (§5.1.8).
If
evaluation of the Expression or the subsequent unboxing conversion
(if any) 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:
If
the value is true
, then the first contained Statement (the one
before the else
keyword) is executed; the if
-then
-else
statement completes normally if and only if execution of that
statement completes normally.
If
the value is false
, then the second contained Statement (the
one after the else
keyword) is executed; the if
-then
-else
statement completes normally if and only if execution of that
statement completes normally.
An assertion is an assert
statement containing
a boolean expression. An assertion is either
enabled or disabled. If an
assertion is enabled, execution of the assertion causes evaluation of
the boolean expression and an error is reported if the expression
evaluates to false
. If the assertion is disabled, execution of the
assertion has no effect whatsoever.
To ease the presentation, the first Expression in both forms of the
assert
statement is referred to
as Expression1. In the second form of the
assert
statement, the second Expression is referred to
as Expression2.
It is a compile-time error if Expression1 does
not have type boolean
or Boolean
.
It is a compile-time error if, in the second form of the assert
statement, Expression2 is void
(§15.1).
An assert
statement that is executed after its
class or interface has completed initialization is enabled if and only
if the host system has determined that the top level class or
interface that lexically contains the assert
statement enables
assertions.
Whether a top level class or interface enables assertions is determined no later than the earliest of (i) the initialization of the top level class or interface, and (ii) the initialization of any class or interface nested in the top level class or interface. Whether a top level class or interface enables assertions cannot be changed after it has been determined.
An assert
statement that is executed before its
class or interface has completed initialization is enabled.
This rule is motivated by a case that demands special treatment. Recall that the assertion status of a class is set no later than the time it is initialized. It is possible, though generally not desirable, to execute methods or constructors prior to initialization. This can happen when a class hierarchy contains a circularity in its static initialization, as in the following example:
public class Foo { public static void main(String[] args) { Baz.testAsserts(); // Will execute after Baz is initialized. } } class Bar { static { Baz.testAsserts(); // Will execute before Baz is initialized! } } class Baz extends Bar { static void testAsserts() { boolean enabled = false; assert enabled = true; System.out.println("Asserts " + (enabled ? "enabled" : "disabled")); } }
Invoking Baz.testAsserts()
causes Baz
to be initialized. Before this can
happen, Bar
must be
initialized. Bar
's static initializer again
invokes Baz.testAsserts()
. Because initialization
of Baz
is already in progress by the current
thread, the second invocation executes immediately,
though Baz
is not initialized
(§12.4.2).
Because of the rule above, if the program above is executed without enabling assertions, it must print:
Asserts enabled Asserts disabled
A disabled assert
statement does nothing. In particular, neither
Expression1 nor Expression2
(if it is present) are evaluated. Execution of a disabled assert
statement always completes normally.
An
enabled assert
statement is executed by first
evaluating Expression1. If the result is of type
Boolean
, it is subjected to unboxing conversion
(§5.1.8).
If
evaluation of Expression1 or the subsequent
unboxing conversion (if any) completes abruptly for some reason, the
assert
statement completes abruptly for the same reason.
Otherwise, execution continues by making a choice based on the value of Expression1:
Typically, assertion checking is enabled during program development and testing, and disabled for deployment, to improve performance.
Because assertions may be disabled, programs must not assume that the expressions contained in assertions will be evaluated. Thus, these boolean expressions should generally be free of side effects. Evaluating such a boolean expression should not affect any state that is visible after the evaluation is complete. It is not illegal for a boolean expression contained in an assertion to have a side effect, but it is generally inappropriate, as it could cause program behavior to vary depending on whether assertions were enabled or disabled.
In light of this, assertions should not be used for
argument checking in public
methods. Argument checking is typically
part of the contract of a method, and this contract must be upheld
whether assertions are enabled or disabled.
A secondary problem with using assertions for
argument checking is that erroneous arguments should result in an
appropriate run-time exception (such as IllegalArgumentException
,
ArrayIndexOutOfBoundsException
, or NullPointerException
). An assertion failure will not throw an
appropriate exception. Again, it is not illegal to use assertions for
argument checking on public
methods, but it is generally
inappropriate. It is intended that AssertionError
never be caught,
but it is possible to do so, thus the rules for try
statements
should treat assertions appearing in a try
block similarly to the
current treatment of throw
statements.
The switch
statement transfers control to one of several statements
or expressions, depending on the value of an expression.
The Expression is called the selector expression.
The type of the selector expression must be char
, byte
, short
, int
,
or a reference type, or a compile-time error occurs.
The body of both a switch
statement and a switch
expression (§15.28) is called a switch block.
This subsection presents general rules which apply to all switch blocks,
whether they appear in switch
statements or switch
expressions.
Other subsections present additional rules which apply either to
switch blocks in switch
statements (§14.11.2)
or to switch blocks in switch
expressions
(§15.28.1).
case
CaseConstant {,
CaseConstant} case
null
[,
default
] case
CasePattern {,
CasePattern} [Guard] default
A switch block can consist of either:
Every switch rule and switch labeled statement group starts with a
switch label, which is either a case
label
or a default
label. Multiple switch labels are permitted for a
switch labeled statement group.
A case
label has either a (non-empty) list of case
constants, a null
literal,
or a (non-empty) list of case
patterns.
Every case
constant must be either a constant expression (§15.29), or the name of an enum constant (§8.9.1), otherwise a compile-time error occurs.
A case
label with a null
literal may have an optional default
.
A case
label with case
patterns may have an optional when
expression,
known as a guard, which represents a further test on values
that match the patterns. A case
label is said to be
unguarded if either (i) it has no guard, or (ii) it has a
guard that is a constant expression (§15.29) with value
true
; and guarded otherwise.
It is a compile-time error for a case
label to have more than one case
pattern and declare any pattern variables (other than those declared by a
guard associated with the case
label).
If a case
label with more than one case
pattern could declare pattern
variables, then it would not be clear which variables would be initialized
if the case
label were to apply. For example:
Object obj = ...; switch (obj) { case Integer i, Boolean b -> { ... // Error! Is i or b initialized? } ... }
Even if only one of the case
patterns declares a pattern variable, it would
still not be clear whether the variable was initialized or not; for example:
Object obj = ...; switch (obj) { case Integer i, Boolean _ -> { ... // Error! Is i initialized? } ... }
The following does not result in a compile-time error:
Object obj = ...; switch (obj) { case Integer _, Boolean _ -> { ... // Matches both an Integer and a Boolean } ... }
Switch labels and their case
constants, null
literals, and case
patterns
are said to be associated with the switch block.
For a given switch block both of the following must be true, otherwise a compile-time error occurs:
A guard associated with a case
label must satisfy all of the following
conditions, otherwise a compile-time error occurs:
Any local variable, formal parameter, or exception parameter used but not
declared in a guard must either be final
or effectively final (§4.12.4).
Any blank final
variable used but not declared in a guard must be definitely
assigned (§16 (Definite Assignment)) before the guard.
A guard cannot be a constant expression (§15.29) with the value false
.
The switch block of a switch
statement or a switch
expression is
switch compatible with the type of the selector expression,
T, if all of the following are true:
If a null
literal is associated with the switch block, then T is a
reference type.
For every case
constant associated with the switch block that names an
enum constant, the type of the case
constant is assignment compatible with
T (§5.2).
For each case
constant associated with the switch block that is a constant
expression, the constant is assignment compatible with T, and T is one
of char
, byte
, short
, int
, Character
, Byte
, Short
, Integer
,
or String
.
Every pattern p
associated with the switch block is applicable at type
T (§14.30.3).
Switch blocks are not designed to work with the types boolean
, long
,
float
, and double
. The selector expression of a switch
statement or
switch
expression can not have one of these types.
The switch block of a switch
statement or a switch
expression must be switch
compatible with the type of the selector expression, or a compile-time error
occurs.
A switch label in a switch block is said to be dominated if for every value that it applies to, it can be determined that one of the preceding switch labels would also apply. It is a compile-time error if any switch label in a switch block is dominated. The rules for determining whether a switch label is dominated are as follows:
A case
label with a case
pattern q
is dominated if there is a
preceding unguarded case
label in the switch block with a case
pattern
p
, and p
dominates q
(§14.30.3).
The definition of one pattern dominating another pattern is based on types.
For example, the type pattern Object
o
dominates the
type pattern String
s
, and so the following results in
a compile-time error:
Object obj = ... switch (obj) { case Object o -> System.out.println("An object"); case String s -> // Error! System.out.println("A string"); }
A guarded case
label with a case
pattern is dominated by a case
label with the same pattern but without the guard. For example, the
following results in a compile-time error:
String str = ...; switch (str) { case String s -> System.out.println("A string"); case String s when s.length() == 2 -> // Error! System.out.println("Two character string"); ... }
On the other hand, a guarded case
label with a case
pattern is not
considered to dominate an unguarded case
label with the same case
pattern. This allows the following common pattern programming style:
Integer j = ...; switch (j) { case Integer i when i <= 0 -> System.out.println("Less than or equal to zero"); case Integer i -> System.out.println("An integer"); }
The only exception is where the guard is a constant expression that has
the value true
, for example:
Integer j = ...; switch (j) { case Integer i when true -> // Ok System.out.println("An integer"); case Integer i -> // Error! System.out.println("An integer"); }
A case
label with more than one case
pattern is dominated if any one of
these patterns is dominated by a pattern that appears as a case
pattern in
a preceding unguarded case
label, and so the following results in a
compile-time error (as the type pattern Integer
_
is dominated
by the type pattern Number
_
):
Object obj = ... switch (obj) { case Number _ -> System.out.println("A Number"); case Integer _, String _ -> // Error - dominated! System.out.println("An Integer or a String"); ... }
A case
label with a case
constant c
is dominated if one of the
following holds:
c
is a constant expression of a primitive type S, and there is a
preceding case
label in the switch block with an unguarded case
pattern p
, where p
is unconditional for the wrapper class of S.
c
is a constant expression of a reference type T, and there is a
preceding case
label in the switch block with an unguarded case
pattern p
, where p
is unconditional for the type T.
c
names an enum constant of enum class E, and there is a preceding
case
label in the switch block with an unguarded case
pattern p
,
where p
is unconditional for the type E.
For example, a case
label with an Integer
type pattern dominates a
case
label with an integer literal:
Integer j = ...; switch (j) { case Integer i -> System.out.println("An integer"); case 42 -> // Error - dominated! System.out.println("42!"); }
A default
label or a case
null
,
default
label is dominated if
there is a preceding unguarded case
label in the switch block with a
case
pattern p
where p
is unconditional for the type of the selector
expression (§14.30.3).
A case
label with a case
pattern that is unconditional for the type of
the selector expression will, as the name suggests, match every value and
so behave like a default
label. A switch block can not have more than
one switch label that acts like a default
.
It is a compile-time error if there is a case
label with n
(n>1) case
patterns p1
, ..., pn
in a switch block where one of
the patterns pi
(1≤i<n) dominates another of the patterns
pj
(i<j≤n).
It is a compile-time error if any of the following holds:
If used, a default
label should come last in a switch block.
For compatibility reasons, a default
label may appear before case
labels that
do not have a null
literal or case
patterns.
int i = ...; switch(i) { default -> System.out.println("Some other integer"); case 42 -> // allowed System.out.println("42"); }
If used, a case
null
,
default
label should come last in a switch
block.
It is a compile-time error if, in a switch block that consists of switch labeled
statement groups, a statement is labeled with a case
label that declares one
or more pattern variables (§6.3.3), and either:
An immediately preceding statement in the switch block can complete normally (§14.22), or
The first condition prevents a statement group from "falling through" to another
statement group without initializing pattern variables. For example, were the
statement labeled by case
Integer
i
reachable from the
preceding statement group, the pattern variable i
would not
have been initialized:
Object o = "Hello"; switch (o) { case String s: System.out.println("String: " + s ); // No break! case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching the // pattern `Integer i` default: }
Switch blocks consisting of switch label statement groups allow multiple labels to apply to a statement group. The second condition prevents a statement group from being executed based on one label without initializing the pattern variables of another label. For example:
Object o = "Hello World"; switch (o) { case String s: case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching the // pattern `Integer i` default: } Object obj = null; switch (obj) { case null: case String s: System.out.println(s); // Error! Can be reached // without matching the // pattern `String s` default: }
Both of these conditions apply only when the case
pattern declares pattern
variables. The following examples, in contrast, are unproblematic:
record R() {} record S() {} Object o = "Hello World"; switch (o) { case String s: System.out.println(s); // No break case R(): // No pattern variables declared System.out.println("It's either an R or a string"); break; default: } Object ob = new R(); switch (ob) { case R(): case S(): // Multiple case labels System.out.println("Either R or an S"); break; default: } Object obj = null; switch (obj) { case null: case R(): // Multiple case labels System.out.println("Either null or an R"); break; default: }
The switch block of a switch
expression or switch
statement is
exhaustive for a selector expression e
if one of the
following cases applies:
There is a case
null
,
default
label associated with the switch
block.
The set containing all the case
constants and case
patterns appearing in
an unguarded case
label (collectively known as case
elements) associated with the switch block is non-empty
and covers the type of the selector expression e
.
A set of case
elements, P, covers a type T if one of
the following cases applies:
T is an intersection type T1&
... &
Tn and P covers Ti,
for one of the types Ti (1≤ i ≤ n).
The type T is an enum class type E and P contains all of the names of the enum constants of E.
A default
label is permitted, but not required, in the case where the
names of all the enum constants appear as case
constants. For example:
enum E { F, G, H } static int testEnumExhaustive(E e) { return switch(e) { case F -> 0; case G -> 1; case H -> 2; // No default required! }; }
The type T names an abstract
sealed
class or sealed
interface C
and for every permitted direct subclass or subinterface D of C, one of
the following two conditions holds:
A default
label is permitted, but not required, in the case where the
switch block exhausts all the permitted direct subclasses and subinterfaces
of an abstract
sealed
class or sealed
interface. For example:
sealed interface I permits A, B, C {} final class A implements I {} final class B implements I {} record C(int j) implements I {} // Implicitly final static int testExhaustive1(I i) { return switch(i) { case A a -> 0; case B b -> 1; case C c -> 2; // No default required! }; }
As the switch block contains case
patterns that match
against all values of types A
, B
and
C
, and no other instances of type I
are permitted, this switch block is exhaustive.
The fact that a permitted direct subclass or subinterface may only extend a
particular parameterization of a generic sealed
superclass or
superinterface means that it may not always need to be considered when
determining whether a switch block is exhaustive. For example:
sealed interface J<X> permits D, E {} final class D<Y> implements J<String> {} final class E<X> implements J<X> {} static int testExhaustive2(J<Integer> ji) { return switch(ji) { // Exhaustive! case E<Integer> e -> 42; }; }
As the selector expression has type J
<Integer
>
the permitted direct subclass D
need not be considered as
there is no possibility that the value of ji
can be an
instance of D
.
The type T names a record class R, and P contains a record pattern p
with a type that names R and for every record component of R of type
U, if any, the singleton set containing the corresponding component
pattern of p
covers U.
A record pattern whose component patterns all cover the type of the corresponding record component is considered to cover the record type. For example:
record Test<X>(Object o, X x){} static int testExhaustiveRecordPattern(Test<String> r) { return switch(r) { // Exhaustive! case Test<String>(Object o, String s) -> 0; }; }
P rewrites to a set Q and Q covers T.
A set of case elements, P, rewrites to the set Q,
if a subset of P reduces to a pattern p
, and Q consists of the
remaining elements of P along with the pattern p
.
A non-empty set of patterns, RP, reduces to a single pattern rp if one of the following holds:
RP consists of record patterns whose types all erase to the same
record class R with k (k≥1) components and there is a
distinguished component cr
(1≤r≤k) of R such that
for every other component ci
(1≤i≤k,
i≠r) the set containing the component patterns from the
record patterns corresponding to component ci
is equivalent to a
single pattern qi
, the set containing the component patterns from the
record patterns corresponding to the component cr
reduces to a single
pattern q
, and rp is the record pattern of type R with a pattern
list consisting of the patterns q1
, ..., qr-1
, q
, qr+1
, ...,
qk
.
A non-empty set of patterns EP is equivalent to a single pattern ep if one of the following holds:
EP consists of type patterns whose types all have the same erasure T, and ep is a type pattern of type T.
EP consists of record patterns whose types all erase to the same
record class R with k (k≥1) components and for
every record component the set containing the corresponding component
patterns from the record patterns is equivalent to a single pattern
qj
(1≤j≤k), and ep is the record pattern of
type R with a component pattern list consisting of the component patterns
q1
,...qk
.
Ordinarily record patterns match only a subset of the values of the record type. However, a number of record patterns in a switch block can combine to actually match all of the values of the record type. For example:
sealed interface I permits A, B, C {} final class A implements I {} final class B implements I {} record C(int j) implements I {} // Implicitly final record Box(I i) {} int testExhaustiveRecordPatterns(Box b) { return switch (b) { // Exhaustive! case Box(A a) -> 0; case Box(B b) -> 1; case Box(C c) -> 2; }; }
Determining whether this switch block is exhaustive requires the analysis of
the combination of the record patterns. The set containing the record pattern
Box(I i)
covers the type Box
, and so the
set containing the patterns Box(A a)
,
Box(B b)
, and Box(C c)
can be rewritten to
the set containing the pattern Box(I i)
. This is because the
set containing the patterns A a
, B b
,
C c
reduces to the pattern I i
(because
the same set covers the type I
), and thus the set containing
the patterns Box(A a)
, Box(B b)
,
Box(C c)
reduces to the pattern Box(I i)
.
However, rewriting a set of record patterns is not always so simple. For example:
record IPair(I i, I j){} int testNonExhaustiveRecordPatterns(IPair p) { return switch (p) { // Not Exhaustive! case IPair(A a, A a) -> 0; case IPair(B b, B b) -> 1; case IPair(C c, C c) -> 2; }; }
It is tempting to apply the logic from the previous example to rewrite the
set containing the patterns IPair(A a, A a)
,
IPair(B b, B b)
, IPair(C c, C c)
to the
set containing the pattern IPair(I i, I j)
, and hence
conclude that the switch block exhausts the type IPair
. But
this is incorrect as, for example, the switch block does not actually have a
label that matches an IPair
value whose first component is an
A
value, and second component is a B
value. It is only valid to combine record patterns on one component if they
match the same values in the other components. For example, the set containing
the three record patterns IPair(A a, I i)
,
IPair(B b, I i)
, and IPair(C c, I i)
can
be reduced to the pattern IPair(I j, I i)
.
A switch
statement or expression is exhaustive if its
switch block is exhaustive for the selector expression.
Both the execution of a switch
statement (§14.11.3) and
the evaluation of a switch
expression (§15.28.2) need to
determine if a switch label associated with the switch block
applies to the value of the selector expression. This
proceeds as follows:
If the value is the null reference, then a case
label with a null
literal applies.
If the value is not the null reference, then we determine the first (if any)
case
label in the switch block that applies to the value as follows:
A case
label with a case
constant c
applies to a value of type
Character
, Byte
, Short
, or Integer
, if the value is first
subjected to unboxing conversion (§5.1.8) and the
constant c
is equal to the unboxed value.
Any unboxing conversion will complete normally as the value being unboxed is guaranteed not to be the null reference.
Equality is defined in terms of the ==
operator (§15.21).
A case
label with a case
constant c
applies to a value that is of
type char
, byte
, short
, int
, or String
or an enum type if the
constant c
is equal to the value.
Equality is defined in terms of the ==
operator unless the
value is a String
, in which case equality is defined in terms of the
equals
method of class String
.
Determining that a case
label with case
patterns p1
, ..., pn
(n≥1) applies to a value proceeds by
finding the first (if any) case
pattern pi
(1≤i≤n)
that applies to the value.
Determining that a case
pattern applies to a value proceeds first by
checking the value matches the pattern (§14.30.2).
Then:
If pattern matching completes abruptly then the whole process of determining which switch label applies completes abruptly for the same reason.
If pattern matching succeeds and the case
label is unguarded then
this case
pattern applies.
If pattern matching succeeds and the case
label is guarded, then
the guard is evaluated. If the result is of type Boolean
, it is
subjected to unboxing conversion (§5.1.8).
If evaluation of the guard or the subsequent unboxing conversion (if any) completes abruptly for some reason, then the whole process of determining which switch label applies completes abruptly for the same reason.
Otherwise, if the resulting value is true
then the case
pattern
applies.
If the value is not the null reference, and no case
label applies
according to the rules of step 2, but there is a default
label associated
with the switch block, then the default
label applies.
A single case
label can contain several case
constants.
The label applies to the value of the selector expression if any one of
its constants is equal to the value of the selector expression. For example,
in the following code, the case
label applies if the enum
variable day
is either one of the enum constants
shown:
switch (day) { ... case SATURDAY, SUNDAY : System.out.println("It's the weekend!"); break; ... }
If a case
label with a case
pattern applies, then this is because the
process of pattern matching the value against the pattern has succeeded (§14.30.2). If a value successfully matches a pattern then the
process of pattern matching initializes any pattern variables declared by the
pattern.
In C and C++ the body of a 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 code in the
Java programming language:
int q = (n+7)/8; switch (n%8) { case 0: do { foo(); // Great C hack, Tom, case 7: foo(); // but it's not valid here. 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.
In addition to the general rules for switch blocks
(§14.11.1), there are further rules for
switch blocks in switch
statements.
An enhanced switch
statement is one where either (i) the
type of the selector expression is not char
, byte
, short
, int
,
Character
, Byte
, Short
, Integer
, String
, or an enum type, or (ii)
there is a case
pattern or null
literal associated with the switch
block.
All of the following must be true for the switch block of a switch
statement,
or a compile-time error occurs:
Every switch rule expression in the switch block is a statement expression (§14.8).
switch
statements differ from switch
expressions in terms of which
expressions may appear to the right of an arrow (->
) in the switch
block, that is, which expressions may be used as
switch rule expressions. In a switch
statement, only
a statement expression may be used as a switch rule expression, but in a
switch
expression, any expression may be used (§15.28.1).
If the switch
statement is an enhanced switch
statement, then it must
be exhaustive (§14.11.1.1).
Prior to Java SE 21, switch
statements (and switch
expressions) were limited
in two ways: (i) the type of the selector expression was restricted to either an
integral type (excluding long
), an enum type, or String
and (ii) no case
null
labels were supported. Moreover, unlike switch
expressions,
switch
statements did not have to be exhaustive. This is often the cause of
difficult-to-detect bugs, where no switch label applies and the switch
statement will silently do nothing. For example:
enum E { A, B, C } E e = ...; switch (e) { case A -> System.out.println("A"); case B -> System.out.println("B"); // No case for C! }
In Java SE 21, in addition to supporting case
patterns, the two limitations of
switch
statements (and switch
expressions) listed above were relaxed to (i)
allow a selector expression of any reference type, and (ii) to allow a case
label with a null
literal. The designers of the Java programming language also decided
that enhanced switch
statements should align with switch
expressions and be
required to be exhaustive. This is often achieved with the addition of a trivial
default
label. For example, the following enhanced switch
statement is not
exhaustive:
Object o = ...; switch (o) { // Error - non-exhaustive switch! case String s -> System.out.println("A string!"); }
but it can easily be made exhaustive:
Object o = ...; switch (o) { case String s -> System.out.println("A string!"); default -> {} }
For compatibility reasons, switch
statements that are not enhanced switch
statements are not required to be exhaustive.
A switch
statement is executed by first evaluating the selector expression. If
evaluation of the selector expression completes abruptly, then the entire
switch
statement completes abruptly for the same reason.
If evaluation of the selector expression completes normally, then execution of
the switch
statement continues by determining if a switch label associated
with the switch block applies to the value of the selector expression (§14.11.1.2). Then:
If the process of determining which switch label applies completes abruptly,
then the entire switch
statement completes abruptly for the same reason.
If no switch label applies, then one of the following holds:
If the value of the selector expression is null
, then a NullPointerException
is
thrown and the entire switch
statement completes abruptly for that
reason.
If the switch
statement is an enhanced switch
statement, then a MatchException
is thrown and the entire switch
statement completes abruptly for that
reason.
If the value of the selector expression is not null
, and the switch
statement is not an enhanced switch
statement, then the entire
switch
statement completes normally.
If a switch label applies, then one of the following holds:
If it is the switch label for a switch rule expression,
then the switch rule expression is necessarily a statement
expression (§14.11.2). The statement
expression is evaluated. If the evaluation completes normally,
then the switch
statement completes normally. If the result
of evaluation is a value, it is discarded.
If it is the switch label for a switch rule block,
then the block is executed. If this block completes normally,
then the switch
statement completes normally.
If it is the switch label for a switch rule throw
statement,
then the throw
statement is executed.
If it is the switch label for a switch labeled statement group,
then all the statements in the switch block that follow the
switch label are executed in order. If these statements
complete normally, then the switch
statement completes
normally.
Otherwise, there are no statements in the switch block that follow the
switch label that applies, and the switch
statement completes
normally.
If execution of any statement or expression in the switch block completes abruptly, it is handled as follows:
If execution of a statement completes abruptly because of a
break
with no label, then no further action is taken and the
switch
statement completes normally.
Abrupt completion because of a break
with a
label is handled by the general rule for labeled statements
(§14.7).
If execution of a statement or expression completes abruptly
for any other reason, then the switch
statement completes
abruptly for the same reason.
Abrupt completion because of a yield
statement
is handled by the general rule for switch expressions
(§15.28.2).
Example 14.11.3-1. Fall-Through in the switch
Statement
When a switch label applies, and that switch label is for a switch rule, the switch rule expression or statement introduced by the switch label is executed, and nothing else. In the case of a switch label for a statement group, all the block statements in the switch block that follow the switch label are executed, including those that appear after subsequent switch labels. The effect is that, as in C and C++, execution of statements can "fall through labels."
For example, the program:
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"); } } public static void main(String[] args) { howMany(3); howMany(2); howMany(1); } }
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:
many too many one too many
Fall through can be the cause of subtle
bugs. If code is not to fall through case
to case
in
this manner, then break
statements can be used to indicate when
control should be transferred, or switch rules can be used, as in
the program:
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 } } static void howManyAgain(int k) { switch (k) { case 1 -> System.out.println("one"); case 2 -> System.out.println("two"); case 3 -> System.out.println("many"); } } public static void main(String[] args) { howMany(1); howMany(2); howMany(3); howManyAgain(1); howManyAgain(2); howManyAgain(3); } }
This program prints:
one two many one two many
The
while
statement executes an Expression and a Statement
repeatedly until the value of the Expression is false
.
The
Expression must have type boolean
or Boolean
, or a compile-time
error occurs.
A while
statement is executed by first evaluating the
Expression. If the result is of type Boolean
, it is subjected to
unboxing conversion (§5.1.8).
If evaluation of the Expression or the subsequent unboxing
conversion (if any) 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:
If the value is true
, then the contained Statement is
executed. Then there is a choice:
If execution of the Statement completes normally, then the
entire while
statement is executed again, beginning by
re-evaluating the Expression.
If execution of the Statement completes abruptly, see §14.12.1.
If the (possibly unboxed) value of the Expression is false
,
no further action is taken and the while
statement completes
normally.
If the (possibly unboxed) value of the
Expression is false
the first time it is evaluated, then the
Statement is not executed.
Abrupt completion of the contained Statement is handled in the following manner:
If execution of the Statement completes abruptly because of a
break
with no label, no further action is taken and the
while
statement completes normally.
If execution of the Statement completes abruptly because of a
continue
with no label, then the entire while
statement is
executed again.
If execution of the Statement completes abruptly because of a
continue
with label L
, then there is a choice:
If execution of the Statement completes abruptly for any other
reason, the while
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.7).
The do
statement executes a Statement and an Expression repeatedly until
the value of the Expression is false
.
The
Expression must have type boolean
or Boolean
, or a compile-time
error occurs.
A do
statement is executed by first executing the Statement. Then
there is a choice:
If execution of the Statement completes normally, then the
Expression is evaluated. If the result is of type Boolean
,
it is subjected to unboxing conversion
(§5.1.8).
If evaluation of the Expression or the subsequent unboxing
conversion (if any) completes abruptly for some reason, the do
statement completes abruptly for the same reason.
If execution of the Statement completes abruptly, see §14.13.1.
Executing a do
statement always executes the
contained Statement at least once.
Abrupt completion of the contained Statement is handled in the following manner:
If execution of the Statement completes abruptly because of a
break
with no label, then no further action is taken and the
do
statement completes normally.
If execution of the Statement completes abruptly because of a
continue
with no label, then the Expression is
evaluated. Then there is a choice based on the resulting
value:
If execution of the Statement completes abruptly because of a
continue
with label L
, then there is a choice:
If execution of the Statement completes abruptly for any other
reason, the 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 for labeled
statements (§14.7).
Example 14.13-1. The do
Statement
The following code is one possible implementation of
the toHexString
method 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.
The for
statement has two forms:
The
basic for
statement executes some initialization code, then executes
an Expression, a Statement, and some update code repeatedly until
the value of the Expression is false
.
The type of the Expression must be boolean
or Boolean
, or a
compile-time error occurs.
The scope and shadowing of a local variable declared in the
ForInit part of a basic for
statement is
specified in §6.3 and §6.4.
References to a local variable declared in the ForInit
part of a basic for
statement from a nested class or interface,
or a lambda expression, are restricted, as specified in
§6.5.6.1.
A for
statement is executed by first executing
the ForInit code:
If the ForInit code is a list of statement expressions (§14.8), the expressions are evaluated in sequence from left to right; their values, if any, are discarded.
If evaluation of any expression completes abruptly for some
reason, the for
statement completes abruptly for the same
reason; any ForInit statement expressions
to the right of the one that completed abruptly are not
evaluated.
If the ForInit code is a local variable declaration (§14.4), it is executed as if it were a local variable declaration statement appearing in a block (§14.4.2).
If execution of the local variable declaration completes
abruptly for any reason, the for
statement completes abruptly
for the same reason.
Next, a for
iteration step is performed, as follows:
If the Expression is present, it is evaluated. If the result
is of type Boolean
, it is subjected to unboxing conversion
(§5.1.8).
If evaluation of the Expression or the subsequent unboxing
conversion (if any) completes abruptly, the 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; see next bullet.
If the Expression is not present, or it is present and the
value resulting from its evaluation (including any possible
unboxing) is true
, then the contained Statement is
executed. Then there is a choice:
If execution of the Statement completes normally, then the following two steps are performed in sequence:
First, if the ForUpdate part is
present, the expressions are evaluated in sequence from
left to right; their values, if any, are discarded. If
evaluation of any expression completes abruptly for some
reason, the 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 execution of the Statement completes abruptly, see §14.14.1.3.
If the Expression is present and the value resulting from its
evaluation (including any possible unboxing) is false
, no
further action is taken and the for
statement completes
normally.
If the (possibly unboxed) value of the
Expression is 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.
Abrupt completion of the contained Statement is handled in the following manner:
If execution of the Statement completes abruptly because of a
break
with no label, no further action is taken and the for
statement completes normally.
If execution of the Statement completes abruptly because of a
continue
with no label, then the following two steps are
performed in sequence:
If execution of the Statement completes abruptly because of a
continue
with label L
, then there is a choice:
If execution of the Statement completes abruptly for any other
reason, the 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.7).
The enhanced for
statement has the form:
The following productions from §4.3, §8.3, §8.4.1, and §14.4 are shown here for convenience:
The type of the Expression must be an array type
(§10.1) or a subtype of the raw type Iterable
,
or a compile-time error occurs.
The header of the enhanced for
statement either declares a local variable
whose name is the identifier given by VariableDeclaratorId, or declares an
unnamed local variable (§6.3). When the enhanced for
statement is executed, the local variable is initialized, on each iteration of
the loop, to successive elements of the Iterable
or the array produced by the
expression.
The rules for a local variable declared in the header of an enhanced
for
statement are specified in §14.4,
disregarding any rules in that section which apply when the
LocalVariableType is var
. In addition, all of the following must be
true, or a compile-time error occurs:
The scope and shadowing of a local variable declared in the header of an
enhanced for
statement is specified in §6.3 and
§6.4.
References to the local variable from a nested class or interface, or a lambda expression, are restricted, as specified in §6.5.6.1.
The type T of the local variable declared in the header of the enhanced
for
statement is determined as follows:
If the LocalVariableType is UnannType, and no bracket pairs appear in UnannType or VariableDeclaratorId, then T is the type denoted by UnannType.
If the LocalVariableType is UnannType, and bracket pairs appear in UnannType or VariableDeclaratorId, then T is specified by §10.2.
If the LocalVariableType is var
, then let R be derived from the
type of the Expression, as follows:
T is the upward projection of R with respect to all synthetic type variables mentioned by R (§4.10.5).
The precise meaning of the enhanced for
statement is given by
translation into a basic for
statement, as follows:
If the type of Expression is a subtype of Iterable
, then the
basic for
statement has this form:
for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} T VarDeclId = (TargetType) #i.next(); Statement }
If the type of Expression is a subtype of
Iterable
<
X>
for some type argument X, then
I is the type java.util.Iterator
<
X>
. Otherwise,
I is the raw type java.util.Iterator
.
#i
is an automatically generated identifier
that is distinct from any other identifiers (automatically
generated or otherwise) that are in scope (§6.3)
at the point where the enhanced for
statement occurs.
{VariableModifier} is as given in the header
of the enhanced for
statement.
If T is a reference type, then TargetType
is T. Otherwise, TargetType is the upper
bound of the capture conversion (§5.1.10)
of the type argument of I, or Object
if I is raw.
If the declaration of a local variable in the header includes an
identifier then VarDeclId is defined to be
Identifier as given in the header; otherwise
VarDeclId is defined to be _
(underscore).
Otherwise, the Expression necessarily has an array type,
S[]
, and the basic for
statement has this form:
S[]
#a = Expression;L1
:L2
: ...Lm
: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} T VarDeclId = #a[#i]; Statement }
L1
... Lm
is the (possibly empty) sequence of labels
immediately preceding the enhanced for
statement.
#a
and #i
are
automatically generated identifiers that are distinct from any
other identifiers (automatically generated or otherwise) that
are in scope at the point where the enhanced for
statement
occurs.
{VariableModifier} is as given in the header
of the enhanced for
statement.
If the declaration of a local variable in the header includes an
identifier then VarDeclId is defined to be
Identifier as given in the header; otherwise
VarDeclId is defined to be _
(underscore).
For example, this code:
List<? extends Integer> l = ... for (float i : l) ...
will be translated to:
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = (Integer)#i.next(); ...
Example 14.14-1. Enhanced for
And Arrays
The following program, which calculates the sum of
an integer array, shows how enhanced for
works for arrays:
int sum(int[] a) { int sum = 0; for (int i : a) sum += i; return sum; }
Example 14.14-2. Enhanced for
And Unboxing Conversion
The following program combines the enhanced for
statement with auto-unboxing to translate a histogram into a frequency
table:
Map<String, Integer> histogram = ...; double total = 0; for (int i : histogram.values()) total += i; for (Map.Entry<String, Integer> e : histogram.entrySet()) System.out.println(e.getKey() + " " + e.getValue() / total); }
A break
statement transfers control out of an enclosing statement.
There are two kinds of break
statement:
A break
statement with no label attempts to transfer control to the
innermost enclosing switch
, while
, do
, or for
statement;
this enclosing statement, which is called the
break target, then immediately completes normally.
A break
statement with label Identifier attempts to transfer
control to the enclosing labeled statement (§14.7)
that has the same Identifier as its label; this enclosing
statement, which is called the break target, then
immediately completes normally. In this case, the break target
need not be a switch
, while
, do
, or for
statement.
It is a compile-time error if a break
statement has no break target.
It is a compile-time error if the break target contains any
method, constructor, instance initializer, static initializer,
lambda expression, or switch expression that encloses the
break
statement. That is, there are no non-local jumps.
Execution of a break
statement with no label always completes
abruptly, the reason being a break
with no label.
Execution of a break
statement with label Identifier always
completes abruptly, the reason being a break
with
label Identifier.
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.20) within the break target whose try
blocks or catch
clauses 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.
Example 14.15-1. The 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[
for
every i
][j
]i
and j
for which the array
reference edges[
does not throw an
i
][j
]ArrayIndexOutOfBoundsException
.
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
.
A continue
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.
There are two kinds of continue
statement:
A continue
statement with no label attempts to transfer control to the
innermost enclosing while
, do
, or for
statement;
this enclosing statement, which is called the
continue target, then immediately ends the
current iteration and begins a new one.
A continue
statement with label Identifier attempts to transfer
control to the enclosing labeled statement (§14.7)
that has the same Identifier as its label; this enclosing statement,
which is called the continue target, then
immediately ends the current iteration and begins a new one. In this case,
the continue target must be a while
, do
, or for
statement,
or a compile-time error occurs.
It is a compile-time error if a continue
statement has no
continue target.
It is a compile-time error if the continue target contains any
method, constructor, instance initializer, static initializer,
lambda expression, or switch expression that encloses the
continue
statement. That is, there are no non-local jumps.
Execution of a continue
statement with no label always completes
abruptly, the reason being a continue
with no label.
Execution of a continue
statement with label Identifier always
completes abruptly, the reason being a continue
with
label Identifier.
It can be seen, then, that a continue
statement
always completes abruptly.
See the descriptions of the while
statement
(§14.12), do
statement
(§14.13), and for
statement
(§14.14) 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.20) within the continue target whose try
blocks or catch
clauses 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.
Example 14.16-1. The continue
Statement
In the Graph
class in
§14.15, 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 { 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][]; edgelists: for (int k = 0; k < n; ++k) { 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]; continue edgelists; } //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; } //edgelists return new Graph(newedges); } }
Which to use, if either, is largely a matter of programming style.
A return
statement returns control to the invoker of a method
(§8.4, §15.12) or
constructor (§8.8, §15.9).
There are two kinds of return
statement:
A return
statement attempts to transfer control to the invoker
of the innermost enclosing constructor, method, or lambda expression;
this enclosing declaration or expression is called the
return target. In the case of a return
statement
with value Expression, the value of the Expression becomes the
value of the invocation.
It is a compile-time error if a return
statement has no return target.
It is a compile-time error if the return target contains either (i)
an instance or static initializer that encloses the return
statement,
or (ii) a switch
expression that encloses the return
statement.
It is a compile-time error if the return target of a return
statement
with no value is a method, and that method is not declared void
.
It is a compile-time error if the return target of a return
statement
with value Expression is either a constructor, or a method that
is declared void
.
It is a compile-time error if the return target of a return
statement
with value Expression is a method with declared return type T,
and the type of Expression is not assignable compatible
(§5.2) with T.
Execution of a return
statement with no value always completes
abruptly, the reason being a return with no value.
Execution of a return
statement with value Expression 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.20) within the method or constructor whose
try
blocks or catch
clauses 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.
A throw
statement causes an exception (§11 (Exceptions)) to be
thrown. The result is an immediate transfer of control
(§11.3) that may exit multiple statements and
multiple constructor, instance initializer, static initializer and
field initializer evaluations, and method invocations until a try
statement (§14.20) is found that catches the
thrown value. If no such try
statement is found, then execution of
the thread (§17 (Threads and Locks)) that executed the throw
is
terminated (§11.3) after invocation of
the uncaughtException
method for the thread group
to which the thread belongs.
The
Expression in a throw
statement must either denote a variable or
value of a reference type which is assignable
(§5.2) to the type Throwable
, or denote the
null reference, or a compile-time error occurs.
The reference type of the Expression will always
be a class type (since no interface types are assignable to
Throwable
) which is not parameterized (since a subclass of
Throwable
cannot be generic (§8.1.2)).
At least one of the following three conditions must be true, or a compile-time error occurs:
The type of the Expression is an unchecked exception class (§11.1.1) or the null type (§4.1).
The throw
statement is contained in the try
block of a try
statement (§14.20) and it is not the case that the
try
statement can throw an exception of the type of the
Expression. (In this case we say the thrown value
is caught by the try
statement.)
The 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.6,
§8.8.5) of the declaration.
The
exception types that a throw
statement can throw are specified in
§11.2.2.
A throw
statement first evaluates the Expression. Then:
If 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 non-null
value V
, then the throw
statement completes abruptly, the reason being a throw
with
value V
.
If evaluation of the Expression completes normally, producing
a null
value, then an instance V'
of class
NullPointerException
is created and thrown instead of null
. The throw
statement then 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.20) 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 or a lambda expression,
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 that invoked the constructor will
complete abruptly because of the throw
(§15.9.4).
If a
throw
statement is contained in a static initializer
(§8.7), then a compile-time check
(§11.2.3) ensures that either its value is always
an unchecked exception or its value is always caught by some try
statement that contains it. If at run time, 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).
If a
throw
statement is contained in an instance initializer
(§8.6), then a compile-time check
(§11.2.3) ensures that either its value is always
an unchecked exception or its value is always caught by some try
statement that contains it, or the type of the thrown exception (or
one of its superclasses) occurs in the throws
clause of every
constructor of the class.
By convention, user-declared throwable types should
usually be declared to be subclasses of class Exception
, which is a
subclass of class Throwable
(§11.1.1).
A
synchronized
statement acquires a mutual-exclusion lock
(§17.1) 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.
The type of Expression must be a reference type, or a compile-time error occurs.
A synchronized
statement is executed by first evaluating the
Expression. Then:
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 monitor associated
with V
. Then the Block is executed, and
then there is a choice:
The locks
acquired by synchronized
statements are the same as the locks that
are acquired implicitly by synchronized
methods
(§8.4.3.6). A single thread
may acquire a lock more than once.
Acquiring
the lock associated with an object does not in itself prevent other
threads from accessing fields of the object or invoking
un-synchronized
methods on the object. Other threads can also use
synchronized
methods or the synchronized
statement in a
conventional manner to achieve mutual exclusion.
Example 14.19-1. The synchronized
Statement
class Test { public static void main(String[] args) { Test t = new Test(); synchronized(t) { synchronized(t) { System.out.println("made it!"); } } } }
This program produces the output:
made it!
Note that this program would deadlock if a single thread were not permitted to lock a monitor more than once.
A try
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.
See §8.3 for UnannClassType. The following productions from §4.3, §8.3, and §8.4.1 are shown here for convenience:
The Block immediately after
the keyword 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 declares
exactly one parameter, which is called an exception
parameter.
It is a compile-time error if final
appears more than once as a
modifier for an exception parameter declaration.
The scope and shadowing of an exception parameter is specified in §6.3 and §6.4.
References to an exception parameter from a nested class or interface, or a lambda expression, are restricted, as specified in §6.5.6.1.
An exception parameter may denote its type as either a single class
type or a union of two or more class types (called
alternatives). The alternatives of a union are
syntactically separated by |
.
A catch
clause whose exception parameter is denoted as a single
class type is called a uni-catch
clause.
A catch
clause whose exception parameter is denoted as a union of
types is called a multi-catch
clause.
Each class type used in the denotation of the type of an exception
parameter must be the class Throwable
or a subclass of Throwable
, or a compile-time error occurs.
It is a compile-time error if a type variable is used in the denotation of the type of an exception parameter.
It is a compile-time error if a union of types contains two alternatives Di and Dj (i ≠ j) where Di is a subtype of Dj (§4.10.2).
The declared type of an exception parameter that denotes its type with a single class type is that class type.
The declared type of an exception parameter that denotes its type
as a union with alternatives D1 |
D2 |
... |
Dn
is lub(D1, D2, ..., Dn).
An exception parameter of a multi-catch
clause is implicitly
declared final
if it is not explicitly declared final
.
It is a compile-time error if an exception parameter that is
implicitly or explicitly declared final
is
assigned to within the body of the catch
clause.
An exception parameter of a uni-catch
clause is never implicitly
declared final
, but it may be explicitly declared final
or be
effectively final (§4.12.4).
An implicitly final
exception parameter is final
by virtue of its declaration, while an effectively final exception
parameter is (as it were) final by virtue of how it is used. An
exception parameter of a multi-catch
clause is implicitly declared
final
, so will never occur as the left-hand operand of an assignment
operator, but it is not considered effectively
final.
If an exception parameter is effectively final (in a uni-catch
clause) or implicitly final (in a multi-catch
clause), then adding
an explicit final
modifier to its declaration will not introduce any
compile-time errors. On the other hand, if the exception parameter of
a uni-catch
clause is explicitly declared final
, then removing the
final
modifier may introduce compile-time errors because the
exception parameter, now considered to be effectively final, can no
longer longer be referenced by anonymous and local class declarations
in the body of the catch
clause. If there are no compile-time
errors, it is possible to further change the program so that the
exception parameter is re-assigned in the body of the catch
clause
and thus will no longer be considered effectively final.
The exception types that a try
statement can throw are specified in
§11.2.2.
The relationship of the exceptions thrown by the try
block of a
try
statement and caught by the catch
clauses (if any) of the
try
statement is specified in §11.2.3.
Exception handlers are considered in left-to-right order: the earliest
possible catch
clause accepts the exception, receiving as its
argument the thrown exception object, as specified in §11.3.
A multi-catch
clause can be thought of as a sequence of uni-catch
clauses. That is, a catch
clause where the type of the exception
parameter is denoted as a union D1|
D2|
...|
Dn is
equivalent to a sequence of n catch
clauses where the types
of the exception parameters are class types D1, D2, ..., Dn
respectively. In the Block of each of the n catch
clauses,
the declared type of the exception parameter is lub(D1, D2, ..., Dn).
For example, the following code:
try { ... throws ReflectiveOperationException ... } catch (ClassNotFoundException | IllegalAccessException ex) { ... body ... }
is semantically equivalent to the following code:
try { ... throws ReflectiveOperationException ... } catch (final ClassNotFoundException ex1) { final ReflectiveOperationException ex = ex1; ... body ... } catch (final IllegalAccessException ex2) { final ReflectiveOperationException ex = ex2; ... body ... }
where the multi-catch
clause with two alternatives has been
translated into two uni-catch
clauses, one for each alternative. A
Java compiler is neither required nor recommended to compile a
multi-catch
clause by duplicating code in this manner, since it is
possible to represent the multi-catch
clause in a class
file
without duplication.
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
(§14.20.1, §14.20.2).
A try
statement is permitted to omit catch
clauses and a finally
clause if it is a try
-with-resources statement
(§14.20.3).
A try
statement without a finally
block is executed by first
executing the try
block. Then there is a choice:
If execution of the try
block completes normally, then no
further action is taken and the try
statement completes
normally.
If execution of the try
block completes abruptly because of a
throw
of a value V
, then there is a
choice:
If the run-time type of V
is assignment
compatible with (§5.2) a catchable
exception class of any 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, and then there is a
choice:
If the run-time type of V
is not
assignment compatible with a catchable exception class of
any catch
clause of the try
statement, then the try
statement completes abruptly because of a throw
of the
value V
.
If execution of the try
block completes abruptly for any other
reason, then the try
statement completes abruptly for the same
reason.
Example 14.20.1-1. Catching An Exception
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("Caught RuntimeException"); } catch (BlewIt b) { System.out.println("Caught BlewIt"); } } }
Here, 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:
Caught BlewIt
A try
statement with a finally
block is executed by first
executing the try
block. Then there is a choice:
If execution of the try
block completes normally, then the
finally
block is executed, and then there is a choice:
If execution of the try
block completes abruptly because of a
throw
of a value V
, then there is a
choice:
If the run-time type of V
is assignment
compatible with a catchable exception class of any 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:
If the run-time type
of V
is not assignment compatible with a
catchable exception class of any catch
clause of the try
statement, then the finally
block is executed. Then there is
a choice:
If execution of the try
block completes abruptly for any other
reason R
, then the finally
block is
executed, and then there is a choice:
Example 14.20.2-1. Handling An Uncaught Exception With finally
class BlewIt extends Exception { BlewIt() { } BlewIt(String s) { super(s); } } class Test { static void blowUp() throws BlewIt { throw new NullPointerException(); } public static void main(String[] args) { try { blowUp(); } catch (BlewIt b) { System.out.println("Caught BlewIt"); } finally { System.out.println("Uncaught Exception"); } } }
This program produces the output:
Uncaught Exception Exception in thread "main" 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, which
typically results in printing the exception name and a simple
backtrace. However, a backtrace is not required by this
specification.
The problem with mandating a backtrace is that an exception can be created at one point in the program and thrown at a later one. It is prohibitively expensive to store a stack trace in an exception unless it is actually thrown (in which case the trace may be generated while unwinding the stack). Hence we do not mandate a back trace in every exception.
A try
-with-resources statement is parameterized with variables
(known as resources) that are initialized before
execution of the try
block and closed automatically, in the reverse
order from which they were initialized, after execution of the try
block. catch
clauses and a finally
clause are often unnecessary
when resources are closed automatically.
The following productions from §4.3, §8.3, §8.4.1, and §14.4 are shown here for convenience:
See §8.3 for UnannType.
The resource specification denotes the resources
of the try
-with-resources statement, either by declaring local
variables with initializer expressions or by referring to existing
variables. An existing variable is referred to by an expression name
(§6.5.6) or a field access expression (§15.11).
The rules for a local variable declared in a resource specification are specified in §14.4. In addition, all of the following must be true, or a compile-time error occurs:
The scope and shadowing of a local variable declared in a resource specification is specified in §6.3 and §6.4.
References to the local variable from a nested class or interface, or a lambda expression, are restricted, as specified in §6.5.6.1.
The type of a local variable declared in a resource specification is specified in §14.4.1.
The type of a local variable declared in a resource specification,
or the type of an existing variable referred to in a resource specification,
must be a subtype of AutoCloseable
, or a compile-time error occurs.
It is a compile-time error for a resource specification to declare two local variables with the same name.
Note that a resource specification may declare more than one unnamed local variable (§6.1).
A local variable declared in a resource specification is implicitly
declared final
if it is not explicitly declared final
(§4.12.4).
An existing variable referred to in a resource specification
must be a final
or effectively final
variable that is definitely
assigned before the try
-with-resources statement
(§16 (Definite Assignment)), or a compile-time error occurs.
Resources are initialized in left-to-right order. If a resource fails
to initialize (that is, its initializer expression throws an
exception), then all resources initialized so far by the
try
-with-resources statement are closed. If all resources initialize
successfully, the try
block executes as normal and then all non-null
resources of the try
-with-resources statement are closed.
Resources are closed in the reverse order from that in which they were
initialized. A resource is closed only if it initialized to a non-null
value. An exception from the closing of one resource does not prevent
the closing of other resources. Such an exception
is suppressed if an exception was thrown
previously by an initializer, the try
block, or the closing of a
resource.
A try
-with-resources statement whose resource specification
indicates multiple resources is treated as if it were multiple
try
-with-resources statements, each of which has a resource
specification that indicates a single resource. When a
try
-with-resources statement with n resources (n > 1)
is translated, the result is a try
-with-resources statement with
n-1 resources. After n such translations, there are n
nested try
-catch
-finally
statements, and the overall translation
is complete.
A try
-with-resources statement with no catch
clauses or finally
clause is called a basic try
-with-resources
statement.
If a basic try
-with-resources statement is of the form:
try (VariableAccess ...)
Block
then the resource is first converted to a local variable declaration by the following translation:
try (T #r = VariableAccess ...) {
Block
}
T
is the type of the variable denoted by
VariableAccess and #r
is an
automatically generated identifier that is distinct from any other
identifiers (automatically generated or otherwise) that are in scope
at the point where the try
-with-resources statement occurs. The
try
-with-resources statement is then translated according to the
rest of this section.
The meaning of a basic try
-with-resources statement of the form:
try ({VariableModifier} R VariableDeclaratorId = Expression ...) Block
is given by the following translation to a local variable declaration
and a try
-catch
-finally
statement:
{ final {VariableModifierNoFinal} R Identifier = Expression; Throwable #primaryExc = null; try ResourceSpecification_tail Block catch (Throwable #t) { #primaryExc = #t; throw #t; } finally { if (Identifier != null) { if (#primaryExc != null) { try { Identifier.close(); } catch (Throwable #suppressedExc) { #primaryExc.addSuppressed(#suppressedExc); } } else { Identifier.close(); } } } }
{VariableModifierNoFinal} is defined
as {VariableModifier} without final
, if
present.
If VariableDeclaratorId is an identifier then
Identifier is defined to be that identifier, otherwise
Identifier is defined to be an automatically generated
identifier that is distinct from any other identifiers (automatically generated
or otherwise) that are in scope at the point where the try
-with-resources
statement occurs.
#t
, #primaryExc
, and
#suppressedExc
are automatically generated
identifiers that are distinct from any other identifiers
(automatically generated or otherwise) that are in scope at the point
where the try
-with-resources statement occurs.
If the resource specification indicates one resource,
then ResourceSpecification_tail is empty (and the
try
-catch
-finally
statement is not itself a try
-with-resources
statement).
If the resource specification indicates n > 1 resources,
then ResourceSpecification_tail consists of the
2nd, 3rd, ..., n'th resources indicated in the resource
specification, in the same order (and the try
-catch
-finally
statement is itself a try
-with-resources statement).
Reachability and definite assignment rules for the basic
try
-with-resources statement are implicitly specified by the
translation above.
In a basic try
-with-resources statement that manages a single
resource:
If the initialization of the resource completes abruptly because
of a throw
of a value V
, then the
try
-with-resources statement completes abruptly because of a
throw
of the value V
.
If the initialization of the resource completes normally, and
the try
block completes abruptly because of a throw
of a
value V
, then:
If the automatic closing of the resource completes normally,
then the try
-with-resources statement completes abruptly
because of a throw
of the value V
.
If the automatic closing of the resource completes abruptly
because of a throw
of a value V2
, then
the try
-with-resources statement completes abruptly
because of a throw
of the value V
,
with V2
added to the suppressed exception
list of V
.
If the initialization of the resource completes normally, and
the try
block completes normally, and the automatic closing of
the resource completes abruptly because of a throw
of a
value V
, then the try
-with-resources
statement completes abruptly because of a throw
of the
value V
.
In a basic try
-with-resources statement that manages multiple
resources:
If the initialization of a resource completes abruptly because
of a throw
of a value V
, then:
If the automatic closings of all successfully initialized
resources (possibly zero) complete normally, then the
try
-with-resources statement completes abruptly because of
a throw
of the value V
.
If the automatic closings of all successfully initialized
resources (possibly zero) complete abruptly because of
throw
s of
values V1
...Vn
, then
the try
-with-resources statement completes abruptly
because of a throw
of the value V
, with
any remaining
values V1
...Vn
added
to the suppressed exception list
of V
.
If the initialization of all resources completes normally, and
the try
block completes abruptly because of a throw
of a
value V
, then:
If the automatic closings of all initialized resources
complete normally, then the try
-with-resources statement
completes abruptly because of a throw
of the
value V
.
If the automatic closings of one or more initialized
resources complete abruptly because of throw
s of
values V1
...Vn
, then
the try
-with-resources statement completes abruptly
because of a throw
of the value V
, with
any remaining
values V1
...Vn
added
to the suppressed exception list
of V
.
If the initialization of every resource completes normally, and
the try
block completes normally, then:
If one automatic closing of an initialized resource
completes abruptly because of a throw
of
value V
, and all other automatic closings
of initialized resources complete normally, then the
try
-with-resources statement completes abruptly because of
a throw
of the value V
.
If more than one automatic closing of an initialized
resource completes abruptly because of throw
s of
values V1
...Vn
(where V1
is the exception from the
rightmost resource failing to close
and Vn
is the exception from the leftmost
resource failing to close), then
the try
-with-resources statement completes abruptly
because of a throw
of the value V1
, with
any remaining
values V2
...Vn
added
to the suppressed exception list
of V1
.
A try
-with-resources
statement with at least one catch
clause and/or a finally
clause
is called an extended try
-with-resources
statement.
The
meaning of an extended try
-with-resources statement:
try ResourceSpecification Block [Catches] [Finally]
is
given by the following translation to a basic try
-with-resources
statement nested inside a try
-catch
or try
-finally
or
try
-catch
-finally
statement:
try { try ResourceSpecification Block } [Catches] [Finally]
The
effect of the translation is to put the resource specification
"inside" the try
statement. This allows a catch
clause of an
extended try
-with-resources statement to catch an exception due to
the automatic initialization or closing of any resource.
Furthermore, all resources will have been closed (or attempted to be
closed) by the time the finally
block is executed, in keeping with
the intent of the finally
keyword.
A yield
statement transfers control by causing an enclosing
switch
expression (§15.28) to produce
a specified value.
A yield
statement attempts to transfer control to the innermost
enclosing switch
expression; this enclosing expression, which is
called the yield target, then immediately
completes normally and the value of the Expression becomes the
value of the switch
expression.
It is a compile-time error if a yield
statement has no yield target.
It is a compile-time error if the yield target contains any
method, constructor, instance initializer, static initializer,
or lambda expression that encloses the yield
statement.
That is, there are no non-local jumps.
It is a compile-time error if the Expression of a yield
statement
is void (§15.1).
Execution of a yield
statement first evaluates the Expression.
If the evaluation of the Expression completes abruptly for some reason,
then the yield
statement completes abruptly for that reason.
If evaluation of the Expression completes normally,
producing a value V
, then the yield
statement
completes abruptly, the reason being a yield with
value V
.
It can be seen, then, that a yield
statement
always completes abruptly.
Example 14.21-1. The yield
Statement
In the following example, a yield
statement is used
to produce a value for the enclosing switch
expression.
class Test { enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } public int calculate(Day d) { return switch (d) { case SATURDAY, SUNDAY -> d.ordinal(); default -> { int len = d.toString().length(); yield len*len; } }; } }
It is a compile-time error if a statement cannot be executed because it is unreachable.
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, instance
initializer, 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) k = 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.
The rules in this section define two technical terms:
The rules allow a statement to complete normally only if it is reachable.
Two further technical terms are used:
A reachable break
statement exits a statement
if, within the break target, either there are no try
statements
whose try
blocks contain the break
statement, or there are try
statements whose try
blocks contain the break
statement and all
finally
clauses of those try
statements can complete
normally.
This definition is based on the logic around "attempts to transfer control" in §14.15.
A continue
statement continues a do
statement
if, within the do
statement, either there are no try
statements
whose try
blocks contain the continue
statement, or there are
try
statements whose try
blocks contain the continue
statement
and all finally
clauses of those try
statements can complete
normally.
The block that is the body of a constructor, method,
instance initializer, static initializer, lambda expression,
or switch
expression is reachable.
An empty block that is not a switch block can complete normally iff it is reachable.
A non-empty block that is not a switch block can complete normally iff the last statement in it can complete normally.
The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable.
Every other statement S
in a non-empty block
that is not a switch block is reachable iff the statement
preceding S
can complete normally.
A local class declaration statement can complete normally iff it is reachable.
A local variable declaration statement can complete normally iff it is reachable.
An empty statement can complete normally iff it is reachable.
A labeled statement can complete normally if at least one of the following is true:
The contained statement is reachable iff the labeled statement is reachable.
An expression statement can complete normally iff it is reachable.
An if
-then
statement can complete normally iff
it is reachable.
The then
-statement is reachable iff the if
-then
statement is reachable.
An 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.
This handling of an if
statement, whether or
not it has an else
part, is rather unusual. The rationale is
given at the end of this section.
An assert
statement can complete normally iff it is reachable.
A switch
statement whose switch block is empty,
or contains only switch labels, can complete normally.
A switch
statement whose switch block consists of
switch labeled statement groups can complete normally iff
at least one of the following is true:
The last statement in the switch block can complete normally.
There is at least one switch label after the last switch block statement group.
There is a reachable break
statement that exits the
switch
statement.
The switch
statement is not enhanced (§14.11.2) and
its switch block does not contain a default
label.
A switch
statement whose switch block consists of
switch rules can complete normally iff
at least one of the following is true:
One of the switch rules introduces a switch rule expression (which is necessarily a statement expression).
One of the switch rules introduces a switch rule block that can complete normally.
One of the switch rules introduces a switch rule block
that contains a reachable break
statement which exits
the switch
statement.
The switch
statement is not enhanced (§14.11.2) and its
switch block does not contain a default
label.
A switch block is reachable iff its switch
statement is
reachable.
A statement in a switch block that consists of switch labeled statement groups is reachable iff the switch block is reachable and at least one of the following is true:
A switch rule block in a switch block is reachable iff the switch block is reachable.
A switch rule throw
statement in a switch block is reachable
iff the switch block is reachable.
A while
statement can complete normally iff at least
one of the following is true:
The while
statement is reachable and the
condition expression is not a constant expression
(§15.29) with value true
.
There is a reachable break
statement that exits
the while
statement.
The contained statement is reachable iff the while
statement
is reachable and the condition expression is not a constant
expression whose value is false
.
A do
statement can complete normally iff at least one of
the following is true:
The contained statement can complete normally and
the condition expression is not a constant expression
(§15.29) with value true
.
The do
statement contains a reachable continue
statement
with no label, and the do
statement is the innermost while
,
do
, or for
statement that contains that continue
statement, and the continue
statement continues
that do
statement, and the condition expression is
not a constant expression with value true
.
The do
statement contains a reachable continue
statement
with label L
, and the do
statement has
label L
, and the continue
statement continues that do
statement, and the
condition expression is not a constant expression with value
true
.
There is a reachable break
statement that exits the do
statement.
The contained statement is reachable iff the do
statement
is reachable.
A basic for
statement can complete normally iff at least
one of the following is true:
The for
statement is reachable, there is a condition
expression, and the condition expression is not a
constant expression (§15.29)
with value true
.
There is a reachable break
statement that exits the
for
statement.
The contained statement is reachable iff the for
statement
is reachable and the condition expression is not a constant
expression whose value is false
.
An enhanced for
statement can complete normally iff it
is reachable.
A break
, continue
, return
, throw
, or yield
statement
cannot complete normally.
A synchronized
statement can complete normally iff the
contained statement can complete normally.
The contained statement is reachable iff the synchronized
statement is reachable.
A try
statement can complete normally iff both of the
following are true:
The try
block is reachable iff the try
statement is reachable.
A catch
block C
is reachable iff both of
the following are true:
Either the type of C
's
parameter is an unchecked exception type or Exception
or
a superclass of Exception
, or some expression
or throw
statement in the try
block is reachable and can
throw a checked exception whose
type is assignment compatible (§5.2)
with the type of C
's parameter.
(An expression is reachable iff the innermost statement
containing it is reachable.)
See §15.6 for normal and abrupt completion of expressions.
There is no earlier 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.
The Block of a catch
block is reachable iff the catch
block is reachable.
If a finally
block is present, it is reachable iff the
try
statement is reachable.
One might expect the if
statement to be handled in the following manner:
An if
-then
statement can
complete normally iff at least one of the following is
true:
The if
-then
statement is reachable and
the condition expression is not a constant expression whose
value is true
.
The then
-statement can complete
normally.
The then
-statement is reachable iff the
if
-then
statement is reachable and the condition expression is
not a constant expression whose value is false
.
An 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
.
This approach would be consistent with the treatment
of other control structures. However, in order to allow the if
statement to be used conveniently for "conditional compilation"
purposes, the actual rules differ.
As an example, the following statement results in a compile-time error:
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.
Conditional compilation comes with a caveat. If a set of classes that
use a "flag" variable - or more precisely, any static
constant
variable (§4.12.4) - 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. The classes that use the flag will not see its new value, so
their behavior may be surprising. In essence, a change to the value of
a flag is binary compatible with pre-existing binaries (no LinkageError
occurs) but not behaviorally compatible.
Another reason for "inlining" values of static
constant variables is because of switch
statements. They are the
only kind of statement that relies on constant expressions, namely
that each case
label of a switch
statement must be a constant
expression whose value is different than every other case
label. case
labels are often references to static
constant
variables so it may not be immediately obvious that all the labels
have different values. If it is proven that there are no duplicate
labels at compile time, then inlining the values into the class
file
ensures there are no duplicate labels at run time either - a very
desirable property.
Example 14.22-1. Conditional Compilation
If the example:
class Flags { static final boolean DEBUG = true; } class Test { public static void main(String[] args) { if (Flags.DEBUG) System.out.println("DEBUG is true"); } }
is compiled and executed, it produces the output:
DEBUG is true
Suppose that a new version of
class Flags
is produced:
class Flags { static final boolean DEBUG = false; }
If Flags
is recompiled but
not Test
, then running the new binary with the
existing binary of Test
produces the output:
DEBUG is true
because DEBUG
is a static
constant variable, so its value could have been used in
compiling Test
without making a reference to the
class Flags
.
This behavior would also occur
if Flags
was an interface, as in the modified
example:
interface Flags { boolean DEBUG = true; } class Test { public static void main(String[] args) { if (Flags.DEBUG) System.out.println("DEBUG is true"); } }
In fact, because the fields of interfaces are always
static
and final
, we recommend that only constant expressions be assigned to
fields of interfaces. We note, but do not recommend, that if a field
of primitive type of an interface may change, its value may be
expressed idiomatically as in:
interface Flags { boolean debug = Boolean.valueOf(true).booleanValue(); }
ensuring that this value is not a constant expression. Similar idioms exist for the other primitive types.
A pattern describes a test that can be performed on a value. Patterns appear as operands of statements and expressions, which provide the values to be tested. Patterns declare zero or more local variables, also known as pattern variables.
The process of testing a value against a pattern is known as pattern matching. If a value successfully matches a pattern, then the process of pattern matching initializes the pattern variables, if any, declared by the pattern.
Pattern variables are only in scope (§6.3) where pattern matching succeeds and thus the pattern variables will have been initialized. It is not possible to use a pattern variable that has not been initialized.
A type pattern is used to test whether a value is an instance of the type appearing in the pattern. A record pattern is used to test whether a value is an instance of a record class type and, if it is, to recursively perform pattern matching on the record component values.
The following productions from §4.3, §8.3, §8.4.1, and §14.4 are shown here for convenience:
See §8.3 for UnannType.
A pattern is nested in a record pattern if (1) it appears directly in the component pattern list of the record pattern, or (2) it is nested in a record pattern that appears directly in the component pattern list of the record pattern. A pattern is top level if it is not nested in a record pattern.
A type pattern declares one local variable, known as a pattern variable. If the declaration includes an identifier then this specifies the name of the pattern variable, otherwise the pattern variable is called an unnamed pattern variable.
The rules for a local variable declared in a type pattern are specified in §14.4. In addition, all of the following must be true, or a compile-time error occurs:
The type of a pattern variable declared in a top level type pattern is the reference type denoted by LocalVariableType.
The type of a pattern variable declared in a nested type pattern is determined as follows:
If the LocalVariableType is UnannType then the type of the pattern variable is denoted by UnannType.
If the LocalVariableType is var
then the type pattern must appear
directly in the component pattern list of a record pattern, or a compile-time error
occurs.
Let R be the type of the record pattern, and let T be the type of the corresponding component field in R (§8.10.3). The type of the pattern variable is the upward projection of T with respect to all synthetic type variables mentioned by T.
Consider the following declaration of a record class:
record R<T>(ArrayList<T> a){}
Given the record pattern R
<String
>(var
b)
, the type of the pattern variable b
is
ArrayList
<String
>.
A type pattern is said to be null matching if it is appears directly in the component pattern list of a record pattern with type R, where the corresponding record component of R has type U, and the type pattern is unconditional for the type U (§14.30.3).
Note that this compile-time property of type patterns is used in the run-time process of pattern matching (§14.30.2), so it is associated with the type pattern for use at run time.
A record pattern consists of a ReferenceType and a component pattern list containing component patterns, if any. If ReferenceType is not a record class type (§8.10) then a compile-time error occurs.
If the ReferenceType is a raw type, then the type of the record pattern is inferred, as described in §18.5.5. It is a compile-time error if no type can be inferred for the record pattern.
If the ReferenceType (or any part of it) is annotated then a compile-time error occurs.
Future versions of the Java Programming Language may lift this restriction on annotations.
Otherwise, the type of the record pattern is ReferenceType.
The length of the record pattern's component pattern list must be the same as the length of the record component list in the declaration of the record class named by ReferenceType otherwise a compile-time error occurs.
A record pattern does not directly declare any pattern variables itself, but may contain declarations of pattern variables in the component pattern list.
It is a compile-time error if a record pattern contains more than one declaration of a pattern variable with the same name.
The match-all pattern is a special pattern that declares no
pattern variables and can only appear directly in the component pattern list of
a record pattern r
.
Let R be the type of the record pattern r
, and let T be the type of the
corresponding component field in R (§8.10.3). The
type of the match-all pattern is the upward projection of T with respect to
all synthetic type variables mentioned by T.
It can be seen that a match-all pattern is equivalent to a nested type pattern
that declares an unnamed pattern variable and whose
LocalVariableType is var
.
Pattern matching is the process of testing a value against a pattern at run time. Pattern matching is distinct from statement execution (§14.1) and expression evaluation (§15.1). If a value successfully matches a pattern, then the process of pattern matching will initialize all the pattern variables declared by the pattern, if any.
The process of pattern matching may involve expression evaluation or statement
execution. Accordingly, pattern matching is said to complete
abruptly if evaluation of an expression or execution of a statement
completes abruptly. An abrupt completion always has an associated reason, which
is always a throw
with a given value. Pattern matching is said to
complete normally if it does not complete abruptly.
The rules for determining whether a value matches a pattern, and for initializing pattern variables, are as follows:
The null reference matches a type pattern if the type pattern is null-matching (§14.30.1); and does not match otherwise.
If the null reference matches, then the pattern variable declared by the type pattern is initialized to the null reference.
If the null reference does not match, then the pattern variable declared by the type pattern is not initialized.
A value v
that is not the null reference
matches a type pattern of type T if v
can be
converted by testing conversion (§5.7) to the target
type T without raising a ClassCastException
; and does not match
otherwise.
If v
matches, then the pattern variable declared by the type
pattern is initialized to v
.
If v
does not match, then the pattern variable declared
by the type pattern is not initialized.
The null reference does not match a record pattern.
In this case, any pattern variables appearing in declarations contained in the record pattern are not initialized.
A value v
that is not the null reference matches a
record pattern with type R and component pattern list L
if (i) v
can be
converted by testing conversion (§5.7) to the target
type R without raising a ClassCastException
; and (ii) each record component of v
matches the corresponding component pattern in L
; and does not
match otherwise.
Each record component of v
is determined by invoking the accessor method
of v
corresponding to that component. If execution of the invocation of
the accessor method completes abruptly for reason S, then pattern matching
completes abruptly by throwing a MatchException
with cause S.
A pattern variable declared by a pattern appearing in the component pattern list of a record pattern is initialized only if all the patterns in the list match.
A pattern p
is said to be applicable at a type T if one
of the following rules apply:
A type pattern that declares a pattern variable of a reference type U is applicable at a reference type T if there is a testing conversion (§5.7) from type T to type U.
A type pattern that declares a pattern variable of a primitive type P is applicable at the type P.
A record pattern with type R and pattern list L
is applicable at type
T if (i) there is a testing conversion (§5.7) from
type T to type R, and (ii) for every component pattern p
appearing in
L
, if any, p
is applicable at the type of the corresponding component
field in R.
A pattern p
is said to be unconditional for a type T if
it can be determined at compile time that every value of type T will match
p
, and so the run time, testing aspect of pattern matching could be elided. It
is defined as follows:
Note that no record pattern is unconditional because the null reference does not match any record pattern.
A pattern p
is said to dominate another pattern q
if
every value that matches q
also matches p
, and is defined as follows:
A pattern p
dominates a type pattern that declares a pattern variable of
type T if p
is unconditional for T.
A pattern p
dominates a record pattern with type R if p
is
unconditional for R.
A record pattern with type R and component pattern list L
dominates another record
pattern with type S and component pattern list M
if (i) R and S name the same
record class, and (ii) every component pattern, if any, in L
dominates the
corresponding component pattern in M
.
A pattern p
dominates a match-all pattern with type T if p
is
unconditional for T.