Summary
Enhance the Java language with pattern matching for switch statements and switch expressions.
This allows patterns to appear in case labels, and uses pattern matching to determine which
case label matches the selector expression. A new kind of case label, case null, is introduced,
such that a switch with case null does not automatically throw NullPointerException when the
selector expression is null. The meaning of existing switch statements and switch expressions,
without case null, is unchanged. Pattern matching for switch complements pattern matching for
instanceof, as finalized in Java 16.
Problem
We often want to compare an variable against multiple alternatives. Java
supports multi-way comparisons with switch statements and, since Java 14,
switch expressions, but unfortunately switch is very limited. You can only
switch on values of a few types — numeric types, enum types, and String — and
you can only test for exact equality against constants. We might like to use
patterns to test the same variable against a number of possibilities, taking a
specific action on each, but since the existing switch does not support that,
we end up with a chain of if...else tests such as:
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
This code benefits from pattern matching for instanceof expressions, but it is far from
perfect. First and foremost, this approach allows coding errors to remain hidden
because we have used an overly general control construct. The intent is to
assign something to formatted in each arm of the if...else chain, but there is
nothing that enables the compiler to identify and verify this invariant. If some
block — perhaps one that is executed rarely — does not assign to formatted, we
have a bug. In addition, the above code is not optimizable; absent
compiler heroics it will have O(n) time complexity, even though the underlying
problem is often O(1).
If we extend switch statements and switch expressions to work on any type,
and allow case labels with patterns rather than just constants, then we could
rewrite the above code more clearly and reliably:
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
The intent of this code is clearer because we are using the right control
construct: we are saying, "the parameter o matches at most one of the following
conditions, so figure it out and evaluate the corresponding arm." As a bonus, this code
is optimizable; we are likely to be able to perform the dispatch in O(1) time.
Solution
The Java language is enhanced as follows, in both switch statements and switch expressions:
-
Allow patterns as
caselabels for switch blocks. -
Allow
nullas acaselabel for switch blocks. -
Introduce a new semantics for
switchthat uses pattern matching to select a matching switch label when the switch label uses a pattern. -
The semantics of
switchbecome "null-sensitive": if the value of the selector expression isnulland the switch block contains anulllabel, then this label is said to match. If the value of the selector expression isnulland the switch block does not contain thenulllabel, thenNullPointerExceptionis thrown as usual. This is a conservative extension to the semantics ofswitch: all existingswitchstatements andswitchexpressions will continue to execute as they do in Java 16. -
Allow the selector expression of a
switchstatement orswitchexpression to be of any type, not just the traditional set ofchar,byte,short,int,Character,Byte,Short,Integer,String, or enum type. This change is conservative in that an existingswitchstatement orswitchexpression that usescaselabels with constants will continue to require that the type of the selector expression be one ofchar,byte,short,int,Character,Byte,Short,Integer,String, or an enum type. -
Introduce the notion of dominance for switch labels, to prevent switch blocks containing switch labels that are "shadowed" by other labels appearing earlier in the block. For example:
switch (x) { case Object o -> ... case String s -> ... }This switch block is erroneous because any value that matches
String swill always matchObject o-- we say the switch labelcase Object odominates the switch labelcase String s. Dominance is determined with regards to the order that switch labels appear in the switch block. This means that the followingswitchstatement is permitted:switch (x) { case String s -> ... case Object o -> ... }Here, a string value for
xwill match the first switch labelcase String s, and any other non-nullvalue will match the second switch labelcase Object o. -
Enhance the existing notion of completeness for
switchexpressions to deal with pattern labels by employing a new concept of pattern totality.switchexpressions continue to be restricted so it is not possible for no switch label to match at runtime. -
Introduce a new kind of pattern, guarded patterns, as a means of refining a pattern with a boolean expression. For example, a value matches the guarded pattern
List l && l.length()>2if it first matches the type patternList l, and (only if it matches) it additionally contains at least two elements. -
Introduce a new kind of pattern, parenthesized patterns, to help address some parsing issues with guarded patterns containing nested && expressions
The standard libraries will be enhanced with java.lang.runtime.SwitchBootstrap (a preview class) with a bootstrap method (`typeSwitch), that will
categorize switch selector values to case numbers.
javac will be enhanced to parse the enhanced Java language, and its Trees API
will be enhanced to cover it.
Specification
Update on June 4: the specification for the bootstrap method has been changed as per review feedback. The updated full specdiff is attached as specdiff.01-4.zip, diff from the previously approved version is attached as specdiff.00-01-4.zip. These are also available for convenience here, respectivelly:
- http://cr.openjdk.java.net/~jlahoda/8262891/specdiff.01/overview-summary.html
- http://cr.openjdk.java.net/~jlahoda/8262891/specdiff.00-01/overview-summary.html
Original text: The current draft of the specification is attached here as jep406-20210519-2.zip.
The specdiff of the proposed API changes is attached as specdiff-20210519.zip and is also available here for convenience: http://cr.openjdk.java.net/~jlahoda/8262891/specdiff.00/overview-summary.html
- csr of
-
JDK-8262891 Compiler implementation for Pattern Matching for switch (Preview)
-
- Resolved
-
- relates to
-
JDK-8275408 Pattern Matching for switch (Second Preview)
-
- Closed
-