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
whenexpressions 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
instanceoffor 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 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 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 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.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 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
whenexpression.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
whenexpression.A pattern variable is introduced by a switch label that supports a pattern that has an associated
whenexpression 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
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 with a linkage error (anIncompatibleClassChangeErrormay be thrown) 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 a linkage error (aMatchExceptionmay be thrown) if theswitchencounters 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-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
:{SwitchLabelBlockStatements:}
- SwitchLabel:
caseCaseConstant {,CaseConstant}default
- SwitchLabel:
- CaseOrDefaultLabel {
:CaseOrDefaultLabel } - CaseOrDefaultLabel:
caseCaseElement {,CaseElement }default- CaseElement:
- CaseConstant
- Pattern [ Guard ]
nulldefault
- CaseConstant:
- ConditionalExpression
- 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.
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
caseconstant 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
caseconstant with the same value.A switch label may not support both a
caseconstant and a pattern.A switch label may not support more than one
default.A switch label may not support a
defaultand have adefaultlabel.A switch label may not have more than
defaultlabel.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
defaultlabel.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 2andcase 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
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 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
caseconstant c is switch compatible with a type T where T is eitherchar,byte,short,int,Character,Byte,Short,Integer, orStringif c is assignment compatible with T (5.2).A
caseconstant 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
nullcase element is switch compatible with T if T is a reference type.A
defaultcase element is switch compatible with every type.
In particular, a switch block containing just a
defaultlabel 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
caseconstant dominates another switch label supporting the samecaseconstant.This rules out examples such as the following where the second occurrence of the
caseconstant2can 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
whenexpression 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
whenexpression 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
whenexpression 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> aldominates the type patternArrayList<Number> alnand 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
caseconstant 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
caseconstant is dominated:Integer i = ...; switch (i) { case Integer j -> ... case 42 -> ... // Error - dominated! }The second rule covers the case where an enum
caseconstant 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
whenexpressions - 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
caseconstants 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 icould be reached from the preceding statement group, and so the pattern variableiwill 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 ois unconditional at typeString.A set of case elements is exhaustive for a type T that includes an
abstractandsealedclass or interface named C, if it is exhaustive for every applicable permitted direct subtype of T.A type T includes an
abstractandsealedclass 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
sealedandabstract.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
abstractandsealedclass 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
abstractandsealedclass 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 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
abstractandsealedclass 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
defaultlabel is transformed to adefaultlabel.A
caselabel L is first transformed to acaselabel where every case element has been transformed as follows:A
caseconstant c is transformed to acaseconstant c.A
defaultcase element is transformed to adefaultcase element.A
nullcase element is transformed to anullcase 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
whenexpression, then q is associated with the same expression.
If the resulting transformed
caselabel has both anullcase 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 sapplies to the null reference value and, in this case, the pattern variablesis 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
nullapplies 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
nulland 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
caseconstant 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.caseconstants is equal to the value, then we say that thecaselabel which contains thecaseconstant 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
caseconstant 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 theequalsmethod 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
whenexpression then this switch label applies.If pattern matching succeeds and the pattern has an associated
whenexpression, 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 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.caselabel matches but there is adefaultlabel, then thedefaultlabel matches.A switch block may have at most one default switch label.
A
switch label cancasecontainsupport 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, theswitch label matches if the enum variablecasedayis 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.
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) 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 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 19,
switchstatements (andswitchexpressions) 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) onlycaseconstant elements were supported. However, 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 19,
switchstatements have been enhanced in the sense that the two limitations listed above have 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 an enhancedswitchstatement 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
switchstatement 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 aNullPointerExceptionis thrown and the entireswitchstatement completes abruptly for that reason. If the process of determining which switch label applies completes abruptly, then the entireswitchstatement 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 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 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
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
switchstatement is an enhancedswitchstatement and the result of evaluating the selector expression is of an enum type then anIncompatibleClassChangeErroris thrown and the entireswitchstatement completes abruptly for that reason.If the
switchstatement is an enhancedswitchstatement and the result of evaluating the selector expression is not of an enum type then aMatchExceptionis thrown and the entireswitchstatement completes abruptly for that reason.If the
switchstatement 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.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
varthen 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 variableris 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
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 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 typeRand 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
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.
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 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
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
:{SwitchLabelBlockStatements:}
- SwitchLabel:
caseCaseConstant {,CaseConstant}default
- SwitchLabel:
- CaseOrDefaultLabel {
:CaseOrDefaultLabel }- CaseOrDefaultLabel:
caseCaseElement {,CaseElement }default- CaseElement:
- CaseConstant
- Pattern { Guard }
nulldefault
- CaseConstant:
- ConditionalExpression
- 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 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.
switchexpressions cannot have empty switch blocks, unlikeswitchstatements. 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).
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 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 the
switchexpression 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 aNullPointerExceptionis thrown and the entireswitchexpression completes abruptly for that reason. If the process of determining which switch label applies completes abruptly, then the entireswitchexpression 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 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 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
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 anIncompatibleClassChangeErroris thrown and the entireswitchexpression 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
MatchExceptionis thrown and the entireswitchexpression 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 execution of an expression completes abruptly, then the
switchexpression 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 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.