This document describes changes to the Java Language Specification
to support Pattern Matching for switch and
Record Patterns, which are both preview features of Java SE 20.
See JEP 433 and JEP 432 respectively for
overviews of the features.
Changes are described with respect to existing sections of the JLS.
New text is indicated like this and deleted text is
indicated like this. Explanation and discussion, as needed,
is set aside in grey boxes.
Changelog:
2022-11-15:
A
switchexpression over an enum class now throwsMatchException, rather thanIncompatibleClassChangeError, if no switch label applies at runtime.Improvements to example of inference of type arguments for record patterns.
2022-10-28: Added details of inference of type arguments for record patterns.
2022-10-18: First draft released. Main changes from the third preview specification, in addition to various bug-fixes, are:
- A simplified grammar for switch labels.
- New support for record patterns to appear in the header of an
enhanced
forstatement. - Named record patterns have been removed.
- The test determining whether a switch block is exhaustive has been strengthened.
Chapter 3: Lexical Structure
3.9 Keywords
51 character sequences, formed from ASCII characters, are reserved for use as keywords and cannot be used as identifiers (3.8). Another 16 character sequences, also formed from ASCII characters, may be interpreted as keywords or as other tokens, depending on the context in which they appear.
- Keyword:
- ReservedKeyword
- ContextualKeyword
- ReservedKeyword:
- (one of)
-
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
_(underscore) - ContextualKeyword:
- (one of)
-
exports permits to with
module provides transitive yield
non-sealed record uses
open requires var
opens sealedwhen
The keywords
constandgotoare reserved, even though they are not currently used. This may allow a Java compiler to produce better error messages if these C++ keywords incorrectly appear in programs.
The keyword
strictfpis obsolete and should not be used in new code.
The keyword
_(underscore) is reserved for possible future use in parameter declarations.
trueandfalseare not keywords, but rather boolean literals (3.10.3).
nullis not a keyword, but rather the null literal (3.10.8).
During the reduction of input characters to input elements (3.5), a sequence of input characters that notionally matches a contextual keyword is reduced to a contextual keyword if and only if both of the following conditions hold:
The sequence is recognized as a terminal specified in a suitable context of the syntactic grammar (2.3), as follows:
For
moduleandopen, when recognized as a terminal in a ModuleDeclaration (7.7).For
exports,opens,provides,requires,to,uses, andwith, when recognized as a terminal in a ModuleDirective.For
transitive, when recognized as a terminal in a RequiresModifier.For example, recognizing the sequence
requirestransitive;does not make use of RequiresModifier, so the termtransitiveis reduced here to an identifier and not a contextual keyword.For
var, when recognized as a terminal in a LocalVariableType (14.4) or a LambdaParameterType (15.27.1).In other contexts, attempting to use
varas an identifier will cause an error, becausevaris not a TypeIdentifier (3.8).For
yield, when recognized as a terminal in a YieldStatement (14.21).In other contexts, attempting to use the
yieldas an identifier will cause an error, becauseyieldis neither a TypeIdentifier nor a UnqualifiedMethodIdentifier.For
record, when recognized as a terminal in a RecordDeclaration (8.10).For
non-sealed,permits, andsealed, when recognized as a terminal in a NormalClassDeclaration (8.1) or a NormalInterfaceDeclaration (9.1).For
when, when recognized as a terminal in a Guard (14.11.1).
The sequence is not immediately preceded or immediately followed by an input character that matches JavaLetterOrDigit.
In general, accidentally omitting white space in source code will cause a sequence of input characters to be tokenized as an identifier, due to the "longest possible translation" rule (3.2). For example, the sequence of twelve input characters
p u b l i c s t a t i cis always tokenized as the identifierpublicstatic, rather than as the reserved keywordspublicandstatic. If two tokens are intended, they must be separated by white space or a comment.
The rule above works in tandem with the "longest possible translation" rule to produce an intuitive result in contexts where contextual keywords may appear. For example, the sequence of eleven input characters
v a r f i l e n a m eis usually tokenized as the identifiervarfilename, but in a local variable declaration, the first three input characters are tentatively recognized as the contextual keywordvarby the first condition of the rule above. However, it would be confusing to overlook the lack of white space in the sequence by recognizing the next eight input characters as the identifierfilename. (This would mean that the sequence undergoes different tokenization in different contexts: an identifier in most contexts, but a contextual keyword and an identifier in local variable declarations.) Accordingly, the second condition prevents recognition of the contextual keywordvaron the grounds that the immediately following input characterfis a JavaLetterOrDigit. The sequencev a r f i l e n a m eis therefore tokenized as the identifiervarfilenamein a local variable declaration.
As another example of the careful recognition of contextual keywords, consider the sequence of 15 input characters
n o n - s e a l e d c l a s s. This sequence is usually translated to three tokens - the identifiernon, the operator-, and the identifiersealedclass- but in a normal class declaration, where the first condition holds, the first ten input characters are tentatively recognized as the contextual keywordnon-sealed. To avoid translating the sequence to two keyword tokens (non-sealedandclass) rather than three non-keyword tokens, and to avoid rewarding the programmer for omitting white space beforeclass, the second condition prevents recognition of the contextual keyword. The sequencen o n - s e a l e d c l a s sis therefore tokenized as three tokens in a class declaration.
In the rule above, the first condition depends on details of the syntactic grammar, but a compiler for the Java programming language can implement the rule without fully parsing the input program. For example, a heuristic could be used to track the contextual state of the tokenizer, as long as the heuristic guarantees that valid uses of contextual keywords are tokenized as keywords, and valid uses of identifiers are tokenized as identifiers. Alternatively, a compiler could always tokenize a contextual keyword as an identifier, leaving it to a later phase to recognize special uses of these identifiers.
Chapter 5: Conversions and Contexts
5.5 Casting Contexts
Casting contexts allow the operand of a cast expression (15.16) to be converted to the type explicitly named by the cast operator. Compared to assignment contexts and invocation contexts, casting contexts allow the use of more of the conversions defined in 5.1, and allow more combinations of those conversions.
If the expression is of a primitive type, then a casting context allows the use of one of the following:
an identity conversion (5.1.1)
a widening primitive conversion (5.1.2)
a narrowing primitive conversion (5.1.3)
a widening and narrowing primitive conversion (5.1.4)
a boxing conversion (5.1.7)
a boxing conversion followed by a widening reference conversion (5.1.5)
If the expression is of a reference type, then a casting context allows the use of one of the following:
an identity conversion (5.1.1)
a widening reference conversion (5.1.5)
a widening reference conversion followed by an unboxing conversion
a widening reference conversion followed by an unboxing conversion, then followed by a widening primitive conversion
a narrowing reference conversion (5.1.6)
a narrowing reference conversion followed by an unboxing conversion
an unboxing conversion (5.1.8)
an unboxing conversion followed by a widening primitive conversion
If the expression has the null type, then the expression may be cast to any reference type.
If a casting context makes use of a narrowing reference conversion
that is checked or partially unchecked (5.1.6.2,
5.1.6.3),
then a run time check will be performed on the class of the expression's
value, possibly causing a ClassCastException. Otherwise, no
run time check is performed.
If an expression can be converted to a reference type by a casting conversion other than a narrowing reference conversion which is unchecked, we say the expression (or its value) is downcast compatible with the reference type.
If an expression of reference type S is downcast compatible with another reference type T, we say that the type S is downcast convertible to type T.
The following tables enumerate which conversions are used in certain casting contexts. Each conversion is signified by a symbol:
- signifies no conversion allowed
≈ signifies identity conversion (5.1.1)
ω signifies widening primitive conversion (5.1.2)
η signifies narrowing primitive conversion (5.1.3)
ωη signifies widening and narrowing primitive conversion (5.1.4)
⇑ signifies widening reference conversion (5.1.5)
⇓ signifies narrowing reference conversion (5.1.6)
⊕ signifies boxing conversion (5.1.7)
⊗ signifies unboxing conversion (5.1.8)
In the tables, a comma between symbols indicates that a casting
context uses one conversion followed by another. The type
Object means any reference type other than the eight
wrapper classes Boolean, Byte,
Short, Character, Integer,
Long, Float, Double.
Table 5.5-A. Casting to primitive types
| To → From ↓ |
byte |
short |
char |
int |
long |
float |
double |
boolean |
|---|---|---|---|---|---|---|---|---|
byte |
≈ | ω | ωη | ω | ω | ω | ω | - |
short |
η | ≈ | η | ω | ω | ω | ω | - |
char |
η | η | ≈ | ω | ω | ω | ω | - |
int |
η | η | η | ≈ | ω | ω | ω | - |
long |
η | η | η | η | ≈ | ω | ω | - |
float |
η | η | η | η | η | ≈ | ω | - |
double |
η | η | η | η | η | η | ≈ | - |
boolean |
- | - | - | - | - | - | - | ≈ |
Byte |
⊗ | ⊗,ω | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Short |
- | ⊗ | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Character |
- | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Integer |
- | - | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | - |
Long |
- | - | - | - | ⊗ | ⊗,ω | ⊗,ω | - |
Float |
- | - | - | - | - | ⊗ | ⊗,ω | - |
Double |
- | - | - | - | - | - | ⊗ | - |
Boolean |
- | - | - | - | - | - | - | ⊗ |
Object |
⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ |
Table 5.5-B. Casting to reference types
| To → From ↓ |
Byte |
Short |
Character |
Integer |
Long |
Float |
Double |
Boolean |
Object |
|---|---|---|---|---|---|---|---|---|---|
byte |
⊕ | - | - | - | - | - | - | - | ⊕,⇑ |
short |
- | ⊕ | - | - | - | - | - | - | ⊕,⇑ |
char |
- | - | ⊕ | - | - | - | - | - | ⊕,⇑ |
int |
- | - | - | ⊕ | - | - | - | - | ⊕,⇑ |
long |
- | - | - | - | ⊕ | - | - | - | ⊕,⇑ |
float |
- | - | - | - | - | ⊕ | - | - | ⊕,⇑ |
double |
- | - | - | - | - | - | ⊕ | - | ⊕,⇑ |
boolean |
- | - | - | - | - | - | - | ⊕ | ⊕,⇑ |
Byte |
≈ | - | - | - | - | - | - | - | ⇑ |
Short |
- | ≈ | - | - | - | - | - | - | ⇑ |
Character |
- | - | ≈ | - | - | - | - | - | ⇑ |
Integer |
- | - | - | ≈ | - | - | - | - | ⇑ |
Long |
- | - | - | - | ≈ | - | - | - | ⇑ |
Float |
- | - | - | - | - | ≈ | - | - | ⇑ |
Double |
- | - | - | - | - | - | ≈ | - | ⇑ |
Boolean |
- | - | - | - | - | - | - | ≈ | ⇑ |
Object |
⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ≈ |
Example 5.5-1. Casting for Reference Types
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}
class Test {
public static void main(String[] args) {
Point p = new Point();
ColoredPoint cp = new ColoredPoint();
Colorable c;
// The following may cause errors at run time because
// we cannot be sure they will succeed; this possibility
// is suggested by the casts:
cp = (ColoredPoint)p; // p might not reference an
// object which is a ColoredPoint
// or a subclass of ColoredPoint
c = (Colorable)p; // p might not be Colorable
// The following are incorrect at compile time because
// they can never succeed as explained in the text:
Long l = (Long)p; // compile-time error #1
EndPoint e = new EndPoint();
c = (Colorable)e; // compile-time error #2
}
}
Here, the first compile-time error occurs because the class types
Long and Point are unrelated (that is, they
are not the same, and neither is a subclass of the other), so a cast
between them will always fail.
The second compile-time error occurs because a variable of type
EndPoint can never reference a value that implements the
interface Colorable. This is because EndPoint
is a final type, and a variable of a final
type always holds a value of the same run-time type as its compile-time
type. Therefore, the run-time type of variable e must be
exactly the type EndPoint, and type EndPoint
does not implement Colorable.
Example 5.5-2. Casting for Array Types
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
public String toString() { return "("+x+","+y+")"; }
}
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
ColoredPoint(int x, int y, int color) {
super(x, y); setColor(color);
}
public void setColor(int color) { this.color = color; }
public String toString() {
return super.toString() + "@" + color;
}
}
class Test {
public static void main(String[] args) {
Point[] pa = new ColoredPoint[4];
pa[0] = new ColoredPoint(2, 2, 12);
pa[1] = new ColoredPoint(4, 5, 24);
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.print("cpa: {");
for (int i = 0; i < cpa.length; i++)
System.out.print((i == 0 ? " " : ", ") + cpa[i]);
System.out.println(" }");
}
}
This program compiles without errors and produces the output:
cpa: { (2,2)@12, (4,5)@24, null, null }
Example 5.5-3. Casting Incompatible Types at Run Time
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
Point[] pa = new Point[100];
// The following line will throw a ClassCastException:
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.println(cpa[0]);
int[] shortvec = new int[2];
Object o = shortvec;
// The following line will throw a ClassCastException:
Colorable c = (Colorable)o;
c.setColor(0);
}
}
This program uses casts to compile, but it throws exceptions at run time, because the types are incompatible.
Chapter 6: Names
6.3 Scope of a Declaration
6.3.1 Scope for Pattern Variables in Expressions
6.3.1.6 switch Expressions
The following rule applies rules apply to
a switch expression (15.28) with a switch block
consisting of switch rules (14.11.1):
A pattern variable introduced by a switch label is definitely matched in the associated switch rule expression, switch rule block, or switch rule
throwstatement.It is a compile-time error if any pattern variable introduced by a switch label is already in scope at the associated switch rule expression, switch rule block, or switch rule
throwstatement.
The following rules apply to a switch expression with a switch block consisting of switch labeled statement groups (14.11.1):
A pattern variable introduced by a switch label is definitely matched in all the statements of the switch labeled statement group.
It is a compile-time error if any pattern variable introduced by a switch label is already in scope at the statements of the switch labeled statement group.
- A pattern variable introduced by a statement S contained in
a switch labeled statement group
(14.11.1)is definitely matched at all the statements following S, if any, in the switch labeled statement group.
6.3.2 Scope for Pattern Variables in Statements
6.3.2.5 for Statements
The following rules apply to a basic for statement (14.14.1):
A pattern variable introduced by the condition expression when true is definitely matched at both the incrementation part and the contained statement.
It is a compile-time error if any pattern variable introduced by the condition expression when true is already in scope at the incrementation part of the contained statement.
A pattern variable is introduced by a basic
forstatement iff (i) it is introduced by the condition expression when false and (ii) the contained statement, S, does not contain a reachablebreakstatement whose break target contains S (14.15).It is a compile-time error if any pattern variable introduced by a basic
forstatement is already in scope at theforstatement.
An enhanced for statement (14.14.2) is defined by translation to a basic
for statement, so no special rules need to be provided for
it.
A property of the translation of an enhanced
forstatementfor (p : e) S, wherepis a record pattern, is that any pattern variable introduced by the patternpis definitely matched in the statementS.
6.3.2.6 switch Statements
The following rule applies rules apply to
a switch statement (14.11) with a switch block
consisting of switch rules (14.11.1):
A pattern variable introduced by a switch label is definitely matched in the associated switch rule expression, switch rule block, or switch rule
throwstatement.It is a compile-time error if any pattern variable introduced by a switch label is already in scope at the associated switch rule expression, switch rule block, or switch rule
throwstatement.
The following rules apply to a switch expression with a switch block consisting of switch labeled statement groups (14.11.1):
A pattern variable introduced by a switch label is definitely matched in all the statements of the switch labeled statement group.
It is a compile-time error if any pattern variable introduced by a switch label is already in scope at the statements of the switch labeled statement group.
- A pattern variable introduced by a labeled statement S
contained in a switch block statement group
(14.11.1)is definitely matched at all the statements following S, if any, in the switch block statement group.
6.3.3 Scope for Pattern Variables in Patterns
6.3.3.1 Record Patterns
The following rule applies to a record pattern p:
For each pattern q in the nested pattern list of p, a pattern variable declared by q is definitely matched in every record pattern component that comes after it within the list.
It is a compile-time error if a pattern variable declared by q is already in scope in a record pattern component that comes after it within the list.
This rule enforces a linearity constraint that pattern variables can only be declared at most once in a single nested pattern list. Specifying that two record components should have equal values can not be encoded directly in the pattern but should be handled in subsequent code:
Object o = ... if (o instanceof Point(int x, int y) && (x == y)) { // Not the pattern Point(int x, int x)! System.out.println("Point on the diagonal"); }
6.3.4 Scope for Pattern Variables in Switch Labels
Pattern variables can be introduced by case labels with
a case pattern, either by the pattern itself or by an
associated when expression, and are in scope for the
relevant parts of the associated switch expression (6.3.1.6) or switch statement (6.3.2.6).
The following rules apply to case labels:
A pattern variable is introduced by a
caselabel with acasepattern p if it is declared by p.A pattern variable declared by the pattern of a guarded
casepattern is definitely matched in the associatedwhenexpression.It is a compile-time error if any pattern variable declared by the pattern of a guarded
casepattern is already in scope at any associatedwhenexpression.A pattern variable is introduced by a
caselabel with a guardedcasepattern if it is introduced by the associatedwhenexpression when true (6.3.1).
Chapter 13: Binary Compatibility
13.4 Evolution of Classes
13.4.2 sealed, non-sealed,
and final Classes
13.4.2.1 sealed Classes
If a class that was freely extensible (8.1.1.2)
is changed to be declared sealed, then an
IncompatibleClassChangeError is thrown if a binary of a
pre-existing subclass of this class is loaded and is not a permitted
direct subclass of this class (8.1.6);
such a change is not recommended for widely distributed classes.
Changing a class that was declared final to be declared
sealed does not break compatibility with pre-existing
binaries.
Adding a class to the set of permitted direct subclasses of a
sealed class will not break compatibility with pre-existing
binaries.
Note that evolving a
sealedclass by adding a permitted direct subclass is considered a binary compatible change because pre-existing binaries that previously linked without error (e.g., a class file that contains an exhaustiveswitch(14.11.1)) will continue to link without error. A class file that contains an exhaustiveswitchwill not fail to link if thesealedclass that it switches over is expanded by the hierarchy's owner to have a new permitted direct subclass. The JVM is not required to perform exhaustiveness checks when linking a class file that contains an exhaustiveswitch.
The execution of an exhaustive
switchcan fail with an error (aMatchExceptionis thrown) if it encounters an instance of a permitted direct subclass that was not known at compile time (14.11.3, 15.28.2). Strictly speaking, the error is not flagging a binary incompatible change of thesealedclass, but more accurately a migration incompatible change of thesealedclass.
If a class is removed from the set of permitted direct subclasses of
a sealed class, then an
IncompatibleClassChangeError is thrown if the pre-existing
binary of the removed class is loaded.
Deleting the sealed modifier from a class that does not
have a sealed direct superclass or a sealed
direct superinterface does not break compatibility with pre-existing
binaries.
If a sealed class C did have a
sealeddirect superclass or asealeddirect superinterface, then deleting thesealedmodifier would prevent C from being recompiled, as every class with asealeddirect superclass or asealeddirect superinterface must be eitherfinal,sealed, ornon-sealed.
13.4.26 Evolution of Enum Classes
Adding or reordering enum constants in an enum class will not break compatibility with pre-existing binaries.
As with
sealedclasses (13.4.2.1), although adding an enum constant to an enum class is considered a binary compatible change, it may cause the execution of an exhaustiveswitch(14.11.1) to fail if theswitchencounters the new enum constant that was not known at compile time (14.11.3, 15.28.2).
Deleting an enum constant from an enum class will delete the
public field that corresponds to the enum constant (8.9.3).
The consequences are specified in 13.4.8.
Such a change is not recommended for widely distributed enum
classes.
In all other respects, the binary compatibility rules for enum classes are identical to those for normal classes.
13.5 Evolution of Interfaces
13.5.2 sealed and
non-sealed Interfaces
If an interface that was freely extensible (9.1.1.4)
is changed to be declared sealed, then an
IncompatibleClassChangeError is thrown if a binary of a
pre-existing subclass or subinterface of this interface is loaded and is
not a permitted direct subclass or subinterface of this interface (9.1.4);
such a change is not recommended for widely distributed classes.
Adding a class or interface to the set of permitted direct subclasses
or subinterfaces, respectively, of a sealed interface will
not break compatibility with pre-existing binaries.
As with
sealedclasses (13.4.2.1), whilst adding a permitted direct subclass or subinterface of asealedinterface is considered a binary compatible change, it may cause the execution of an exhaustiveswitch(14.11.1) to fail with an error (aMatchExceptionmay be thrown) if theswitchencounters an instance of the new permitted direct subclass or subinterface that was not known at compile time (14.11.3, 15.28.2).
If a class or interface is removed from the set of permitted direct
subclasses or subinterfaces of a sealed interface, then an
IncompatibleClassChangeError is thrown if the pre-existing
binary of the removed class or interface is loaded.
Changing an interface that was declared sealed to be
declared non-sealed does not break compatibility with
pre-existing binaries.
A
non-sealedinterface I must have asealeddirect superinterface. Deleting thenon-sealedmodifier would prevent I from being recompiled, as every interface with asealeddirect superinterface must besealedornon-sealed.
Deleting the sealed modifier from an interface that does
not have a sealed direct superinterface does not break
compatibility with pre-existing binaries.
If a sealed interface I did have a
sealeddirect superinterface, then deleting thesealedmodifier would prevent I from being recompiled, as every interface with asealeddirect superinterface must besealedornon-sealed.
Chapter 14: Blocks, Statements, and Patterns
14.11 The switch Statement
The switch statement transfers control to one of several
statements or expressions, depending on the value of an expression.
- SwitchStatement:
-
switch(Expression)SwitchBlock
The Expression is called the selector expression.
The type of the selector expression must be char,
byte, short, int,
Character, Byte, Short,
Integer, String, or an enum type (8.9),
or a compile-time error occurs.
These restrictions on the type of the selector expression are now included in the notion of a switch block being compatible with a selector expression, defined in the following section.
14.11.1 Switch Blocks
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).
- SwitchBlock:
-
{SwitchRule {SwitchRule}} -
{{SwitchBlockStatementGroup} {SwitchLabel:}} - SwitchRule:
-
SwitchLabel
->Expression; -
SwitchLabel
->Block -
SwitchLabel
->ThrowStatement - SwitchBlockStatementGroup:
-
SwitchLabel
:{ SwitchLabel:} BlockStatements - SwitchLabel:
-
caseCaseConstant {,CaseConstant} -
case null[, default] -
caseCasePattern -
default - CaseConstant:
- ConditionalExpression
- CasePattern:
- Pattern [ Guard ]
- Guard:
-
whenExpression
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; orSwitch 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 one or more case
constants. Every case constant must be either a constant
expression (15.29)
or the name of an enum constant (8.9.1),
or a compile-time error occurs.
Switch labels and their case constants are said to be
associated with the switch block. No two of the
case constants associated with a switch block may have the
same value, or a compile-time error occurs.
A case label consists of either a list of
case constants or a single case pattern.
Every case constant must be either (1) the
null literal, (2) a constant expression (15.29),
or (3) the name of an enum constant (8.9.1);
otherwise a compile-time error occurs. A single null case
constant may also be paired with the default keyword.
A case pattern may have an optional when
expression, which represents a further test on values that match the
pattern. A case pattern is said to be unguarded if
either (i) it has no when expression, or (ii) it has a
when expression that is a constant expression (15.29)
with value true; and guarded otherwise.
Switch labels and their case constants 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
defaultlabel may be associated with a switch block.
Any when expression associated with a switch block must
have type boolean or Boolean. Any variable
that is used but not declared in a when expression must be
either final or effectively final (4.12.4).
It is a compile-time error if a when expression is a
constant expression (15.29)
with the value false.
The switch block of a switch statement or a
switch expression is compatible with the type of
the selector expression, T, if both of the following are
true:
If T is not an enum type, then every
caseconstant associated with the switch block is assignment compatible with T (5.2).If T is an enum type, then every
caseconstant associated with the switch block is an enum constant of type T.
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:
A
nullliteral is associated with the switch block only if T is a reference type.A constant expression c is associated with the switch block only if T is one of
char,byte,short,int,Character,Byte,Short,Integer, orString; and only if c is assignment compatible with T (5.2).An enum constant e is associated with the switch block only if T is the enum type of E.
A pattern p is associated with the switch block only if p is applicable at type T (14.30.3).
No
caselabel supports a selector expression of typeboolean,long,float, ordouble. Switch blocks are not designed to work with 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 is said to dominate another switch label if the former applies to every value that the latter applies to. It is a compile-time error if a switch label in a switch block dominates any switch label that follows it. The rules for determining dominance are as follows:
A
caselabel with an unguardedcasepattern p dominates anothercaselabel with acasepattern q if p dominates q (14.30.3).The definition of one pattern dominating another pattern (14.30.3) is based on types. For example, the following results in a compile-time error:
Object obj = ... switch (obj) { case Object o -> System.out.println("An object"); case String s -> // Error - dominated case label System.out.println("A string"); }More precisely, pattern dominance is defined in terms of the erasure of types. For example, the type pattern
ArrayList<? extends Number> aldominates the type patternArrayList<Number> alnand vice versa. The following results in a compile-time error:List<Number> l = ...; switch (l) { case ArrayList<Number> al -> System.out.println("An ArrayList of Number"); case ArrayList<? extends Number> aln -> // Error - dominated case label System.out.println("An ArrayList of Number"); default -> System.out.println("A List"); }A
caselabel with a guardedcasepattern 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 - dominated case label System.out.println("Two character string"); ... }On the other hand, a
caselabel with a guardedcasepattern p is not considered to dominate acaselabel with an unguardedcasepattern p. 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
whenexpression is a constant expression that has the valuetrue, for example:Integer j = ...; switch (j) { case Integer i when true -> // Allowed but why write this? System.out.println("An integer"); case Integer i -> // Error - dominated case label System.out.println("An integer"); }
A
caselabel with acasepattern p (guarded or unguarded) dominates anothercaselabel with acaseconstant c if one of the following is true:p is a type pattern (possibly nested within parentheses) that declares a pattern variable of type T, c is a constant expression of a primitive type S, and the wrapper class (5.1.7) of S is a subtype of the erasure of T.
p is a type pattern (possibly nested within parentheses) that declares a pattern variable of type T, c is a constant expression or an enum constant of reference type S, and S is a subtype of the erasure of T.
For example, a label with an
Integertype pattern dominates a label with anintliteral:Integer j = ...; switch (j) { case Integer i -> System.out.println("An integer"); case 42 -> // Error - dominated! System.out.println("42!"); }Analysis of
whenexpressions—undecidable in general—is not attempted. For example, the following results in a compile-time error, even though the first switch label does not match if the value of the selector expression is42:Integer j = ...; switch (j) { case Integer i when i != 42 -> System.out.println("An integer that isn't 42"); case 42 -> // Error - dominated! System.out.println("42!"); }Any
caselabels withcaseconstants should appear before those withcasepatterns; for example:Integer j = ...; switch (j) { case 42 -> System.out.println("42"); case Integer i when i < 50 -> System.out.println("An integer less than 50"); case Integer i -> System.out.println("An integer"); }
A
defaultlabel dominates acaselabel with acasepattern, and it also dominates acaselabel with anullcaseconstant.If used, a
defaultlabel should come last in aswitchblock.For historical reasons, a
defaultlabel may appear beforecaselabels with non-nullcaseconstants.int i = ...; switch(i) { default -> System.out.println("Some other integer"); case 42 -> // allowed System.out.println("42"); }This style is discouraged in new code.
A
case null, defaultlabel dominates all other switch labels.If used, a
case null, defaultlabel always comes last in aswitchblock.
A
caselabel with an unguardedcasepattern p where p is unconditional at the type of the selector expression (14.30.3) dominates adefaultlabel and acase null, defaultlabel.A
caselabel with acasepattern that is unconditional at the type of the selector expression will, as the name suggests, match every value and so behaves like adefaultlabel. A switch block can not have more than one switch label that acts like adefault.
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 pattern that declares one or more pattern variables,
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 a statement labeled by
case Integer ireachable from the preceding statement group, the pattern variableiwould 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
casepattern 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(): 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: }
14.11.1.1 Exhaustive Switch Blocks
The switch block of a switch expression or
switch statement is exhaustive for a selector
expression of type T if either (i) there is a
default label associated with the switch block, or (ii)
there is a case label with a default
associated with the switch block, or (iii) the set containing all the
case constants and unguarded case patterns
(collectively known as case elements) associated with the
switch block is non-empty and exhausts T, which is specified as
follows:
A set of
caseelements exhausts an enum class type E if it contains all of the enum constants of E.Note this means that a
defaultlabel is permitted, but not required in the case where 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! }; }A set of
caseelements exhausts a type T if it exhausts a type S and S is downcast convertible to T (5.5).If a set of
caseelements exhausts a type then it will exhaust a range of other types, in particular those types that are downcast convertible. For example, if a set exhausts the typeObjectthen it clearly also exhausts the typeString. However, if a set exhausts the typeList<Object>then it does not exhaust the typeList<String>. In contrast, a set that exhausts the typeList<? extends Object>also exhausts the typeList<String>.A set of
caseelements exhausts a type T if it contains a type pattern of type T (14.30.3).A set of
caseelements exhausts a type T that names anabstractandsealedclass or interface C, if for every permitted direct subclass or subinterface D of C, one of the following two conditions holds:D is not a generic class or interface, T is downcast convertible to D, and the set of
caseelements exhausts the type D.D is a generic class or interface, and either (i) there is no parameterized type S that names D that is downcast convertible to T, or (ii) the set of case elements exhausts a type S that names D where S is downcast convertible to T.
Note this means that a
defaultlabel is permitted, but not required in the case where the switch block exhausts all the permitted direct subclasses and subinterfaces of anabstractandsealedclass or interface. For example:sealed interface I permits A, B, C {} final class A implements I {} final class B implements I {} record C(int j) implements I {} // Implicitly final static int testExhaustive1(I i) { return switch(i) { case A a -> 0; case B b -> 1; case C c -> 2; // No default required! }; }As the switch block contains switch labels supporting patterns that match against values of type
A,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.A set of
caseelements, P, exhausts a type T that names a record class R if (i) Q is a non-empty subset of P containing only record patterns with the type T, and (ii) either the record class R has zero record components or Q is exhaustive from component c at type U, where c is the first component in the record component list of R, and U is the type of the corresponding component field in T (8.10.3).Given a record type T that names a record class R, a set of record patterns P is exhaustive from a record component c at a type U if
- the set of patterns containing the component patterns corresponding to c from every record pattern in P exhausts the type U, and (ii) if c is not the final component of the record component list of the record class R then one of the following is true:
The set Q is exhaustive from component d at type V, where d is the component following c, V is the type of corresponding component field in T, and Q is the set of patterns containing every record pattern in P whose component pattern corresponding to c is exhaustive for U.
The type U is a class or interface type that names an
abstractandsealedclass or interface D, and for every permitted direct subclass or subinterface E of D, one of the following two conditions holds:E is not a generic class or interface, U is downcast convertible to E, and P is exhaustive from component c at type E.
E is a generic class or interface, and either (i) there is no parameterized type V that names E that is downcast convertible to U, or (ii) P is exhaustive from component c at a type V that names E, where V is downcast convertible to U.
A switch statement or expression is exhaustive
if its switch block exhausts the type of the selector expression.
14.11.1.2 Executable Switch Blocks and Determining which Switch Label Applies at Run-Time
As the meaning of some patterns is determined by the type of the
expression that is being matched against, patterns must be resolved
before pattern matching can be performed (14.30.2). Consequently, any case
patterns associated with a switch block of a switch
expression or switch statement must also be resolved.
An executable switch expression or
switch statement is one where every case
pattern associated with the switch block has been resolved with respect
to the type of the selector expression, T, as follows:
- A
caselabel with acasepattern p is resolved to acaselabel with acasepattern q, where q is the result of resolving pattern p at type T (14.30.2). If p is guarded, then q is guarded with the samewhenexpression.
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 matches the value of the
selector expression. To determine whether a switch label in a switch
block matches a given value, the value is compared with the
case constants associated with the switch block. Then:
If one of the
caseconstants is equal to the value, then we say that thecaselabel which contains thecaseconstant matches.Equality is defined in terms of the
==operator (15.21) unless the value is aString, in which case equality is defined in terms of theequalsmethod of classString.If no
caselabel matches but there is adefaultlabel, then we say that thedefaultlabel matches.
Both the execution of an executable switch statement (14.11.3) and the evaluation of an executable
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, as
follows:
If the value is the null reference, then a
caselabel with anullcaseconstant 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 a non-nullcaseconstant c applies to a value of typeCharacter,Byte,Short, orInteger, if the value is first subjected to unboxing conversion (5.1.8) and the constant c is equal to the unboxed value.Any unboxing conversion must 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 a non-nullcaseconstant c applies to a value that is not of typeCharacter,Byte,Short, orInteger, if the constant c is 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 with acasepattern p applies to a value proceeds first by checking if the value matches the pattern p (14.30.2).If pattern matching completes abruptly then the process of determining which switch label applies completes abruptly for the same reason.
If pattern matching succeeds and the
casepattern is unguarded then thiscaselabel applies.If pattern matching succeeds and the
casepattern is guarded, then thewhenexpression is evaluated. If the result is of typeBoolean, it is subjected to unboxing conversion (5.1.8).If evaluation of the
whenexpression or the subsequent unboxing conversion (if any) completes abruptly for some reason, the process of determining which switch label applies completes abruptly for the same reason.Otherwise, if the resulting value is
truethen thecaselabel applies.A
case null, defaultlabel applies to every value
If the value is not the null reference, and no
caselabel applies according to the rules of step 2, then if adefaultlabel is associated with the switch block, that label applies.
A single
caselabel can contain severalcaseconstants. The labelmatchesapplies to the value of the selector expression if any one of its constantsmatchesis equal to the value of the selector expression. For example, in the following code, thecaselabel matches if the enum variabledayis either one of the enum constants shown:switch (day) { ... case SATURDAY, SUNDAY : System.out.println("It's the weekend!"); break; ... }If a switch label that supports a 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.
For historical reasons, a
defaultlabel is only considered after allcaselabels have failed to match, even if some of those labels appear after thedefaultlabel. However, subsequent labels may only make use of non-nullcase constants (14.11.1), and as a matter of style, programmers are encouraged to place theirdefaultlabels last.
nullcannot be used as acaseconstant because it is not a constant expression. Even ifcasenullwas allowed, it would be undesirable because the code in thatcasecan never be executed. Namely, if the selector expression is of a reference type (that is,Stringor a boxed primitive type or an enum type), then an exception will occur if the selector expression evaluates tonullat run time. In the judgment of the designers of the Java programming language, propagating the exception is a better outcome than either having nocaselabel match, or having thedefaultlabel match.
In C and C++ the body of a
switchstatement can be a statement and statements withcaselabels do not have to be immediately contained by that statement. Consider the simple loop:for (i = 0; i < n; ++i) foo();where
nis 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.
14.11.2 The Switch Block of a switch
Statement
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 case constant associated with the switch
block.
Namely, all All of the following must be
true for the switch block of a switch statement, or a
compile-time error occurs:
No more than onedefaultlabel is associated with theswitchblock.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.
Prior to Java SE 20,
switchstatements (andswitchexpressions) were limited in two ways: (i) the type of the selector expression was restricted to either an integral type (excludinglong), an enum type, orString; and (ii) only non-nullcaseconstants were supported. Moreover, unlikeswitchexpressions,switchstatements did not have to be exhaustive. This is often the cause of difficult-to-detect bugs, where no switch label applies and theswitchstatement 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! }With Java SE 20,
switchstatements have been enhanced in the sense that along with supportingcasepatterns, the two limitations listed above have also been lifted. The designers of the Java programming language decided that enhancedswitchstatements should align withswitchexpressions and be required to be exhaustive. This is often achieved with the addition of a trivialdefaultlabel. For example, the following enhancedswitchstatement 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,
switchstatements that are not enhancedswitchstatements are not required to be exhaustive.
14.11.3 Execution of a switch
Statement
An executable A switch
statement (14.11.1.2) is
executed by first evaluating the selector expression. Then:
If evaluation of the selector expression completes abruptly, then the
entire switch statement completes abruptly for the same
reason.
Otherwise, if the result of evaluating the selector expression is
null, then aNullPointerExceptionis thrown and the entireswitchstatement completes abruptly for that reason.Otherwise, if the result of evaluating the selector expression is of type
Character,Byte,Short, orInteger, it is subjected to unboxing conversion (5.1.8). If this conversion completes abruptly, the entireswitchstatement completes abruptly for the same reason.
If evaluation of the selector expression completes normally and
the result is non-, then execution of the
null, and the subsequent unboxing
conversion (if any) completes normallyswitch statement continues by determining if a switch label
associated with the resolved switch block
matches 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
matches, the entireapplies, then one of the following holds:switchstatement completes normally.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
matchesapplies, then one of the followingappliesholds: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 that follow the
matchedswitch label that applies in the switch block, and theswitchstatement 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 selector expression matches a switch label
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
14.14 The for Statement
14.14.2 The enhanced for
statement
The enhanced for statement has the form:
- EnhancedForStatement:
-
for(LocalVariableDeclarationEnhancedForDeclaration:Expression)
Statement - EnhancedForStatementNoShortIf:
-
for(LocalVariableDeclarationEnhancedForDeclaration:Expression)
StatementNoShortIf
- EnhancedForDeclaration:
- LocalVariableDeclaration
- RecordPattern
The following productions from 4.3, 8.3, 8.4.1,
and14.4, and 14.30 are shown here for convenience:
- LocalVariableDeclaration:
- {VariableModifier} LocalVariableType VariableDeclaratorList
- VariableModifier:
- Annotation
final- LocalVariableType:
- UnannType
var- VariableDeclaratorList:
- VariableDeclarator {
,VariableDeclarator}- VariableDeclarator:
- VariableDeclaratorId [
=VariableInitializer]- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[]{{Annotation}[]}
- RecordPattern:
- ReferenceType
([ PatternList ])- PatternList:
- Pattern {
,Pattern }
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
EnhancedForDeclaration is either (i) a local variable
declaration that declares a local variable whose name is the
identifier given by VariableDeclaratorId, or (ii) a
record pattern (14.30.1). When the
enhanced for statementfor statement is executed, then
either the local variable is initialized, on each iteration of
the loop, to successive elements of the Iterable or the
array produced by the expression, or on each iteration of the
loop, successive elements of the Iterable or the array
produced by the expression are pattern matched against the record
pattern.
The iteration type of the Expression is defined as follows:
If the Expression has an array type, then the iteration type 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 the iteration type is X.Otherwise, the Expression has a type that is a subtype of the raw type
Iterable, and the iteration type isObject.
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:
If the EnhancedForDeclaration of an enhanced
for statement is a LocalVariableDeclaration, then
both 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 isvar.
For the purposes of the compile-time rules given in 14.4,
if the EnhancedForDeclaration of an enhanced for
statement is a LocalVariableDeclaration then it is treated as
if it had an initializer whose type is the for statement's
iteration type.
These changes shift responsibility for the detailed treatment of the
local variable declaration—in particular the treatment of
var—to 14.4.
There is no substantive change in how an enhanced for with
a local variable declaration is interpreted.
If the EnhancedForDeclaration of an enhanced
for statement is a record pattern, then both of the
following must be true, or a compile-time error occurs:
The scope and shadowing of a any local
variable declared in the header
EnhancedForDeclaration of an enhanced for
statement is specified in 6.3
and 6.4.
References to the any such 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).
These rules are subsumed by the definition of "iteration type" and the assertion that the local variable declaration is treated as if it had an initializer of the iteration type.
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 Identifier = (TargetType) #i.next();Statement#Body }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 EnhancedForDeclaration of the enhanced
forstatement is a LocalVariableDeclaration, then#Bodyis the following:LocalVariableDeclaration = #i.next(); StatementThis removes the cast to TargetType. As discussed in JDK-6690688, the cast may have helped provide clarity, but was not formally necessary—any erasure-related casts are already specified as a consequence of assignment conversion (5.2). With the addition of record patterns, the extra complexity is no longer helpful.
Otherwise, the EnhancedForDeclaration of the enhanced
forstatement is a RecordPattern, and#Bodyis the following:switch(#i.next()) { case null -> throw new MatchException(new NullPointerException()); case RecordPattern -> Statement }
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 Identifier = #a[#i];Statement#Body }where:
L1 ... Lm is the (possibly empty) sequence of labels immediately preceding the enhanced
forstatement.#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 EnhancedForDeclaration of the enhanced
forstatement is a LocalVariableDeclaration, then#Bodyis the following:LocalVariableDeclaration = #a[#i]; StatementOtherwise, the EnhancedForDeclaration of the enhanced
forstatement is a RecordPattern, and#Bodyis the following:switch(#a[#i]) { case null -> throw new MatchException(new NullPointerException()); case RecordPattern -> Statement }
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(); ...for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = #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);
}
14.30 Patterns
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 may declare local variables, 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
variable 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.
14.30.1 Kinds of Patterns
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. A pattern may be parenthesized to assist in readability.
- Pattern:
- TypePattern
- RecordPattern
- ParenthesizedPattern
- TypePattern:
- LocalVariableDeclaration
- RecordPattern:
-
ReferenceType
([ PatternList ]) - PatternList :
-
Pattern {
,Pattern } - ParenthesizedPattern:
-
(Pattern)
The following productions from 4.3, 8.3, 8.4.1, and 14.4 are shown here for convenience:
- LocalVariableDeclaration:
- {VariableModifier} LocalVariableType VariableDeclaratorList
- VariableModifier:
- Annotation
final- LocalVariableType:
- UnannType
var- VariableDeclaratorList:
- VariableDeclarator {
,VariableDeclarator}- VariableDeclarator:
- VariableDeclaratorId [
=VariableInitializer]- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[]{{Annotation}[]}
See 8.3 for UnannType.
A type pattern declares one local variable, known as a pattern variable. The Identifier in the local variable declaration specifies the name of the pattern variable.
A type pattern that does not appear as an element in the nested pattern list of a record pattern is called a top-level type pattern.
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 type pattern that is not a top-level 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 pattern variable must appear in a pattern list of a record pattern with type R. Let T be the type of the corresponding component field in R. 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 record declaration:
record R<T>(ArrayList<T> r){}Given the pattern
R<String>(var r), the type of the pattern variableris thusArrayList<String>.
The type of a type pattern is the type of its pattern
variable.
A record pattern consists of a ReferenceType and a nested pattern list. 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.
Otherwise, the type of the record pattern is ReferenceType.
The length of the record pattern's nested 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.
Currently, there is no support for variable arity record patterns. This may be supported in future versions of the Java Programming Language.
A record pattern declares the local variables, if any, that are declared by the patterns in the nested pattern list.
A parenthesized pattern declares the local variables that are declared by the contained pattern.
There is also a special any pattern, which is a pattern that arises from the process of resolving a pattern (14.30.2).
Currently, no syntax exists for any patterns so they can not be used as a pattern in a pattern
instanceofexpression, or as a pattern in a switch label of aswitchexpression orswitchstatement. It is possible that future versions of the Java programming language may relax this restriction.
An any pattern declares one local variable, known as a pattern variable.
The pattern variable declared by an any pattern has a type, which is a reference type.
An expression e is compatible with a pattern of type T if e is downcast compatible with T (5.5).
Compatibility of an expression with a pattern is used by the
instanceofpattern match operator (15.20.2).
14.30.2 Pattern Matching
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.
Before pattern matching is performed, all patterns are first
resolved with respect to the type of the expression that they
are to be matched against (either the selector expression of a
switch statement or switch statement, or the
RelationalExpression of an instanceof expression),
resulting in a possibly amended pattern.
Resolving a pattern at type U is specified as follows:
- A type pattern, p, declaring a pattern variable x of type T, is resolved to an any pattern that declares x of type T if p is unconditional at U; otherwise it is resolved to p.
A record pattern p with type R and nested pattern list L is resolved to a record pattern with type R and the nested pattern list resulting from resolving, in order, each pattern in L, if any, at the type of the corresponding component field in R. type of the corresponding component field in R.
A parenthesized pattern, with contained pattern p, is resolved to a parenthesized pattern whose contained pattern is the result of resolving p at type U.
This process of resolving a pattern is to capture the correct semantics of record patterns. Consider, for example:
class Super {} class Sub extends Super {} record R(Super s) {}We expect all non-null values of type R to match the pattern
R(Super s), including the value resulting from evaluating the expressionnew R(null). (Even though the null value does not match the patternSuper s.) However, we would not expect this value to match the patternR(Subs)as the null value for the record component does not match the patternSub s.The meaning of a pattern occurring in a nested pattern list is then determined with respect to the record declaration. Resolution replaces any type patterns appearing in a nested pattern list that should match all values including null with instances of the special any pattern. In our example above, the pattern
R(Sub s)is resolved to the patternR(Sub s), whereas the patternR(Super s)is resolved to a record pattern with typeRand a nested pattern list containing an any pattern.
The process of pattern matching may involve expression evaluation or
statement execution. Accordingly, pattern matching is said to
complete abruptly if evaluation of a 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 resolved pattern, and for initializing pattern variables, are as follows:
The null reference matches an any pattern.
The pattern variable declared by the any pattern is initialized to the null reference.
A value v that is not the null reference matches an any pattern of type T if v can be cast to T without raising a
ClassCastException; and does not match otherwise.If v matches, then the pattern variable declared by the any pattern is initialized to v.
If v does not match, then the pattern variable declared by the any pattern is not initialized.
The null reference does not match a type pattern.
In this case, the pattern variable declared by the type pattern is not initialized.
A value v that is not the null reference matches a type pattern of type T if v can be cast to T without raising a
ClassCastException; and does not match otherwise.If v matches, then the pattern variable declared by the type pattern is initialized to v.
If v does not match, then the pattern variable declared by the type pattern is not initialized.
The null reference does not match a record pattern.
In this case, any pattern variables declared by the record pattern are not initialized.
A value v that is not the null reference matches a record pattern with type R and nested pattern list L if (i) v can be cast to R without raising a
ClassCastException; and (ii) each record component of v matches the corresponding pattern in L; and does not match otherwise.Each record component of v is determined by invoking the accessor method of v corresponding to that component. If execution of the invocation of the accessor method completes abruptly for reason S, then pattern matching completes abruptly by throwing a
MatchExceptionwith cause S.Any pattern variable declared in a pattern appearing in the record component pattern list is initialized only if all the patterns in the list match.
A value matches a parenthesized pattern if it matches the contained pattern; and does not match otherwise.
There is no rule to cover a value that is the null reference. This is because the solitary construct that performs pattern matching, the
instanceofpattern match operator (15.20.2), only does so when a value is not the null reference. It is possible that future versions of the Java programming language will allow pattern matching in other expressions and statements.
14.30.3 Properties of Patterns
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 another reference type T if T is downcast convertible to U (5.5).
A type pattern that declares a pattern variable of a primitive type P is applicable at the type P.
A parenthesized pattern is applicable at type T if its contained pattern is applicable at type T.
A record pattern with type R and nested pattern list L is applicable at type T if (i) T is downcast convertible to R, and (ii) for every pattern p appearing in L, if any, p is applicable at the type of the corresponding component field in R (5.5).
A pattern p is said to be unconditional at a type T if every value of type T will match p (after p has been resolved at type T (14.30.2)), and is defined as follows:
A type pattern that declares a pattern variable of a reference type S is unconditional at another 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 at the type P.
A parenthesized pattern is unconditional at a type T if its contained pattern is unconditional at T.
Note that record patterns are not unconditional at any type because the null reference does not match any record pattern.
A pattern p is said to dominate another pattern q if every value that matches q also matches p, and is defined as follows:
A pattern p dominates a type pattern that declares a pattern variable of type T if p is unconditional at T.
A pattern p dominates a parenthesized pattern with contained pattern q if p dominates q.
A pattern p dominates a record pattern with type R if p is unconditional at R.
A record pattern with type R and nested pattern list L dominates another record pattern with type S and nested pattern list M if (i) the erasure of S is a subtype of the erasure of R, and (ii) every pattern, if any, in L dominates the corresponding pattern in M.
Chapter 15: Expressions
15.20 Relational Operators
15.20.2 The instanceof Operator
An instanceof expression may perform either type
comparison or pattern matching.
- InstanceofExpression:
-
RelationalExpression
instanceofReferenceType -
RelationalExpression
instanceofPattern
If the operand to the right of the instanceof keyword is
a ReferenceType, then the instanceof keyword is
the type comparison operator.
If the operand to the right of the instanceof keyword is
a Pattern, then the instanceof keyword is the
pattern match operator.
The following rules apply when instanceof is the type
comparison operator:
The type of the expression RelationalExpression must be a reference type or the null type, or a compile-time error occurs.
The RelationalExpression must be downcast compatible with the ReferenceType (5.5), or a compile-time error occurs.
At run time, the result of the type comparison operator is determined as follows:
If the value of the RelationalExpression is the null reference (4.1), then the result is
false.If the value of the RelationalExpression is not the null reference, then the result is
trueif the value could be cast to the ReferenceType without raising aClassCastException, andfalseotherwise.
The following rules apply when instanceof is the pattern
match operator:
The type of the expression RelationalExpression must be a reference type or the null type, or a compile-time error occurs.
The RelationalExpression must be compatible with the Pattern (14.30.1), or a compile-time error occurs.The Pattern must be applicable at the type of the expression RelationalExpression (14.30.3), or a compile-time error occurs.If the type of the RelationalExpression is a subtype of the type of the Pattern, then a compile-time error occurs.Before pattern matching is performed, all patterns are first resolved (14.30.2). An executable pattern match operator is one where the Pattern has been resolved at the type of the RelationalExpression.
At run time, the result of the executable pattern match operator is determined as follows:
If the value of the RelationalExpression is the null reference, then the result is
false.If the value of the RelationalExpression is not the null reference, then the result is
trueif the value matches the Pattern (14.30.2), andfalseotherwise.A side effect of a
trueresult is that all the pattern variables declared in Pattern, if any, will be initialized.
Example 15.20.2-1. The Type Comparison Operator
class Point { int x, y; }
class Element { int atomicNumber; }
class Test {
public static void main(String[] args) {
Point p = new Point();
Element e = new Element();
if (e instanceof Point) { // compile-time error
System.out.println("I get your point!");
p = (Point)e; // compile-time error
}
}
}
This program results in two compile-time errors. The cast
(Point)e is incorrect because no instance of
Element or any of its possible subclasses (none are shown
here) could possibly be an instance of any subclass of
Point. The instanceof expression is incorrect
for exactly the same reason. If, on the other hand, the class
Point were a subclass of Element (an
admittedly strange notion in this example):
class Point extends Element { int x, y; }
then the cast would be possible, though it would require a run-time
check, and the instanceof expression would then be sensible
and valid. The cast (Point)e would never raise an exception
because it would not be executed if the value of e could
not correctly be cast to type Point.
Prior to Java SE 16, the ReferenceType operand of a type
comparison operator was required to be reifiable (4.7).
This prevented the use of a parameterized type unless all its type
arguments were wildcards. The requirement was lifted in Java SE 16 to
allow more parameterized types to be used. For example, in the following
program, it is legal to test whether the method parameter
x, with static type List<Integer>, has a
more "refined" parameterized type ArrayList<Integer>
at run time:
import java.util.ArrayList;
import java.util.List;
class Test2 {
public static void main(String[] args) {
List<Integer> x = new ArrayList<Integer>();
if (x instanceof ArrayList<Integer>) { // OK
System.out.println("ArrayList of Integers");
}
if (x instanceof ArrayList<String>) { // error
System.out.println("ArrayList of Strings");
}
if (x instanceof ArrayList<Object>) { // error
System.out.println("ArrayList of Objects");
}
}
}
The first instanceof expression is legal because there
is a casting conversion from List<Integer> to
ArrayList<Integer>. However, the second and third
instanceof expressions both cause a compile-time error
because there is no casting conversion from
List<Integer> to ArrayList<String>
or ArrayList<Object>.
15.28 switch Expressions
A switch expression transfers control to one of several
statements or expressions, depending on the value of an expression; all
possible values of that expression must be handled, and all of the
several statements and expressions must produce a value for the result
of the switch expression.
- SwitchExpression:
-
switch(Expression)SwitchBlock
The Expression is called the selector expression.
The type of the selector expression must be char,
byte, short, int,
Character, Byte, Short,
Integer, String, or an enum type (8.9),
or a compile-time error occurs.
The body of both a
switchexpression and aswitchstatement (14.11) is called a switch block. General rules which apply to all switch blocks, whether they appear inswitchexpressions orswitchstatements, are given in 14.11.1. The following productions from 14.11.1 are shown here for convenience:
- SwitchBlock:
{SwitchRule {SwitchRule}}{{SwitchBlockStatementGroup} {SwitchLabel:}}- SwitchRule:
- SwitchLabel
->Expression;- SwitchLabel
->Block- SwitchLabel
->ThrowStatement- SwitchBlockStatementGroup:
- SwitchLabel
:{ SwitchLabel:} BlockStatements
- SwitchLabel:
caseCaseConstant {,CaseConstant}default
- SwitchLabel:
caseCaseConstant {,CaseConstant }caseCasePatterncase null[, default]default
- CaseConstant:
- ConditionalExpression
- CasePattern:
- Pattern [ Guard ]
- Guard:
whenExpression
15.28.1 The Switch Block of a switch
Expression
In addition to the general rules for switch blocks (14.11.1), there are further rules for switch
blocks in switch expressions.
Namely, all of the following must be true for the switch block of a
switch expression, or a compile-time error occurs:
If the type of the selector expression is not an enum type, then there is exactly one
defaultlabel associated with the switch block.If the type of the selector expression is an enum type, then (i) the set of the
caseconstants associated with the switch block includes every enum constant of the enum type, and (ii) at most onedefaultlabel is associated with the switch block.A
defaultlabel is permitted, but not required, when thecaselabels cover all the enum constants.
If It is a compile-time error if the
switch block of a switch expression
consists of switch rules, then any switch rule block cannot
but one or more switch rule blocks can complete
normally (14.22).
If It is a compile-time error if the
switch block of a switch expression
consists of switch labeled statement groups, then
but the last statement in the switch block
cannot can complete normally, and
or the switch block does not have any
has one or more switch labels after the last switch
labeled statement group.
It is a compile-time error if a switch
expression is not exhaustive (14.11.1.1).
switchexpressions cannot have empty switch blocks, unlikeswitchstatements.
This is covered by the result expression requirement, below.
Furthermore,switchexpressions differ fromswitchstatements 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 aswitchexpression, any expression may be used as a switch rule expression, but in aswitchstatement, only a statement expression may be used (14.11.1).
The result expressions of a switch expression
are determined as follows:
If the switch block consists of switch rules, then each switch rule is considered in turn:
If the switch rule is of the form
...->Expression then Expression is a result expression of theswitchexpression.If the switch rule is of the form
...->Block then every expression which is immediately contained in ayieldstatement in Block whose yield target is the givenswitchexpression, is a result expression of theswitchexpression.
If the switch block consists of switch labeled statement groups, then every expression immediately contained in a
yieldstatement in the switch block whose yield target is the givenswitchexpression, is a result expression of theswitchexpression.
It is a compile-time error if a switch expression has no
result expressions.
A switch expression is a poly expression if it appears
in an assignment context or an invocation context (5.2,
5.3).
Otherwise, it is a standalone expression.
Where a poly switch expression appears in a context of a
particular kind with target type T, its result expressions
similarly appear in a context of the same kind with target type
T.
A poly switch expression is compatible with a target
type T if each of its result expressions is compatible with
T.
The type of a poly switch expression is the same as its
target type.
The type of a standalone switch expression is determined
as follows:
If the result expressions all have the same type (which may be the null type (4.1)), then that is the type of the
switchexpression.Otherwise, if the type of each result expression is
booleanorBoolean, then an unboxing conversion (5.1.8) is applied to each result expression of typeBoolean, and theswitchexpression has typeboolean.Otherwise, if the type of each result expression is convertible to a numeric type (5.1.8), then the type of the
switchexpression is the result of general numeric promotion (5.6) applied to the result expressions.Otherwise, boxing conversion (5.1.7) is applied to each result expression that has a primitive type, after which the type of the
switchexpression is the result of applying capture conversion (5.1.10) to the least upper bound (4.10.4) of the types of the result expressions.
15.28.2 Run-Time Evaluation of switch
Expressions
An executable A switch
expression (14.11.1.2) is
evaluated by first evaluating the selector expression. Then:
If evaluation of the selector expression completes abruptly, then
evaluation of the switch expression
completes abruptly for the same reason.
Otherwise, if the result of evaluating the selector expression is
null, then aNullPointerExceptionis thrown and the entireswitchexpression completes abruptly for that reason.Otherwise, if the result of evaluating the selector expression is of type
Character,Byte,Short, orInteger, it is subjected to unboxing conversion (5.1.8). If this conversion completes abruptly, then the entireswitchexpression completes abruptly for the same reason.
If evaluation of the selector expression completes normally and
the result is non- then evaluation of the
null, and the subsequent unboxing
conversion (if any) completes normally,switch expression continues by determining if a switch
label associated with the resolved switch block
matches 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
switchexpression completes abruptly for the same reason.If no switch label
matchesapplies,and the result of evaluating the selector expression is of an enum type then anthen one of the following holds:IncompatibleClassChangeErroris thrown and the entireswitchexpression completes abruptly for that reason.If the value of the selector expression is
null, then aNullPointerExceptionis thrown and evaluation of theswitchexpression completes abruptly for that reason.Otherwise, a
MatchExceptionis thrown and evaluation of theswitchexpression completes abruptly for that reason.
If a switch label
matchesapplies, then one of the followingappliesholds:If it is the switch label for a switch rule expression, then the expression is evaluated. If the result of evaluation is a value, then the
switchexpression completes normally with the same value.If it is the switch label for a switch rule block, then the block is executed. If this block completes normally, then the
switchexpression completes normally.If it is the switch label for a switch rule
throwstatement, then thethrowstatement is executed.Otherwise, all the statements in the switch block after the
matchingswitch label that applies are executed in order. If these statements complete normally, then theswitchexpression completes normally.
If execution of any statement or expression in the switch block completes abruptly, it is handled as follows:
If
executionevaluation of an expression completes abruptly, then evaluation of theswitchexpression completes abruptly for the same reason.If execution of a statement completes abruptly because of a
yieldwith value V, then theswitchexpression completes normally and the value of theswitchexpression is V.If execution of a statement completes abruptly for any reason other than a
yieldwith a value, then evaluation of theswitchexpression completes abruptly for the same reason.
Chapter 16: Definite Assignment
16.2 Definite Assignment and Statements
16.2.9 switch Statements
V is [un]assigned after a
switchstatement (14.11) iff all of the following are true:V is [un]assigned before every
breakstatement (14.15) that may exit theswitchstatement.For each switch rule (14.11.1) in the switch block, V is [un]assigned after the switch rule expression, switch rule block, or switch rule
throwstatement introduced by the switch rule.If there is a switch labeled statement group in the switch block, then V is [un]assigned after the last block statement of the last switch labeled statement group.
If
there is nothe switch statement is not exhaustive, or if the switch block ends with a switch label followed by thedefaultlabel in the switch block}separator, then V is [un]assigned after the selector expression.
V is [un]assigned before the selector expression of a
switchstatement iff V is [un]assigned before theswitchstatement.V is [un]assigned before the switch rule expression, switch rule block, or switch rule
throwstatement introduced by a switch rule in the switch block iff V is [un]assigned after the selector expression of theswitchstatement.V is [un]assigned before the first block statement of a switch labeled statement group in the switch block iff both of the following are true:
V is [un]assigned after the selector expression of the
switchstatement.If the switch labeled statement group is not the first in the switch block, V is [un]assigned after the last block statement of the preceding switch labeled statement group.
V is [un]assigned before a block statement that is not the first of a switch labeled statement group in the switch block iff V is [un]assigned after the preceding block statement.
Chapter 18: Type Inference
18.5 Uses of Inference
18.5.5 Record Pattern Type Inference
When a record pattern (14.30.1) for a generic record class R appears in a context in which values of a type T will be matched against it, and the pattern does not provide type arguments for R, the type arguments are inferred, as described below.
If T is not downcast convertible (5.5) to the raw type R, inference fails.
Otherwise, where P1, ..., Pn (n ≥ 1) are the type parameters of R, let α1, ..., αn be inference variables. An initial bound set, B0, is generated from the declared bounds of P1, ..., Pn, as described in 18.1.3.
A type T' is derived from T, as follows:
If T is a wildcard-parameterized type, let β1, ..., βk (k ≥ 1) be inference variables, where k is the number of wildcard type arguments in T. T' is the result of replacing each wildcard type argument in T with βi (1 ≤ i ≤ k).
Additional bounds for β1, ..., βk are incorporated into B0 to form a bound set B1, as follows:
If βi (1 ≤ i ≤ k) replaced a wildcard in T with upper bound U, then the bound βi
<:U appears in the bound setIf βi (1 ≤ i ≤ k) replaced a wildcard in T with lower bound L, then the bound L
<:βi appears in the bound setLet Q1, ..., Qm (m ≥ 1) be the type parameters of the class or interface named by T', and A1, ..., Am be the type arguments of T'.
For each βi (1 ≤ i ≤ k), and for each type U delimited by
&in the TypeBound of the type parameter corresponding to βi (1 ≤ i ≤ m), the bound βi<:U[Q1:=A1, ..., Qm:=Am]appears in the bound set. If there is no TypeBound for the type parameter corresponding to βi, or if no proper upper bounds are derived from the TypeBound (only dependencies), then the bound βi<:Objectappears in the set.
If T is any other class or interface type, then T' is the same as T, and B1 is the same as B0.
If T is a type variable or an intersection type, then for each upper bound of the type variable or element of the intersection type, this step and step 4 are repeated recursively. All bounds produced in steps 3 and 4 are incorporated into a single bound set.
If T' is a parameterization of a generic class G, and there exists a supertype of R
<α1, ..., αn>that is also a parameterization of G, let R' be that supertype. The constraint formula ‹T' = R'› is reduced (18.2) and the resulting bounds are incorporated into B1 to produce a new bound set, B2.Otherwise, B2 is the same as B1.
If B2 contains the bound false, inference fails.
Otherwise, the inference variables α1, ..., αn are resolved in B2 (18.4). Unlike normal resolution, in this case resolution skips the step that attempts to produce an instantiation for an inference variable from its proper lower bounds or proper upper bounds; instead, any new instantiations are created by skipping directly to the step that introduces fresh type variables.
If resolution fails, then inference fails.
Otherwise, let A1, ..., An be the resolved instantiations for α1, ..., αn, and let Y1, ..., Yp (p ≥ 0) be any fresh type variables introduced by resolution.
The type of the record pattern is the upward projection of R
<A1, ..., An>with respect to Y1, ..., Yp (4.10.5).
Example 18.5.5-1. Record Pattern Type Inference
The following program infers a parameterization for a record class:
record Mapper<T>(T in, T out) implements UnaryOperator<T> {
public T apply(T arg) { return in.equals(arg) ? out : null; }
}
void test(UnaryOperator<? extends CharSequence> op) {
if (op instanceof Mapper(var in, var out)) {
boolean shorter = out.length() < in.length();
}
}
In this case, R is the record class Mapper, and
T is the type
UnaryOperator<? extends CharSequence>. T is
downcast convertible to raw Mapper, so we'll infer an
instantiation for α in
Mapper<α>.
T' is
the type UnaryOperator<β>,
where β has upper bound CharSequence.
Mapper<α> has the supertype
UnaryOperator<α>, so we'll
reduce the constraint formula
‹UnaryOperator<β> =
UnaryOperator<α>›. This leads
to the bound α = β. Incorporation further infers that
α <: CharSequence.
Now we resolve α, yielding α = Y, a
fresh type variable with upper bound CharSequence.
Finally, we find the upward projection
of Mapper<Y> with respect to Y,
inferring that the type of the record pattern is
Mapper<? extends CharSequence>.
Once we know the type of the record pattern, we can find its
component types, which are matched against the nested patterns. Pattern
variables in and out both have type
CharSequence.