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 19. See JEP 427 and JEP 405 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-06-01: - 14.11.1.2 Fixed treatment of how null case labels are resolved and determined to apply at run time. - 14.30.2 Fixed semantics of pattern matching for any patterns.
2022-05-17: 15.20.2 Added missing text about resolving the pattern (which was explicitly discussed in 14.30.2)
2022-05-10: 14.30.2 Require an instance of MatchException
resulting from a record accessor completing abruptly explicitly record this reason as a cause.
2022-04-26: Updated to JEP 427 (Pattern Matching for switch
(Third Preview)).
2022-04-25: Fixed some code examples and renamed "unrefined" pattern labels as "unguarded". Clarified text surrounding default switch labels.
2022-04-20: Second draft with corrections following feedback on amber-spec-experts
mailing list.
2022-04-07: First draft released. Main changes from JEP 420 preview, in addition to various bug-fixes, are:
- Now includes the changes required to support Record Patterns (JEP 405).
- Removed guarded patterns.
- 14.11.1 Added optional
when
expressions to switch labels. - 14.11.1 Compatibility of switch blocks with selector expressions is now called switch compatibility.
- 15.20.2 Removed restriction on pattern
instanceof
for total patterns. - 14.30.3 Subsection has been renamed.
- 14.30.3 A total pattern is now called an unconditional pattern.
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 sealed
when
The keywords
const
andgoto
are 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
strictfp
is obsolete and should not be used in new code.
The keyword
_
(underscore) is reserved for possible future use in parameter declarations.
true
andfalse
are not keywords, but rather boolean literals (3.10.3).
null
is 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
module
andopen
, 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
requires
transitive
;
does not make use of RequiresModifier, so the termtransitive
is 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
var
as an identifier will cause an error, becausevar
is not a TypeIdentifier (3.8).For
yield
, when recognized as a terminal in a YieldStatement (14.21).In other contexts, attempting to use the
yield
as an identifier will cause an error, becauseyield
is 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 SwitchBlock (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 c
is always tokenized as the identifierpublicstatic
, rather than as the reserved keywordspublic
andstatic
. 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 e
is usually tokenized as the identifiervarfilename
, but in a local variable declaration, the first three input characters are tentatively recognized as the contextual keywordvar
by 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 keywordvar
on the grounds that the immediately following input characterf
is a JavaLetterOrDigit. The sequencev a r f i l e n a m e
is therefore tokenized as the identifiervarfilename
in 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-sealed
andclass
) 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 s
is 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
throw
statement.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
throw
statement.
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.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
throw
statement.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
throw
statement.
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 record component 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 record component 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"); }
If the record pattern p is a named record pattern, then it is a compile-time error if the record pattern variable is already in scope in a record pattern component that comes after it within the list.
6.3.4 Scope for Pattern Variables in Switch Labels
Pattern variables can be introduced by patterns that are supported by switch labels and by any when
expressions associated with the patterns, 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 a switch label:
A pattern variable is introduced by a switch label that supports a pattern p if it is declared by p.
A pattern variable declared by a pattern supported by a switch label is definitely matched in any associated
when
expression.It is a compile-time error if any pattern variable declared by a pattern supported by a switch label is already in scope at any associated
when
expression.A pattern variable is introduced by a switch label that supports a pattern that has an associated
when
expression e if it is introduced by e 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
sealed
class 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 exhaustiveswitch
will not fail to link if thesealed
class 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
switch
can fail with an error (aMatchException
is 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 thesealed
class, but more accurately a migration incompatible change of thesealed
class.
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
sealed
direct superclass or asealed
direct superinterface, then deleting thesealed
modifier would prevent C from being recompiled, as every class with asealed
direct superclass or asealed
direct 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
sealed
classes (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 with a linkage error (anIncompatibleClassChangeError
may be thrown) if theswitch
encounters 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
sealed
classes (13.4.2.1), whilst adding a permitted direct subclass or subinterface of asealed
interface is considered a binary compatible change, it may cause the execution of an exhaustiveswitch
(14.11.1) to fail with a linkage error (aMatchException
may be thrown) if theswitch
encounters an instance of the new permitted direct subclass and 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-sealed
interface I must have asealed
direct superinterface. Deleting thenon-sealed
modifier would prevent I from being recompiled, as every interface with asealed
direct superinterface must besealed
ornon-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
sealed
direct superinterface, then deleting thesealed
modifier would prevent I from being recompiled, as every interface with asealed
direct superinterface must besealed
ornon-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
:
{SwitchLabelBlockStatements:
}
- SwitchLabel:
case
CaseConstant {,
CaseConstant}default
- SwitchLabel:
- CaseOrDefaultLabel {
:
CaseOrDefaultLabel } - CaseOrDefaultLabel:
case
CaseElement {,
CaseElement }default
- CaseElement:
- CaseConstant
- Pattern [ Guard ]
null
default
- CaseConstant:
- ConditionalExpression
- Guard:
when
Expression
A switch block can consist of either:
Switch rules, which use
->
to introduce either a switch rule expression, a switch rule block, or a switch rulethrow
statement; 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.
Every switch rule and switch labeled statement group starts with a switch label. A switch label consists of one or more case
and default
labels. A case
label has one or more case elements. A switch label is said to support a case element if it has a case
label that has that case element.
A pattern case element may have an associated when
expression. A pattern case element is said to be unguarded if either (i) it has no associated when
expression, or (ii) it has an associated when
expression that is a constant expression (15.29) with value true
. A case element is unguarded if it is not a pattern case element or it is an unguarded pattern case element.
For every switch label in a switch block, all of the following must be true, otherwise a compile-time error occurs:
Every
case
constant supported by a switch label is either a constant expression (15.29) or the name of an enum constant (8.9.1).A switch label may not support more than one
case
constant with the same value.A switch label may not support both a
case
constant and a pattern.A switch label may not support more than one
default
.A switch label may not support a
default
and have adefault
label.A switch label may not have more than
default
label.A switch label may not support more than one
null
.A switch label may not support more than one pattern.
A switch label may not support both a pattern and a
default
.A switch label may not support both a pattern and have a
default
label.A switch label may not support both a record pattern and a
null
.
These rules restrict the form of switch labels. Much of the complexity is due to the historical support of two ways of combining case label elements in switch labels for statement groups (for example
case 1: case 2
andcase 1,2
).
Any when
expression associated with a pattern case element 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
.
It is a compile-time error if the switch label of a switch rule consists of more than one case
or default
label.
This means that
case 1: case 2 -> ...
is not a valid switch rule, but can be written ascase 1, 2 -> ...
.
A switch label is said to be a default switch label if either it has a default
label, or it supports a default
case element. It is a compile-time error if a switch block has more than one default switch label.
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
case
constant associated with the switch block is assignment compatible with T (5.2).- If T is an enum type, then every
case
constant 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 every switch label in the switch block is switch compatible with T. A switch label is switch compatible with T if every case element it supports, if any, is switch compatible with T, which is defined as follows:
A
case
constant c is switch compatible with a type T where T is eitherchar
,byte
,short
,int
,Character
,Byte
,Short
,Integer
, orString
if c is assignment compatible with T (5.2).A
case
constant c is switch compatible with an enum type T if c is an enum constant of type T.A pattern case element p is switch compatible with T if p is applicable at type T (14.30.3).
A
null
case element is switch compatible with T if T is a reference type.A
default
case element is switch compatible with every type.
In particular, a switch block containing just a
default
label is compatible with all selector expressions.
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 switch label that supports a
case
constant dominates another switch label supporting the samecase
constant.This rules out examples such as the following where the second occurrence of the
case
constant2
can never match.int i = ...; switch (i) { case 1, 2 -> System.out.println("1 or 2"); case 2, 3 -> System.out.println("2 or 3"); // Error! }
A switch label that supports an unguarded pattern p dominates another switch label supporting a pattern q if p dominates q (14.30.3).
A switch label that supports a pattern with a
when
expression is not considered to dominate another switch label supporting the same pattern. This allows the following common pattern programming style:Object o = ...; switch (o) { case Integer i when i <= 0 -> ... case Integer i when i > 0 -> ... case Integer i -> ... ... }
The only exception is where the
when
expression has the valuetrue
, for example:Object o = ...; switch (o) { case Integer i when true -> ... // Allowed but ... why? case Integer i -> ... // Error - dominated switch label ... }
However, a switch label supporting a pattern with a
when
expression is dominated by a switch label supporting just the pattern. For example, the following results in a compile-time error:Object o = ...; switch (o) { case List<?> l -> ... case List<?> l when l.size() == 2 -> ... // Error - dominated by previous switch label ... }
The definition of a pattern dominating another pattern (14.30.3) is based on types. For example, the following results in a compile-time error:
Object o = ... switch (o) { case Object obj -> System.out.println("Object"); case String s -> System.out.println("String"); // Error - dominated switch label }
More precisely, dominance is defined in terms of the erasure of types. For example, the type pattern
ArrayList<? extends Number> al
dominates the type patternArrayList<Number> aln
and vice versa. This means that, for example, the following switch block results in a compile-time error:List<Number> l = ...; switch (l) { case ArrayList<? extends Number> al -> ... case ArrayList<Number> aln -> ... // Error - dominated switch label ... }
A switch label that supports a pattern p dominates another switch label supporting a
case
constant c if p dominates c, which is defined as follows:A type pattern that declares a pattern variable of type T dominates a constant c of a primitive type P if the wrapper class of P (5.1.7) is a subtype of the erasure of T.
A type pattern that declares a pattern variable of type T dominates an enum constant c of type E if E is a subtype of the erasure of the type of T.
A parenthesized pattern dominates a constant c if its contained pattern dominates c.
The first rule covers the case where a primitive
case
constant is dominated:Integer i = ...; switch (i) { case Integer j -> ... case 42 -> ... // Error - dominated! }
The second rule covers the case where an enum
case
constant is dominated:enum E { A, B, C } E value = ...; switch (value) { case E e -> ... case A -> ... // Error - dominated! }
There is no rule for record patterns.
No analysis of any
when
expressions - undecidable in general - is 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 i = ...; switch (i) { case Integer j when j != 42 -> ... case 42 -> ... // Error - dominated! ... }
Switch labels supporting
case
constants should appear before those supporting patterns; for example:Integer i = ...; switch (i) { case 42 -> ... case Integer j when j < 50 -> ... // All integers less than 50 // *except* 42 case Integer j -> ... // All other integers }
It is a compile-time error if there is a statement in a switch block that consists of switch-labeled statement groups for which both of the following are true:
It is labeled with a switch label that introduces a pattern variable.
There is a statement preceding it in the switch block and that statement can complete normally (14.22).
This condition is required to exclude the possibility of a switch labeled statement being reached for which a pattern variable declared in its switch label is in scope but without the pattern matching having succeeded. For example, the statement labeled by the switch label supporting the type pattern
Integer i
could be reached from the preceding statement group, and so the pattern variablei
will not be initialized:Object o = "Hello"; switch (o) { case String s: System.out.println("String: " + s ); case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching switch label ... }
It is a compile-time error if both of the following are true for a switch
expression or a switch
statement:
There is a default switch label in the switch block, and
There is a switch label supporting a pattern p in the switch block where p is unconditional at the type of the selector expression (14.30.3).
A pattern that is unconditional at the type of the selector expression will, as the name suggests, match every value and so behaves much like a default switch label.
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) the switch block contains a default switch label, or (ii) the set containing all the unguarded case elements supported by switch labels in the switch block is exhaustive for T, which is specified as follows:
A set of case elements is exhaustive for an enum class type E if it contains all of the enum constants of E.
Note that a default switch label is permitted, but not required in the case where all the enum constants appear in the switch labels. 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 case elements is exhaustive for a type T if it contains a pattern that is unconditional at type T (14.30.3).
A pattern is said to be unconditional at a given type if it is known to match every value of that type. For example, the type pattern
Object o
is unconditional at typeString
.A set of case elements is exhaustive for a type T that includes an
abstract
andsealed
class or interface named C, if it is exhaustive for every applicable permitted direct subtype of T.A type T includes an
abstract
andsealed
class or interface named C if and only if one of the following holds:T is a class type that names C, and the class C is both
sealed
andabstract
.T is an interface type that names C, and the interface C is
sealed
.T is a type variable, and its upper bound includes C.
T is an intersection type T1
&
...&
Tn, and a type Ti includes C (1 ≤ i ≤ n).
A class or interface type T is said to be an applicable permitted direct subtype of a class or interface type S that names an
abstract
andsealed
class or interface, C, if one of the following holds:T is the class or interface type D, where (i) D is a permitted direct subclass or subinterface of C, (ii) D is not a generic class or interface, and (iii) S is downcast convertible to D (5.5).
T is the type D<
?
, ...,?
> (i.e. the parameterization of D with n type arguments that are all wildcards), where (i) D is a permitted direct subclass or subinterface of C, (ii) D is a generic class or interface with n type parameters, and (iii) S is downcast convertible to D<?
, ...,?
>.
Note that a default switch label is permitted, but not required in the case where the switch block is exhaustive for all the permitted direct subclasses and subinterfaces of an
abstract
andsealed
class 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
,B
andC
, and no other instances of typeI
are permitted, this switch block is exhaustive.The fact that a permitted direct subclass or subinterface may only extend a particular parameterization of a generic
sealed
superclass or superinterface means that it may not always need to be considered when determining whether a switch block is exhaustive. For example:sealed interface J<X> permits D, E {} final class D<Y> implements J<String> {} final class E<X> implements J<X> {} static int testExhaustive2(J<Integer> ji) { return switch(ji) { // Exhaustive! case E<Integer> e -> 42; }; }
As the selector expression has type
J<Integer>
the permitted direct subclassD
need not be considered as there is no possibility that the value ofji
can be an instance ofD
.A set of case elements, P, is exhaustive for a type T that names a record class R if (i) Q is a non-empty subset of P containing only record patterns with a type that names R, 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 .
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 is exhaustive for 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
abstract
andsealed
class or interface D, and for every applicable permitted direct subtype of U, the set P is exhaustive from component c at that type.
A switch
statement or expression is exhaustive if its switch block is exhaustive for 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 are being matching against, patterns must be resolved before pattern matching can be performed (14.30.2). Consequently, any patterns supported by switch labels in a switch
expression or switch
statement must also be resolved.
An executable switch
expression or switch
statement is one where every default
and case
label has been transformed with respect to the type of the selector expression, T, as follows:
A
default
label is transformed to adefault
label.A
case
label L is first transformed to acase
label where every case element has been transformed as follows:A
case
constant c is transformed to acase
constant c.A
default
case element is transformed to adefault
case element.A
null
case element is transformed to anull
case element.A pattern case element p is transformed to a pattern case element label q, where q is the result of resolving pattern p at type T (14.30.2). If p has an associated
when
expression, then q is associated with the same expression.
If the resulting transformed
case
label has both anull
case element and a pattern case element p where p is a pattern declaring a pattern variable x of type U, then p is replaced with an any pattern that declares x of type U (14.30.1).Recall that if a switch label supports both a pattern and a
null
, the pattern can not be a record pattern (14.11.1).This final step covers a special case of pattern matching for
switch
. For example, the labelcase null, String s
applies to the null reference value and, in this case, the pattern variables
is initialized with the null reference value. This is achieved by replacing the type pattern with an any pattern of typeString
.
Both the execution of a an executable switch
statement (14.11.3) and the evaluation of a an executable switch
expression (15.28.2) need to determine if a switch label matches applies to the value of the selector expression. To determine Determining whether a switch label in a switch block matches applies to a given value, the value is compared with the case
constants associated with the switch blockis proceeds as follows: Then:
If the value is the null reference, then we determine the first (if any) switch label in the switch block that applies to the value as follows:
A switch label that supports a
null
applies if the value matches every pattern, if any, that the switch label also supports. If pattern matching completes abruptly then determining which switch label applies completes abruptly for the same reason.This means that, in particular, a switch label that supports a
null
and does not support any patterns, applies to the null reference value.
If the value is not the null reference, then we determine the first (if any) switch label in the switch block that applies to the value as follows:
A switch label that supports a
case
constant 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.If one of the.case
constants is equal to the value, then we say that thecase
label which contains thecase
constant matchesAny 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 switch label that supports a
case
constant 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(15.21)unless the value is aString
, in which case equality is defined in terms of theequals
method of classString
.Determining that a switch label that supports a pattern 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 pattern does not have an associated
when
expression then this switch label applies.If pattern matching succeeds and the pattern has an associated
when
expression, then thewhen
expression is evaluated. If the result is of typeBoolean
, it is subjected to unboxing conversion (5.1.8).If evaluation of the
when
expression 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
true
then the switch label applies.If noIf none of the preceding rules apply to any of the switch labels in the switch block, then a default switch label applies.case
label matches but there is adefault
label, then thedefault
label matches.A switch block may have at most one default switch label.
A
switch label cancase
containsupport severalcase
constants. 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, theswitch label matches if the enum variablecase
day
is 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.
null
cannot be used as acase
constant because it is not a constant expression. Even ifcase
null
was allowed, it would be undesirable because the code in thatcase
can never be executed. Namely, if the selector expression is of a reference type (that is,String
or a boxed primitive type or an enum type), then an exception will occur if the selector expression evaluates tonull
at run time. In the judgment of the designers of the Java programming language, propagating the exception is a better outcome than either having nocase
label match, or having thedefault
label match.
In C and C++ the body of a
switch
statement can be a statement and statements withcase
labels do not have to be immediately contained by that statement. Consider the simple loop:for (i = 0; i < n; ++i) foo();
where
n
is known to be positive. A trick known as Duff's device can be used in C or C++ to unroll the loop, but this is not valid code in the Java programming language:int q = (n+7)/8; switch (n%8) { case 0: do { foo(); // Great C hack, Tom, case 7: foo(); // but it's not valid here. case 6: foo(); case 5: foo(); case 4: foo(); case 3: foo(); case 2: foo(); case 1: foo(); } while (--q > 0); }
Fortunately, this trick does not seem to be widely known or used. Moreover, it is less needed nowadays; this sort of code transformation is properly in the province of state-of-the-art optimizing compilers.
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) at least one of the switch labels supports a pattern or a null
.
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 onedefault
label is associated with theswitch
block.Every switch rule expression in the switch block is a statement expression (14.8).
switch
statements differ fromswitch
expressions in terms of which expressions may appear to the right of an arrow (->
) in the switch block, that is, which expressions may be used as switch rule expressions. In aswitch
statement, only a statement expression may be used as a switch rule expression, but in aswitch
expression, any expression may be used (15.28.1).If the
switch
statement is an enhancedswitch
statement, then it must be exhaustive.
Prior to Java SE 19,
switch
statements (andswitch
expressions) were limited in two ways: (i) the type of the selector expression was restricted to an integral type (excludinglong
), an enum type, orString
; and (ii) onlycase
constant elements were supported. However, unlikeswitch
expressions,switch
statements did not have to be exhaustive. This is often the cause of difficult to detect bugs, where no switch label applies and theswitch
statement will silently do nothing. For example:enum E { A, B, C} E e = ...; switch (e) { case A -> System.out.println("A"); case B -> System.out.println("B"); // No case for C! }
With Java SE 19,
switch
statements have been enhanced in the sense that the two limitations listed above have been lifted. The designers of the Java programming language decided that enhancedswitch
statements should align withswitch
expressions and be required to be exhaustive. This is often achieved with the addition of a trivialdefault
label. For example, the following enhancedswitch
statement is not exhaustive:Object o = ...; switch (o) { // Error - non-exhaustive switch! case String s -> System.out.println("A string!"); }
but it can easily be made exhaustive:
Object o = ...; switch (o) { case String s -> System.out.println("A string!"); default -> {} }
For compatibility reasons,
switch
statements that are not an enhancedswitch
statement 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
, and no switch label in the resolved switch block applies then aNullPointerException
is thrown and the entireswitch
statement completes abruptly for that reason. If the process of determining which switch label applies completes abruptly, then the entireswitch
statement completes abruptly for the same 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 entireswitch
statement 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 normally,switch
statement continues by determining if a switch label associated with in 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
switch
statement completes abruptly for the same reason.If no switch label
matches, the entireapplies then one of the following holds:switch
statement completes normally.If the
switch
statement is an enhancedswitch
statement and the result of evaluating the selector expression is of an enum type then anIncompatibleClassChangeError
is thrown and the entireswitch
statement completes abruptly for that reason.If the
switch
statement is an enhancedswitch
statement and the result of evaluating the selector expression is not of an enum type then aMatchException
is thrown and the entireswitch
statement completes abruptly for that reason.If the
switch
statement is not an enhancedswitch
statement then the entireswitch
statement 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
switch
statement completes normally. If the result of evaluation is a value, it is discarded.If it is the switch label for a switch rule block, then the block is executed. If this block completes normally, then the
switch
statement completes normally.If it is the switch label for a switch rule
throw
statement, then thethrow
statement is executed.If it is the switch label for a switch labeled statement group, then all the statements in the switch block that follow the switch label are executed in order. If these statements complete normally, then the
switch
statement completes normally.Otherwise, there are no statements that follow the
matchedswitch label that applies in the switch block, and theswitch
statement completes normally.
If execution of any statement or expression in the switch block completes abruptly, it is handled as follows:
If execution of a statement completes abruptly because of a
break
with no label, then no further action is taken and theswitch
statement completes normally.Abrupt completion because of a
break
with a label is handled by the general rule for labeled statements (14.7).If execution of a statement or expression completes abruptly for any other reason, then the
switch
statement completes abruptly for the same reason.Abrupt completion because of a
yield
statement is handled by the general rule for switch expressions (15.28.2).
Example 14.11.3-1. Fall-Through in the switch
Statement
When a 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.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 pattern may be parenthesized to assist in readability. 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.
- Pattern:
- TypePattern
- ParenthesizedPattern
- RecordPattern
- TypePattern:
- LocalVariableDeclaration
- ParenthesizedPattern:
(
Pattern)
- RecordPattern:
- ReferenceType RecordStructurePattern [ Identifier ]
- RecordStructurePattern:
(
[ RecordComponentPatternList ])
- RecordComponentPatternList :
- Pattern {
,
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 a record component pattern list 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 declared type of a pattern variable declared in a top-level type pattern is the reference type denoted by LocalVariableType.
The declared 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
var
then the pattern variable must appear in a record component pattern list of a record pattern with type R. The declared type of the pattern variable is the type of the corresponding component field in R.Consider the following record declaration:
record R<T>(ArrayList<T> r){}
Given the pattern
R<String>(var r)
, the declared type of the pattern variabler
is thusArrayList<String>
.
The type of a type pattern is the type of its pattern variable.
A parenthesized pattern declares the local variables that are declared by the contained pattern.
A record pattern consists of a ReferenceType, a record component pattern list, and an optional Identifier. If ReferenceType is not a record class type (8.10) then a compile-time error occurs.
A record pattern with an Identifier is called a named record pattern.
The length of record component pattern list must be the same as the length of the record component list in the declaration of the record class named by ReferenceType; otherwise a compile-time error occurs.
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 record component pattern list.
A named record pattern additionally declares a local variable, called the record pattern variable, which is named Identifier and whose declared type is the type denoted by ReferenceType.
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
instanceof
expression, or as a pattern in a switch label of aswitch
expression orswitch
statement. 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
instanceof
pattern 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 parenthesized pattern, with contained pattern p, is resolved to a parenthesized pattern whose contained pattern is the result of resolving p at type U.
- A record pattern p with type R and record component pattern list L is resolved to a record pattern with type R and the record component pattern list resulting from resolving, in order, each pattern in L, if any, at the type of the corresponding component field in R.
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(Sub s)
as the null value for the record component does not match the patternSub s
.The meaning of a pattern occurring in a record component pattern list is then determined with respect to the record declaration. Resolution replaces any type patterns appearing in a record component 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 typeR
and a record component 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 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.
A value matches a parenthesized pattern if it matches the contained pattern; and does not match otherwise.
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 record component pattern list L if (i) v can be cast to R without raising a
ClassCastException
; and (ii) v matches L; and does not match otherwise.If v matches and the record pattern is a named record pattern, then the record pattern variable is initialized with v.
A value matches a record component pattern list if it matches every pattern, if any, in the list at the corresponding record component. If pattern matching the value at any record component completes abruptly then pattern matching completes abruptly for the same reason.
A value v that is not the null reference matches a pattern p at a record component c if the result of invoking the accessor method corresponding to c on the value v matches the pattern p. If execution of the invocation of the accessor method completes abruptly for reason S then pattern matching completes abruptly by throwing a
MatchException
with cause S.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.
There is no rule to cover a value that is the null reference. This is because the solitary construct that performs pattern matching, the
instanceof
pattern 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 record component 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 must be applicable at the type of the corresponding component field in R (5.5).
If the type R names a generic record class then it is a compile-time error if R is not a parameterized type.
Record patterns with a type that names a generic record class should use a parameterization of this class. Currently, there is no support for inference of type arguments in record patterns. This may be supported in future versions of the Java Programming Language.
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 record component pattern list L dominates another record pattern with type S and record component 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
instanceof
ReferenceType - RelationalExpression
instanceof
Pattern
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
true
if the value could be cast to the ReferenceType without raising aClassCastException
, andfalse
otherwise.
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
true
if the value matches the Pattern (14.30.2), andfalse
otherwise.A side effect of a
true
result 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
switch
expression and aswitch
statement (14.11) is called a switch block. General rules which apply to all switch blocks, whether they appear inswitch
expressions orswitch
statements, 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
:
{SwitchLabelBlockStatements:
}
- SwitchLabel:
case
CaseConstant {,
CaseConstant}default
- SwitchLabel:
- CaseOrDefaultLabel {
:
CaseOrDefaultLabel }- CaseOrDefaultLabel:
case
CaseElement {,
CaseElement }default
- CaseElement:
- CaseConstant
- Pattern { Guard }
null
default
- CaseConstant:
- ConditionalExpression
- Guard:
when
Expression
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
default
label associated with the switch block.If the type of the selector expression is an enum type, then (i) the set of the
case
constants associated with the switch block includes every enum constant of the enum type, and (ii) at most onedefault
label is associated with the switch block.A
default
label is permitted, but not required, when thecase
labels cover all the enum constants.
If the switch block consists of switch rules, then any switch rule block cannot complete normally (14.22).
If the switch block consists of switch labeled statement groups, then the last statement in the switch block cannot complete normally, and the switch block does not have any switch labels after the last switch labeled statement group.
switch
expressions cannot have empty switch blocks, unlikeswitch
statements. Furthermore,switch
expressions differ fromswitch
statements 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 aswitch
expression, any expression may be used as a switch rule expression, but in aswitch
statement, only a statement expression may be used (14.11.1).
A switch
expression must be exhaustive (14.11.1.1), or a compile-time error occurs.
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 theswitch
expression.If the switch rule is of the form
...
->
Block then every expression which is immediately contained in ayield
statement in Block whose yield target is the givenswitch
expression, is a result expression of theswitch
expression.
If the switch block consists of switch labeled statement groups, then every expression immediately contained in a
yield
statement in the switch block whose yield target is the givenswitch
expression, is a result expression of theswitch
expression.
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
switch
expression.Otherwise, if the type of each result expression is
boolean
orBoolean
, then an unboxing conversion (5.1.8) is applied to each result expression of typeBoolean
, and theswitch
expression has typeboolean
.Otherwise, if the type of each result expression is convertible to a numeric type (5.1.8), then the type of the
switch
expression 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
switch
expression 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 the
switch
expression completes abruptly for the same reason.Otherwise, if the result of evaluating the selector expression is
null
, and no switch label in the resolved switch block applies then aNullPointerException
is thrown and the entireswitch
expression completes abruptly for that reason. If the process of determining which switch label applies completes abruptly, then the entireswitch
expression completes abruptly for the same reason.
- Otherwise, if the result of evaluating the selector expression is non-
null
, and of typeCharacter
,Byte
,Short
, orInteger
, it is subjected to unboxing conversion (5.1.8). If this conversion completes abruptly, then the entireswitch
expression 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 in 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
switch
expression 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 anIncompatibleClassChangeError
is thrown and the entireswitch
expression completes abruptly for that reason.If no switch label applies, and the result of evaluating the selector expression is not of an enum type then a
MatchException
is thrown and the entireswitch
expression 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
switch
expression 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
switch
expression completes normally.If it is the switch label for a switch rule
throw
statement, then thethrow
statement 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 theswitch
expression completes normally.
If execution of any statement or expression in the switch block completes abruptly, it is handled as follows:
If execution of an expression completes abruptly, then the
switch
expression completes abruptly for the same reason.If execution of a statement completes abruptly because of a
yield
with value V, then theswitch
expression completes normally and the value of theswitch
expression is V.If execution of a statement completes abruptly for any reason other than a
yield
with a value, then theswitch
expression 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
switch
statement (14.11) iff all of the following are true:V is [un]assigned before every
break
statement (14.15) that may exit theswitch
statement.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
throw
statement 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 thedefault
label in the switch block}
separator, then V is [un]assigned after the selector expression.
V is [un]assigned before the selector expression of a
switch
statement iff V is [un]assigned before theswitch
statement.V is [un]assigned before the switch rule expression, switch rule block, or switch rule
throw
statement introduced by a switch rule in the switch block iff V is [un]assigned after the selector expression of theswitch
statement.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
switch
statement.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.