Chapter 14. Blocks, Statements, and Patterns
Table of Contents
- 14.1. Normal and Abrupt Completion of Statements
- 14.2. Blocks
- 14.3. Local Class and Interface Declarations
- 14.4. Local Variable Declarations
- 14.5. Statements
- 14.6. The Empty Statement
- 14.7. Labeled Statements
- 14.8. Expression Statements
- 14.9. The
ifStatement - 14.10. The
assertStatement - 14.11. The
switchStatement - 14.12. The
whileStatement - 14.13. The
doStatement - 14.14. The
forStatement - 14.15. The
breakStatement - 14.16. The
continueStatement - 14.17. The
returnStatement - 14.18. The
throwStatement - 14.19. The
synchronizedStatement - 14.20. The
trystatement - 14.21. The
yieldStatement - 14.22. Unreachable Statements
- 14.30. Patterns
The 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, andreturnstatements (§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:
-
A
breakwith no label -
A
breakwith a given label -
A
continuewith no label -
A
continuewith a given label -
A
returnwith no value -
A
returnwith a given value -
A
throwwith a given value, including exceptions thrown by the Java Virtual Machine -
A
yieldwith a given value
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:
-
More than one VariableDeclarator is listed.
-
The VariableDeclaratorId has one or more bracket pairs.
-
The VariableDeclarator lacks an initializer.
-
The initializer of the VariableDeclarator is an ArrayInitializer.
-
The initializer of the VariableDeclarator contains a reference to the variable.
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.
An empty statement does nothing.
; Execution of an empty statement always completes normally.
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:
-
If the value is
true, then the contained Statement is executed; theif-thenstatement completes normally if and only if execution of the Statement completes normally. -
If the value is
false, no further action is taken and theif-thenstatement completes normally.
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 theelsekeyword) is executed; theif-then-elsestatement 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 theelsekeyword) is executed; theif-then-elsestatement 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:
-
If the value is
true, no further action is taken and theassertstatement completes normally. -
If the value is
false, the execution behavior depends on whether Expression2 is present:-
If Expression2 is present, it is evaluated. Then:
-
If the evaluation completes abruptly for some reason, the
assertstatement completes abruptly for the same reason. -
If the evaluation completes normally, an
AssertionErrorinstance whose "detail message" is the resulting value of Expression2 is created. Then:-
If the instance creation completes abruptly for some reason, the
assertstatement completes abruptly for the same reason. -
If the instance creation completes normally, the
assertstatement completes abruptly by throwing the newly createdAssertionErrorobject.
-
-
-
If Expression2 is not present, an
AssertionErrorinstance with no "detail message" is created. Then:-
If the instance creation completes abruptly for some reason, the
assertstatement completes abruptly for the same reason. -
If the instance creation completes normally, the
assertstatement completes abruptly by throwing the newly createdAssertionErrorobject.
-
-
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 when Expression A switch block can consist of either:
-
Switch rules, which use
->to introduce either a switch rule expression, a switch rule block, or a switch rulethrowstatement; or -
Switch labeled statement groups, which use
:to introduce switch labeled block statements.
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:
-
No two of the
caseconstants associated with a switch block may have the same value. -
No more than one
nullliteral may be associated with a switch block. -
No more than one
defaultlabel may be associated with a switch block.
A guard associated with a case label must satisfy all of the following conditions, otherwise a compile-time error occurs:
-
A guard must have type
booleanorBoolean. -
Any local variable, formal parameter, or exception parameter used but not declared in a guard must either be
finalor effectively final (§4.12.4). -
Any blank
finalvariable 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
nullliteral is associated with the switch block, then T is a reference type. -
For every
caseconstant associated with the switch block that names an enum constant, the type of thecaseconstant is assignment compatible with T (§5.2). -
For each
caseconstant associated with the switch block that is a constant expression, the constant is assignment compatible with T, and T is one ofchar,byte,short,int,Character,Byte,Short,Integer, orString. -
Every pattern
passociated 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
caselabel with acasepatternqis dominated if there is a preceding unguardedcaselabel in the switch block with acasepatternp, andpdominatesq(§14.30.3).The definition of one pattern dominating another pattern is based on types. For example, the type pattern
Objectodominates the type patternStrings, 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
caselabel with acasepattern is dominated by acaselabel 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
caselabel with acasepattern is not considered to dominate an unguardedcaselabel with the samecasepattern. 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
caselabel with more than onecasepattern is dominated if any one of these patterns is dominated by a pattern that appears as acasepattern in a preceding unguardedcaselabel, and so the following results in a compile-time error (as the type patternInteger_is dominated by the type patternNumber_):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
caselabel with acaseconstantcis dominated if one of the following holds:-
cis a constant expression of a primitive type S, and there is a precedingcaselabel in the switch block with an unguardedcasepatternp, wherepis unconditional for the wrapper class of S. -
cis a constant expression of a reference type T, and there is a precedingcaselabel in the switch block with an unguardedcasepatternp, wherepis unconditional for the type T. -
cnames an enum constant of enum class E, and there is a precedingcaselabel in the switch block with an unguardedcasepatternp, wherepis unconditional for the type E.
For example, a
caselabel with anIntegertype pattern dominates acaselabel 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
defaultlabel or acasenull,defaultlabel is dominated if there is a preceding unguardedcaselabel in the switch block with acasepatternpwherepis unconditional for the type of the selector expression (§14.30.3).A
caselabel with acasepattern that is unconditional for the type of the selector expression will, as the name suggests, match every value and so behave like adefaultlabel. A switch block can not have more than one switch label that acts like adefault.
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:
-
There is a
defaultlabel in the switch block that precedes acaselabel withcasepatterns. -
There is a
defaultlabel in the switch block that precedes acaselabel with anullliteral. -
There is a
casenull,defaultlabel in the switch block followed by any other switch label.
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 statement is labeled with more than one switch label.
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
defaultlabel associated with the switch block. -
There is a
casenull,defaultlabel associated with the switch block. -
The set containing all the
caseconstants andcasepatterns appearing in an unguardedcaselabel (collectively known ascaseelements) associated with the switch block is non-empty and covers the type of the selector expressione.
A set of case elements, P, covers a type T if one of the following cases applies:
-
P covers a type U where T and U have the same erasure.
-
P contains a pattern that is unconditional for T.
-
T is a type variable with upper bound B and P covers B.
-
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
defaultlabel is permitted, but not required, in the case where the names of all the enum constants appear ascaseconstants. 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
abstractsealedclass orsealedinterface C and for every permitted direct subclass or subinterface D of C, one of the following two conditions holds:-
There is no type that both names D and is a subtype of T, or
-
There is a type U that both names D and is a subtype of T, and P covers U.
A
defaultlabel is permitted, but not required, in the case where the switch block exhausts all the permitted direct subclasses and subinterfaces of anabstractsealedclass orsealedinterface. 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
casepatterns that match against all values of typesA,BandC, and no other instances of typeIare permitted, this switch block is exhaustive.The fact that a permitted direct subclass or subinterface may only extend a particular parameterization of a generic
sealedsuperclass 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 subclassDneed not be considered as there is no possibility that the value ofjican be an instance ofD. -
-
The type T names a record class R, and P contains a record pattern
pwith 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 ofpcovers 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 patternp.A non-empty set of patterns, RP, reduces to a single pattern rp if one of the following holds:
-
RP covers some type U, and rp is a type pattern of type U.
-
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 componentci(1≤i≤k, i≠r) the set containing the component patterns from the record patterns corresponding to componentciis equivalent to a single patternqi, the set containing the component patterns from the record patterns corresponding to the componentcrreduces to a single patternq, and rp is the record pattern of type R with a pattern list consisting of the patternsq1, ...,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 patternsq1,...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
caselabel with anullliteral applies. -
If the value is not the null reference, then we determine the first (if any)
caselabel in the switch block that applies to the value as follows:-
A
caselabel with acaseconstantcapplies to a value of typeCharacter,Byte,Short, orInteger, if the value is first subjected to unboxing conversion (§5.1.8) and the constantcis 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
caselabel with acaseconstantcapplies to a value that is of typechar,byte,short,int, orStringor an enum type if the constantcis equal to the value.Equality is defined in terms of the
==operator unless the value is aString, in which case equality is defined in terms of theequalsmethod of classString. -
Determining that a
caselabel withcasepatternsp1, ...,pn(n≥1) applies to a value proceeds by finding the first (if any)casepatternpi(1≤i≤n) that applies to the value.Determining that a
casepattern 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
caselabel is unguarded then thiscasepattern applies. -
If pattern matching succeeds and the
caselabel is guarded, then the guard is evaluated. If the result is of typeBoolean, 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
truethen thecasepattern applies.
-
-
A
casenull,defaultlabel applies to every value.
-
-
If the value is not the null reference, and no
caselabel applies according to the rules of step 2, but there is adefaultlabel associated with the switch block, then thedefaultlabel 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).
switchstatements differ fromswitchexpressions 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 aswitchstatement, only a statement expression may be used as a switch rule expression, but in aswitchexpression, any expression may be used (§15.28.1). -
If the
switchstatement is an enhancedswitchstatement, 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
switchstatement 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 aNullPointerExceptionis thrown and the entireswitchstatement completes abruptly for that reason. -
If the
switchstatement is an enhancedswitchstatement, then aMatchExceptionis thrown and the entireswitchstatement completes abruptly for that reason. -
If the value of the selector expression is not
null, and theswitchstatement is not an enhancedswitchstatement, then the entireswitchstatement 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
switchstatement 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
switchstatement completes normally. -
If it is the switch label for a switch rule
throwstatement, then thethrowstatement 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
switchstatement completes normally. -
Otherwise, there are no statements in the switch block that follow the switch label that applies, and the
switchstatement 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
breakwith no label, then no further action is taken and theswitchstatement completes normally.Abrupt completion because of a
breakwith 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
switchstatement completes abruptly for the same reason.Abrupt completion because of a
yieldstatement 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
whilestatement 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 thewhilestatement completes normally.If the (possibly unboxed) value of the Expression is
falsethe 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
breakwith no label, no further action is taken and thewhilestatement completes normally. -
If execution of the Statement completes abruptly because of a
continuewith no label, then the entirewhilestatement is executed again. -
If execution of the Statement completes abruptly because of a
continuewith labelL, then there is a choice:-
If the
whilestatement has labelL, then the entirewhilestatement is executed again. -
If the
whilestatement does not have labelL, thewhilestatement completes abruptly because of acontinuewith labelL.
-
-
If execution of the Statement completes abruptly for any other reason, the
whilestatement completes abruptly for the same reason.The case of abrupt completion because of a
breakwith 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
dostatement completes abruptly for the same reason.Otherwise, there is a choice based on the resulting value:
-
If the value is
true, then the entiredostatement is executed again. -
If the value is
false, no further action is taken and thedostatement completes normally.
-
-
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
breakwith no label, then no further action is taken and thedostatement completes normally. -
If execution of the Statement completes abruptly because of a
continuewith no label, then the Expression is evaluated. Then there is a choice based on the resulting value:-
If the value is
true, then the entiredostatement is executed again. -
If the value is
false, no further action is taken and thedostatement completes normally.
-
-
If execution of the Statement completes abruptly because of a
continuewith labelL, then there is a choice:-
If the
dostatement has labelL, then the Expression is evaluated. Then there is a choice:-
If the value of the Expression is
true, then the entiredostatement is executed again. -
If the value of the Expression is
false, no further action is taken and thedostatement completes normally.
-
-
If the
dostatement does not have labelL, thedostatement completes abruptly because of acontinuewith labelL.
-
-
If execution of the Statement completes abruptly for any other reason, the
dostatement completes abruptly for the same reason.The case of abrupt completion because of a
breakwith 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
forstatement. -
The enhanced
forstatement
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
forstatement 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
forstatement completes abruptly for the same reason. -
If the ForInit part is not present, no action is taken.
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
forstatement 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
forstatement completes abruptly for the same reason; any ForUpdate statement expressions to the right of the one that completed abruptly are not evaluated.If the ForUpdate part is not present, no action is taken.
-
Second, another
foriteration step is performed.
-
-
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 theforstatement completes normally.If the (possibly unboxed) value of the Expression is
falsethe 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
breakwith no label, no further action is taken and theforstatement completes normally. -
If execution of the Statement completes abruptly because of a
continuewith no label, 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 the ForUpdate part is not present, no action is taken.
-
Second, another
foriteration step is performed.
-
-
If execution of the Statement completes abruptly because of a
continuewith labelL, then there is a choice:-
If the
forstatement has labelL, 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 the ForUpdate is not present, no action is taken.
-
Second, another
foriteration step is performed.
-
-
If the
forstatement does not have labelL, theforstatement completes abruptly because of acontinuewith labelL.
-
-
If execution of the Statement completes abruptly for any other reason, the
forstatement completes abruptly for the same reason.Note that the case of abrupt completion because of a
breakwith 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 VariableDeclaratorList consists of a single VariableDeclarator.
-
The VariableDeclarator has no initializer.
-
The VariableDeclaratorId has no bracket pairs if the LocalVariableType is
var.
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:-
If the Expression has an array type, then R is the component type of the array type.
-
Otherwise, if the Expression has a type that is a subtype of
Iterable<X>, for some type X, then R is X. -
Otherwise, the Expression has a type that is a subtype of the raw type
Iterable, and R isObject.
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 basicforstatement has this form:for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} T VarDeclId = (TargetType) #i.next(); Statement }
where:
-
If the type of Expression is a subtype of
Iterable<X>for some type argument X, then I is the typejava.util.Iterator<X>. Otherwise, I is the raw typejava.util.Iterator. -
#iis 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 enhancedforstatement occurs. -
{VariableModifier} is as given in the header of the enhanced
forstatement. -
T is the type of the local variable as determined above.
-
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
Objectif 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 basicforstatement has this form:S
[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} T VarDeclId = #a[#i]; Statement }where:
-
L1...Lmis the (possibly empty) sequence of labels immediately preceding the enhancedforstatement. -
#aand#iare automatically generated identifiers that are distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the enhancedforstatement occurs. -
{VariableModifier} is as given in the header of the enhanced
forstatement. -
T is the type of the local variable as determined above.
-
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
breakstatement with no label. -
A
breakstatement with the label Identifier.
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
continuestatement with no label. -
A
continuestatement with the label Identifier.
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
returnstatement with no value. -
A
returnstatement with value Expression.
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
throwstatement is contained in thetryblock of atrystatement (§14.20) and it is not the case that thetrystatement can throw an exception of the type of the Expression. (In this case we say the thrown value is caught by thetrystatement.) -
The
throwstatement 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 thethrowsclause (§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
throwcompletes abruptly for that reason. -
If evaluation of the Expression completes normally, producing a non-
nullvalueV, then thethrowstatement completes abruptly, the reason being athrowwith valueV. -
If evaluation of the Expression completes normally, producing a
nullvalue, then an instanceV'of classNullPointerExceptionis created and thrown instead ofnull. Thethrowstatement then completes abruptly, the reason being athrowwith valueV'.
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
synchronizedstatement completes abruptly for the same reason. -
Otherwise, if the value of the Expression is
null, aNullPointerExceptionis thrown. -
Otherwise, let the non-
nullvalue of the Expression beV. The executing thread locks the monitor associated withV. Then the Block is executed, and then there is a choice:-
If execution of the Block completes normally, then the monitor is unlocked and the
synchronizedstatement completes normally. -
If execution of the Block completes abruptly for any reason, then the monitor is unlocked and the
synchronizedstatement completes abruptly for the same reason.
-
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
tryblock completes normally, then no further action is taken and thetrystatement completes normally. -
If execution of the
tryblock completes abruptly because of athrowof a valueV, then there is a choice:-
If the run-time type of
Vis assignment compatible with (§5.2) a catchable exception class of anycatchclause of thetrystatement, then the first (leftmost) suchcatchclause is selected. The valueVis assigned to the parameter of the selectedcatchclause, and the Block of thatcatchclause is executed, and then there is a choice:-
If that block completes normally, then the
trystatement completes normally. -
If that block completes abruptly for any reason, then the
trystatement completes abruptly for the same reason.
-
-
If the run-time type of
Vis not assignment compatible with a catchable exception class of anycatchclause of thetrystatement, then thetrystatement completes abruptly because of athrowof the valueV.
-
-
If execution of the
tryblock completes abruptly for any other reason, then thetrystatement 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
tryblock completes normally, then thefinallyblock is executed, and then there is a choice:-
If the
finallyblock completes normally, then thetrystatement completes normally. -
If the
finallyblock completes abruptly for reasonS, then thetrystatement completes abruptly for reasonS.
-
-
If execution of the
tryblock completes abruptly because of athrowof a valueV, then there is a choice:-
If the run-time type of
Vis assignment compatible with a catchable exception class of anycatchclause of thetrystatement, then the first (leftmost) suchcatchclause is selected. The valueVis assigned to the parameter of the selectedcatchclause, and the Block of thatcatchclause is executed. Then there is a choice:-
If the
catchblock completes normally, then thefinallyblock is executed. Then there is a choice:-
If the
finallyblock completes normally, then thetrystatement completes normally. -
If the
finallyblock completes abruptly for any reason, then thetrystatement completes abruptly for the same reason.
-
-
If the
catchblock completes abruptly for reasonR, then thefinallyblock is executed. Then there is a choice:-
If the
finallyblock completes normally, then thetrystatement completes abruptly for reasonR. -
If the
finallyblock completes abruptly for reasonS, then thetrystatement completes abruptly for reasonS(and reasonRis discarded).
-
-
-
If the run-time type of
Vis not assignment compatible with a catchable exception class of anycatchclause of thetrystatement, then thefinallyblock is executed. Then there is a choice:-
If the
finallyblock completes normally, then thetrystatement completes abruptly because of athrowof the valueV. -
If the
finallyblock completes abruptly for reasonS, then thetrystatement completes abruptly for reasonS(and thethrowof valueVis discarded and forgotten).
-
-
-
If execution of the
tryblock completes abruptly for any other reasonR, then thefinallyblock is executed, and then there is a choice:-
If the
finallyblock completes normally, then thetrystatement completes abruptly for reasonR. -
If the
finallyblock completes abruptly for reasonS, then thetrystatement completes abruptly for reasonS(and reasonRis discarded).
-
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 VariableDeclaratorList consists of a single VariableDeclarator.
-
The VariableDeclarator has an initializer.
-
The VariableDeclaratorId has no bracket pairs.
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).
Resources are final, in that:
-
A local variable declared in a resource specification is implicitly declared
finalif it is not explicitly declaredfinal(§4.12.4). -
An existing variable referred to in a resource specification must be a
finalor effectivelyfinalvariable that is definitely assigned before thetry-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
throwof a valueV, then thetry-with-resources statement completes abruptly because of athrowof the valueV. -
If the initialization of the resource completes normally, and the
tryblock completes abruptly because of athrowof a valueV, then:-
If the automatic closing of the resource completes normally, then the
try-with-resources statement completes abruptly because of athrowof the valueV. -
If the automatic closing of the resource completes abruptly because of a
throwof a valueV2, then thetry-with-resources statement completes abruptly because of athrowof the valueV, withV2added to the suppressed exception list ofV.
-
-
If the initialization of the resource completes normally, and the
tryblock completes normally, and the automatic closing of the resource completes abruptly because of athrowof a valueV, then thetry-with-resources statement completes abruptly because of athrowof the valueV.
In a basic try-with-resources statement that manages multiple resources:
-
If the initialization of a resource completes abruptly because of a
throwof a valueV, then:-
If the automatic closings of all successfully initialized resources (possibly zero) complete normally, then the
try-with-resources statement completes abruptly because of athrowof the valueV. -
If the automatic closings of all successfully initialized resources (possibly zero) complete abruptly because of
throws of valuesV1...Vn, then thetry-with-resources statement completes abruptly because of athrowof the valueV, with any remaining valuesV1...Vnadded to the suppressed exception list ofV.
-
-
If the initialization of all resources completes normally, and the
tryblock completes abruptly because of athrowof a valueV, then:-
If the automatic closings of all initialized resources complete normally, then the
try-with-resources statement completes abruptly because of athrowof the valueV. -
If the automatic closings of one or more initialized resources complete abruptly because of
throws of valuesV1...Vn, then thetry-with-resources statement completes abruptly because of athrowof the valueV, with any remaining valuesV1...Vnadded to the suppressed exception list ofV.
-
-
If the initialization of every resource completes normally, and the
tryblock completes normally, then:-
If one automatic closing of an initialized resource completes abruptly because of a
throwof valueV, and all other automatic closings of initialized resources complete normally, then thetry-with-resources statement completes abruptly because of athrowof the valueV. -
If more than one automatic closing of an initialized resource completes abruptly because of
throws of valuesV1...Vn(whereV1is the exception from the rightmost resource failing to close andVnis the exception from the leftmost resource failing to close), then thetry-with-resources statement completes abruptly because of athrowof the valueV1, with any remaining valuesV2...Vnadded to the suppressed exception list ofV1.
-
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:
-
whether a statement is reachable
-
whether a statement can complete normally
The rules allow a statement to complete normally only if it is reachable.
Two further technical terms are used:
-
A reachable
breakstatement exits a statement if, within the break target, either there are notrystatements whosetryblocks contain thebreakstatement, or there aretrystatements whosetryblocks contain thebreakstatement and allfinallyclauses of thosetrystatements can complete normally.This definition is based on the logic around "attempts to transfer control" in §14.15.
-
A
continuestatement continues adostatement if, within thedostatement, either there are notrystatements whosetryblocks contain thecontinuestatement, or there aretrystatements whosetryblocks contain thecontinuestatement and allfinallyclauses of thosetrystatements can complete normally.
The rules are as follows:
-
The block that is the body of a constructor, method, instance initializer, static initializer, lambda expression, or
switchexpression 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
Sin a non-empty block that is not a switch block is reachable iff the statement precedingScan 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 can complete normally.
-
There is a reachable
breakstatement that exits the labeled statement.
The contained statement is reachable iff the labeled statement is reachable.
-
-
An expression statement can complete normally iff it is reachable.
-
An
if-thenstatement can complete normally iff it is reachable.The
then-statement is reachable iff theif-thenstatement is reachable.An
if-then-elsestatement can complete normally iff thethen-statement can complete normally or theelse-statement can complete normally.The
then-statement is reachable iff theif-then-elsestatement is reachable.The
else-statement is reachable iff theif-then-elsestatement is reachable.This handling of an
ifstatement, whether or not it has anelsepart, is rather unusual. The rationale is given at the end of this section. -
An
assertstatement can complete normally iff it is reachable. -
A
switchstatement whose switch block is empty, or contains only switch labels, can complete normally. -
A
switchstatement 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
breakstatement that exits theswitchstatement. -
The
switchstatement is not enhanced (§14.11.2) and its switch block does not contain adefaultlabel.
-
-
A
switchstatement 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
breakstatement which exits theswitchstatement. -
The
switchstatement is not enhanced (§14.11.2) and its switch block does not contain adefaultlabel.
-
-
A switch block is reachable iff its
switchstatement 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:
-
It bears a
caseordefaultlabel. -
There is a statement preceding it in the switch block and that preceding statement can complete normally.
-
-
A switch rule block in a switch block is reachable iff the switch block is reachable.
-
A switch rule
throwstatement in a switch block is reachable iff the switch block is reachable. -
A
whilestatement can complete normally iff at least one of the following is true:-
The
whilestatement is reachable and the condition expression is not a constant expression (§15.29) with valuetrue. -
There is a reachable
breakstatement that exits thewhilestatement.
The contained statement is reachable iff the
whilestatement is reachable and the condition expression is not a constant expression whose value isfalse. -
-
A
dostatement 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
dostatement contains a reachablecontinuestatement with no label, and thedostatement is the innermostwhile,do, orforstatement that contains thatcontinuestatement, and thecontinuestatement continues thatdostatement, and the condition expression is not a constant expression with valuetrue. -
The
dostatement contains a reachablecontinuestatement with labelL, and thedostatement has labelL, and thecontinuestatement continues thatdostatement, and the condition expression is not a constant expression with valuetrue. -
There is a reachable
breakstatement that exits thedostatement.
The contained statement is reachable iff the
dostatement is reachable. -
-
A basic
forstatement can complete normally iff at least one of the following is true:-
The
forstatement is reachable, there is a condition expression, and the condition expression is not a constant expression (§15.29) with valuetrue. -
There is a reachable
breakstatement that exits theforstatement.
The contained statement is reachable iff the
forstatement is reachable and the condition expression is not a constant expression whose value isfalse. -
-
An enhanced
forstatement can complete normally iff it is reachable. -
A
break,continue,return,throw, oryieldstatement cannot complete normally. -
A
synchronizedstatement can complete normally iff the contained statement can complete normally.The contained statement is reachable iff the
synchronizedstatement is reachable. -
A
trystatement can complete normally iff both of the following are true:-
The
tryblock can complete normally or anycatchblock can complete normally. -
If the
trystatement has afinallyblock, then thefinallyblock can complete normally.
-
-
The
tryblock is reachable iff thetrystatement is reachable. -
A
catchblockCis reachable iff both of the following are true:-
Either the type of
C's parameter is an unchecked exception type orExceptionor a superclass ofException, or some expression orthrowstatement in thetryblock is reachable and can throw a checked exception whose type is assignment compatible (§5.2) with the type ofC'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
catchblockAin thetrystatement such that the type ofC's parameter is the same as, or a subclass of, the type ofA's parameter.
-
-
The Block of a
catchblock is reachable iff thecatchblock is reachable. -
If a
finallyblock is present, it is reachable iff thetrystatement is reachable.
One might expect the if statement to be handled in the following manner:
-
An
if-thenstatement can complete normally iff at least one of the following is true:-
The
if-thenstatement is reachable and the condition expression is not a constant expression whose value istrue. -
The
then-statement can complete normally.
The
then-statement is reachable iff theif-thenstatement is reachable and the condition expression is not a constant expression whose value isfalse. -
-
An
if-then-elsestatement can complete normally iff thethen-statement can complete normally or theelse-statement can complete normally.The
then-statement is reachable iff theif-then-elsestatement is reachable and the condition expression is not a constant expression whose value isfalse.The
else-statement is reachable iff theif-then-elsestatement is reachable and the condition expression is not a constant expression whose value istrue.
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 LocalVariableType in a top level type pattern denotes a reference type (and furthermore is not
var). -
The VariableDeclaratorList consists of a single VariableDeclarator.
-
The VariableDeclarator has no initializer.
-
The VariableDeclaratorId has no bracket pairs.
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
varthen 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 variablebisArrayList<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
vthat is not the null reference matches a type pattern of type T ifvcan be converted by testing conversion (§5.7) to the target type T without raising aClassCastException; and does not match otherwise.If
vmatches, then the pattern variable declared by the type pattern is initialized tov.If
vdoes 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
vthat is not the null reference matches a record pattern with type R and component pattern listLif (i)vcan be converted by testing conversion (§5.7) to the target type R without raising aClassCastException; and (ii) each record component ofvmatches the corresponding component pattern inL; and does not match otherwise.Each record component of
vis determined by invoking the accessor method ofvcorresponding to that component. If execution of the invocation of the accessor method completes abruptly for reason S, then pattern matching completes abruptly by throwing aMatchExceptionwith 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.
-
Every value matches a match-all pattern.
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
Lis applicable at type T if (i) there is a testing conversion (§5.7) from type T to type R, and (ii) for every component patternpappearing inL, if any,pis applicable at the type of the corresponding component field in R. -
A match-all pattern is applicable at every type T.
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:
-
A type pattern that declares a pattern variable of a reference type S is unconditional for a reference type T if the erasure of T is a subtype of the erasure of S.
-
A type pattern that declares a pattern variable of a primitive type P is unconditional for the type P.
-
A match-all pattern is unconditional for every type T.
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
pdominates a type pattern that declares a pattern variable of type T ifpis unconditional for T. -
A pattern
pdominates a record pattern with type R ifpis unconditional for R. -
A record pattern with type R and component pattern list
Ldominates another record pattern with type S and component pattern listMif (i) R and S name the same record class, and (ii) every component pattern, if any, inLdominates the corresponding component pattern inM. -
A pattern
pdominates a match-all pattern with type T ifpis unconditional for T.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.