Movatterモバイル変換


[0]ホーム

URL:


JEP 433: Pattern Matching for switch (Fourth Preview)

OwnerGavin Bierman
TypeFeature
ScopeSE
StatusClosed / Delivered
Release20
Componentspecification / language
Discussionamber dash dev at openjdk dot org
Relates toJEP 427: Pattern Matching for switch (Third Preview)
JEP 432: Record Patterns (Second Preview)
JEP 441: Pattern Matching for switch
Reviewed byAlex Buckley, Brian Goetz
Endorsed byBrian Goetz
Created2022/09/23 14:22
Updated2023/05/12 15:34
Issue8294285

Summary

Enhance the Java programming language with pattern matching forswitchexpressions and statements. Extending pattern matching toswitch allows anexpression to be tested against a number of patterns, each with a specificaction, so that complex data-oriented queries can be expressed concisely andsafely. This is apreview language feature.

History

Pattern Matching forswitch was proposed as a preview feature byJEP 406 and delivered inJDK 17, proposed for a secondpreview byJEP 420 and delivered inJDK 18, and proposed for athird preview byJEP 427 and deliveredinJDK 19. This JEP proposes afourth preview to enable its continued co-evolution with theRecord Patterns preview feature (JEP 432) and toallow other refinements based upon continued experience and feedback.

The main changes since the third preview are:

Goals

Motivation

In Java 16,JEP 394 extended theinstanceof operator to take atype pattern and performpattern matching. This modest extension allows thefamiliar instanceof-and-cast idiom to be simplified, making it both more conciseand less error-prone:

// Old codeif (obj instanceof String) {    String s = (String)obj;    ... use s ...}// New codeif (obj instanceof String s) {    ... use s ...}

We often want to compare a variable such asobj against multiple alternatives.Java supports multi-way comparisons withswitch statements and, sinceJava 14,switch expressions (JEP 361), but unfortunatelyswitch is very limited. You can only switch on values of a few types —integral primitive types (excludinglong), their corresponding boxed forms,enum types, andString — and you can only test for exact equality againstconstants. We might like to use patterns to test the same variable against anumber of possibilities, taking a specific action on each, but since theexistingswitch does not support that we end up with a chain ofif...elsetests such as:

static String formatter(Object obj) {    String formatted = "unknown";    if (obj instanceof Integer i) {        formatted = String.format("int %d", i);    } else if (obj instanceof Long l) {        formatted = String.format("long %d", l);    } else if (obj instanceof Double d) {        formatted = String.format("double %f", d);    } else if (obj instanceof String s) {        formatted = String.format("String %s", s);    }    return formatted;}

This code benefits from using patterninstanceof expressions, but it is farfrom perfect. First and foremost, this approach allows coding errors to remainhidden because we have used an overly general control construct. The intent isto assign something toformatted in each arm of theif...else chain, butthere is nothing that enables the compiler to identify and verify thisinvariant. If some block — perhaps one that is executed rarely — does not assigntoformatted, we have a bug. (Declaringformatted as a blank local would atleast enlist the compiler’s definite-assignment analysis in this effort, butdevelopers do not always write such declarations.) In addition, the above codeis not optimizable; absent compiler heroics it will haveO(n) timecomplexity, even though the underlying problem is oftenO(1).

Butswitch is a perfect match for pattern matching! If we extendswitchstatements and expressions to work on any type, and allowcase labels withpatterns rather than just constants, then we can rewrite the above code moreclearly and reliably:

static String formatterPatternSwitch(Object obj) {    return switch (obj) {        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        -> obj.toString();    };}

The semantics of thisswitch are clear: Acase label with a pattern appliesif the value of the selector expressionobj matches the pattern. (We have shownaswitch expression for brevity but could instead have shown aswitchstatement; the switch block, including thecase labels, would be unchanged.)

The intent of this code is clearer because we are using the right controlconstruct: We are saying, "the parameterobj matches at most one of thefollowing conditions, figure it out and evaluate the corresponding arm." As abonus, it is more optimizable; in this case we are more likely to be able toperform the dispatch inO(1) time.

Switches and null

Traditionally,switch statements and expressions throwNullPointerExceptionif the selector expression evaluates tonull, so testing fornull must bedone outside of theswitch:

static void testFooBar(String s) {    if (s == null) {        System.out.println("Oops!");        return;    }    switch (s) {        case "Foo", "Bar" -> System.out.println("Great");        default           -> System.out.println("Ok");    }}

This was reasonable whenswitch supported only a few reference types.However, ifswitch allows a selector expression of any type, andcase labelscan have type patterns, then the standalonenull test feels like an arbitrarydistinction, and invites needless boilerplate and opportunity for error. Itwould be better to integrate thenull test into theswitch by allowing a newnull case label:

static void testFooBar(String s) {    switch (s) {        case null         -> System.out.println("Oops");        case "Foo", "Bar" -> System.out.println("Great");        default           -> System.out.println("Ok");    }}

The behavior of theswitch when the value of the selector expression isnullis always determined by itscase labels. With acase null, theswitchexecutes the code associated with that label; without acase null, theswitch throwsNullPointerException, just as before. (To maintain backwardcompatibility with the current semantics ofswitch, thedefault label doesnot match anull selector.)

Case refinement

Experimentation with patterns inswitch suggests it is common to want torefine the test embodied by a pattern label. For example, consider the followingcode that switches over aShape value:

class Shape {}class Rectangle extends Shape {}class Triangle  extends Shape { int calculateArea() { ... } }static void testTriangle(Shape s) {    switch (s) {        case null:            break;        case Triangle t:            if (t.calculateArea() > 100) {                System.out.println("Large triangle");                break;            }        default:            System.out.println("A shape, possibly a small triangle");    }}

The intent of this code is to have a special case for large triangles (thosewhose area is over 100), and a default case for everything else (including smalltriangles). However, we cannot express this directly with a single pattern. Wefirst have to write acase label that matches all triangles, and then placethe test of the area of the triangle rather uncomfortably within thecorresponding statement group. Then we have to use fall-through to get thecorrect behavior when the triangle has an area less than 100. (Note the carefulplacement of thebreak statement inside theif block.)

The problem here is that using a single pattern to discriminate amongcases does not scale beyond a single condition — we need some way toexpress a refinement to a pattern. We therefore allowwhen clauses inswitch blocks to specify guards to patterncase labels, e.g.,case Triangle t when t.calculateArea() > 100. We refer to such acase labelas aguardedcase label, and to the boolean expression as theguard.

With this approach, we can revisit thetestTriangle code to express thespecial case for large triangles directly. This eliminates the use offall-through in theswitch statement, which in turn means we can enjoy concisearrow-style (->) rules:

static void testTriangle(Shape s) {    switch (s) {        case null ->             { break; }        case Triangle t        when t.calculateArea() > 100 ->            System.out.println("Large triangle");        default ->            System.out.println("A shape, possibly a small triangle");    }}

The second clause is taken if the value ofs matches the patternTriangle tand subsequently the guardt.calculateArea() > 100 evaluates totrue.(The guard is able to use any pattern variables that are declared by the patternin thecase label.)

Usingswitch makes it easy to understand and change case labels whenapplication requirements change. For example, we might want to split trianglesout of the default path; we can do that by using two case clauses, one with aguard and one without:

static void testTriangle(Shape s) {    switch (s) {        case null ->             { break; }        case Triangle t        when t.calculateArea() > 100 ->            System.out.println("Large triangle");        case Triangle t ->            System.out.println("Small triangle");        default ->            System.out.println("Non-triangle");    }}

Description

We enhanceswitch statements and expressions in three ways:

For convenience, we also introduceparenthesized patterns.

Patterns in switch labels

We revise the grammar for switch labels in a switch block to read (compareJLS §14.11.1):

SwitchLabel:  case CaseConstant { , CaseConstant }  case null [, default]  case Pattern  default

The main enhancement is to introduce a newcase label,case p, wherep is apattern. The essence ofswitch is unchanged: The value of the selectorexpression is compared to the switch labels, one of the labels is selected, andthe code associated with that label is executed or evaluated. The difference nowis that forcase labels with patterns, the label selected is determined by theresult of pattern matching rather than by an equality test. For example, in thefollowing code, the value ofobj matches the patternLong l, and theexpression associated with the labelcase Long l is evaluated:

Object obj = 123L;String formatted = switch (obj) {    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        -> obj.toString();};

After a successful pattern match we often further test the result of thematch. This can lead to cumbersome code, such as:

static void test(Object obj) {    switch (obj) {        case String s:            if (s.length() == 1) { ... }            else { ... }            break;        ...    }}

The desired test — thatobj is aString of length 1 — is unfortunately splitbetween the patterncase label and the followingif statement.

To address this we introduceguarded patterncase labels by supporting an optionalguard after the pattern label. This allows the above code to be rewritten sothat all the conditional logic is lifted into the switch label:

static void test(Object obj) {    switch (obj) {        case String s when s.length() == 1 -> ...        case String s                      -> ...        ...    }}

The first clause matches ifobj is both aStringand of length 1. The secondcase matches ifobj is aString of any length.

Only pattern labels can have a guard. For example, it is not valid to write alabel with acase constant and a guard; e.g.,case "Hello" when RandomBooleanExpression().

Sometimes we need to parenthesize patterns to improve readability. We thereforeextend the language of patterns to supportparenthesized patterns written(p), wherep is a pattern. A parenthesized pattern(p) introduces thepattern variables that are introduced by the subpatternp. A value matches aparenthesized pattern(p) if it matches the patternp.

There are five major language design areas to consider when supportingpatterns inswitch:

  1. Enhanced type checking
  2. Exhaustiveness ofswitch expressions and statements
  3. Scope of pattern variable declarations
  4. Dealing withnull
  5. Errors

1. Enhanced type checking

1a. Selector expression typing

Supporting patterns inswitch means that we can relax the current restrictionson the type of the selector expression. Currently the type of the selectorexpression of a normalswitch must be either an integral primitive type(excludinglong), the corresponding boxed form (i.e.,Character,Byte,Short, orInteger),String, or anenum type. We extend this and requirethat the type of the selector expression be either an integral primitive type(excludinglong) or any reference type.

For example, in the following patternswitch the selector expressionobj ismatched with type patterns involving a class type, anenum type, a record type,and an array type, along with anullcase label and adefault:

record Point(int i, int j) {}enum Color { RED, GREEN, BLUE; }static void typeTester(Object obj) {    switch (obj) {        case null     -> System.out.println("null");        case String s -> System.out.println("String");        case Color c  -> System.out.println("Color: " + c.toString());        case Point p  -> System.out.println("Record class: " + p.toString());        case int[] ia -> System.out.println("Array of ints of length" + ia.length);        default       -> System.out.println("Something else");    }}

Everycase label in the switch block must be compatible with the selectorexpression. For acase label with a pattern, known as apattern label, weuse the existing notion ofcompatibility of an expression with a pattern(JLS §14.30.1).

1b. Dominance ofcase labels

Supporting patterncase labels means that for a given value of the selectorexpression it is now possible for more than onecase label to potentially apply(previously, at most only onecase label could apply). For example, if theselector expression evaluates to aString then both thecase labelscase String s andcase CharSequence cs would apply.

The first issue to resolve is deciding exactly which label should apply in thiscircumstance. Rather than attempt a complicated best-fit approach, we adopt asimpler semantics: The firstcase label appearing in a switch block thatapplies to a value is chosen.

static void first(Object obj) {    switch (obj) {        case String s ->            System.out.println("A string: " + s);        case CharSequence cs ->            System.out.println("A sequence of length " + cs.length());        default -> {            break;        }    }}

In this example, if the value ofobj is of typeString then the firstcaselabel will apply; if it is of typeCharSequence but not of typeString thenthe second pattern label will apply.

But what happens if we swap the order of these twocase labels?

static void error(Object obj) {    switch (obj) {        case CharSequence cs ->            System.out.println("A sequence of length " + cs.length());        case String s ->    // Error - pattern is dominated by previous pattern            System.out.println("A string: " + s);        default -> {            break;        }    }}

Now if the value ofobj is of typeString theCharSequencecase labelapplies, since it appears first in the switch block. TheStringcase labelis unreachable in the sense that there is no value of the selector expressionthat would cause it to be chosen. By analogy to unreachable code, this istreated as a programmer error and results in a compile-time error.

More precisely, we say that the firstcase labelcase CharSequence csdominates the secondcase labelcase String s because every value thatmatches the patternString s also matches the patternCharSequence cs, butnot vice versa. This is because the type of the second pattern,String, is asubtype of the type of the first pattern,CharSequence.

An unguarded patterncase label dominates a guarded patterncase label thathas the same pattern. For example, the (unguarded) patterncase labelcase String s dominates the guarded patterncase labelcase String s when s.length() > 0, since every value that matches thecase labelcase String s when s.length() > 0 must match thecase labelcase String s.

A guarded patterncase label dominates another patterncase label (guardedor unguarded) only when both the former's pattern dominates the latter's patternand when its guard is a constant expression of valuetrue. For example, theguarded patterncase labelcase String s when true dominates the patterncase labelcase String s. We do not analyze the guarding expression anyfurther in order to determine more precisely which values match the patternlabel (a problem which is undecidable in general).

A patterncase label can dominate a constantcase label. For example, thepatterncase labelcase Integer i dominates the constantcase labelcase 42, and the patterncase labelcase E e dominates the constantcase labelcase A whenA is a member ofenum class typeE. A guarded patterncase label dominates a constantcase label if the same patterncase labelwithout the guard does. In other words, we do not check the guard, since this isundecidable in general. For example, the patterncase labelcase String s when s.length() > 1 dominates the constantcase labelcase "hello", asexpected; butcase Integer i when i != 0 dominates thecase labelcase 0.

All of this suggests a simple, predictable, and readable ordering ofcase labelsin which the constantcase labels should appear before the guarded patterncase labels, and those should appear before the unguarded patterncaselabels:

Integer i = ...switch (i) {    case -1, 1 -> ...                 // Special cases    case Integer i when i > 0 -> ...  // Positive integer cases    case Integer i -> ...             // All the remaining integers}

The compiler checks allcase labels. It is a compile-time error for acaselabel in a switch block to be dominated by a precedingcase label in thatswitch block. This dominance requirement ensures that if a switch blockcontains only type patterncase labels, they will appear in subtype order.

(The notion of dominance is analogous to conditions on thecatch clauses of atry statement, where it is an error if acatch clause that catches anexception classE is preceded by acatch clause that can catchE or asuperclass ofE (JLS §11.2.3). Logically, the precedingcatchclause dominates the subsequentcatch clause.)

It is also a compile-time error for a switch block of aswitch expression orswitch statement to have more than one match-all switch label. The match-alllabels aredefault and patterncase labels where the pattern unconditionallymatches the selector expression. For example, the type patternString sunconditionally matches a selector expression of typeString, and the typepatternObject o unconditionally matches a selector expression of anyreference type.

1c. Inference of type arguments in record patterns

If a record pattern names a generic record class but gives no type arguments(i.e., the record pattern uses a raw type) then the type arguments are alwaysinferred. For example:

record MyPair<S,T>(S fst, T snd){};static void recordInference(MyPair<String, Integer> pair){    switch (pair) {        case MyPair(var f, var s) ->             ... // Inferred record Pattern MyPair<String,Integer>(var f, var s)        ...    }}

Inference of type arguments for record patterns is supported in all constructsthat support patterns:switch statements and expressions,instanceofexpressions, and enhancedfor statements.

2. Exhaustiveness ofswitch expressions and statements

Aswitch expression requires that all possible values of the selectorexpression be handled in the switch block; in other words, it must beexhaustive.This maintains the property that successful evaluation of aswitch expressionwill always yield a value. For normalswitch expressions, this is enforced bya fairly straightforward set of extra conditions on the switch block.

For patternswitch expressions and statements, we achieve this by defining anotion oftype coverage of switch labels in a switch block. The type coverageof all the switch labels in the switch block is then combined to determine ifthe switch block exhausts all the possibilities of the selector expression.

Consider this (erroneous) patternswitch expression:

static int coverage(Object obj) {    return switch (obj) {         // Error - not exhaustive        case String s -> s.length();    };}

The switch block has only one switch label,case String s. This matches anyvalue ofobj whose type is a subtype ofString. Wetherefore say that the type coverage of this switch label is every subtype ofString. This patternswitch expression is not exhaustive because the typecoverage of its switch block (all subtypes ofString) does not include thetype of the selector expression (Object).

Consider this (still erroneous) example:

static int coverage(Object obj) {    return switch (obj) {         // Error - still not exhaustive        case String s  -> s.length();        case Integer i -> i;    };}

The type coverage of this switch block is the union of the coverage of its twoswitch labels. In other words, the type coverage is the set of all subtypes ofString and the set of all subtypes ofInteger. But, again, the type coveragestill does not include the type of the selector expression, so this patternswitch expression is also not exhaustive and causes a compile-time error.

The type coverage of adefault label is all types, so this example is (atlast!) legal:

static int coverage(Object obj) {    return switch (obj) {        case String s  -> s.length();        case Integer i -> i;        default -> 0;    };}

If the type of the selector expression is a sealed class(JEP 409) then the type coverage check can take into account thepermits clause of the sealed class to determine whether a switch block isexhaustive. This can sometimes remove the need for adefault clause. Considerthe following example of asealed interfaceS with three permittedsubclassesA,B, andC:

sealed interface S permits A, B, C {}final class A implements S {}final class B implements S {}record C(int i) implements S {}  // Implicitly finalstatic int testSealedExhaustive(S s) {    return switch (s) {        case A a -> 1;        case B b -> 2;        case C c -> 3;    };}

The compiler can determine that the type coverage of the switch block is thetypesA,B, andC. Since the type of the selector expression,S, is asealed interface whose permitted subclasses are exactlyA,B, andC, thisswitch block is exhaustive. As a result, nodefault label is needed.

Some extra care is needed when a permitted direct subclass only implements aspecific parameterization of a (generic)sealed superclass. For example:

sealed interface I<T> permits A, B {}final class A<X> implements I<String> {}final class B<Y> implements I<Y> {}static int testGenericSealedExhaustive(I<Integer> i) {    return switch (i) {        // Exhaustive as no A case possible!        case B<Integer> bi -> 42;    }}

The only permitted subclasses ofI areA andB, but the compiler candetect that the switch block need only cover the classB to be exhaustivesince the selector expression is of typeI<Integer>.

This condition of exhaustiveness applies to both patternswitch expressionsand patternswitchstatements. To ensure backward compatibility, allexistingswitch statements will compile unchanged. But if aswitch statementuses any of theswitch enhancements described in this JEP then the compilerwill check that it is exhaustive. (Future compilers of the Java language mayemit warnings for legacyswitch statements that are not exhaustive.)

More precisely, exhaustiveness is required of anyswitch statement that usespattern ornull labels or whose selector expression is not one of the legacytypes (char,byte,short,int,Character,Byte,Short,Integer,String, or anenum type). For example:

sealed interface S permits A, B, C {}final class A implements S {}final class B implements S {}record C(int i) implements S {}  // Implicitly finalstatic void switchStatementExhaustive(S s) {    switch (s) {    // Error - not exhaustive;                    // missing clause for permitted class B!        case A a :            System.out.println("A");            break;        case C c :            System.out.println("C");            break;    };}

Making most switches exhaustive is just a matter of adding a simpledefaultclause at the end of the switch block. This leads to clearer and easier toverify code. For example, the following patternswitch statement is notexhaustive and is erroneous:

Object obj = ...switch (obj) {    // Error - not exhaustive!    case String s:        System.out.println(s);        break;    case Integer i:        System.out.println("Integer");        break;}

It can be made exhaustive trivially:

Object obj = ...switch (obj) {    case String s:        System.out.println(s);        break;    case Integer i:        System.out.println("Integer");        break;    default:    // Now exhaustive!        break;}

The notion of exhaustiveness is made more complicated byrecord patterns(JEP 432) since these patterns support the nesting of otherpatterns inside them. Accordingly, the notion of exhaustiveness has to reflectthis potentially recursive structure.

3. Scope of pattern variable declarations

Pattern variables (JEP 394) are local variables that aredeclared by patterns. Pattern variable declarations are unusual in that theirscope isflow-sensitive. As a recap consider the following example, where thetype patternString s declares the pattern variables:

static void test(Object obj) {    if ((obj instanceof String s) && s.length() > 3) {        System.out.println(s);    } else {        System.out.println("Not a string");    }}

The declaration ofs is in scope in the right-hand operand of the&&expression, as well as in the "then" block. However, it is not in scope in the"else" block: In order for control to transfer to the "else" block the patternmatch must fail, in which case the pattern variable will not have beeninitialized.

We extend this flow-sensitive notion of scope for pattern variable declarationsto encompass pattern declarations occurring incase labels with three newrules:

  1. The scope of a pattern variable declaration which occurs in a switch labelincludes anywhen clause of that label.

  2. The scope of a pattern variable declaration which occurs in acase label ofaswitch rule includes the expression, block, orthrow statement thatappears to the right of the arrow.

  3. The scope of a pattern variable declaration which occurs in acase label ofaswitch labeled statement group includes the block statements of thestatement group. Falling through acase label that declares a patternvariable is forbidden.

This example shows the first rule in action:

static void test(Object obj) {    switch (obj) {        case Character c        when c.charValue() == 7:            System.out.println("Ding!");            break;        default:            break;        }    }}

The scope of the declaration of the pattern variablec includes thewhenexpression of the switch label.

This variant shows the second rule in action:

static void test(Object obj) {    switch (obj) {        case Character c -> {            if (c.charValue() == 7) {                System.out.println("Ding!");            }            System.out.println("Character");        }        case Integer i ->            throw new IllegalStateException("Invalid Integer argument: "                                            + i.intValue());        default -> {            break;        }    }}

Here the scope of the declaration of the pattern variablec is the block tothe right of the first arrow. The scope of the declaration of the patternvariablei is thethrow statement to the right of the second arrow.

The third rule is more complicated. Let us first consider an example wherethere is only onecase label for aswitch labeled statement group:

static void test(Object obj) {    switch (obj) {        case Character c:            if (c.charValue() == 7) {                System.out.print("Ding ");            }            if (c.charValue() == 9) {                System.out.print("Tab ");            }            System.out.println("Character");        default:            System.out.println();    }}

The scope of the declaration of the pattern variablec includes all thestatements of the statement group, namely the twoif statements andtheprintln statement. The scope does not include the statements of thedefault statement group, even though the execution of the first statementgroup can fall through thedefault switch label and execute these statements.

We forbid the possibility of falling through acase label that declares apattern variable. Consider this erroneous example:

static void test(Object obj) {    switch (obj) {        case Character c:            if (c.charValue() == 7) {                System.out.print("Ding ");            }            if (c.charValue() == 9) {                System.out.print("Tab ");            }            System.out.println("character");        case Integer i:                 // Compile-time error            System.out.println("An integer " + i);        default:            break;    }}

If this were allowed and the value ofobj were aCharacter then execution of the switch block could fall through the secondstatement group (aftercase Integer i:) where the pattern variablei wouldnot have been initialized. Allowing execution to fall through acase label thatdeclares a pattern variable is therefore a compile-time error.

This is why a switch label consisting of multiple pattern labels, e.g.case Character c: case Integer i: ..., is not permitted. Similar reasoning appliesto the prohibition of multiple patterns within a singlecase label: Neithercase Character c, Integer i: ... norcase Character c, Integer i -> ... isallowed. If suchcase labels were allowed then bothc andi would be inscope after the colon or arrow, yet only one of them would have been initializeddepending on whether the value ofobj was aCharacter or anInteger.

On the other hand, falling through a label that does not declare a patternvariable is safe, as this example shows:

void test(Object obj) {    switch (obj) {        case String s:            System.out.println("A string");        default:            System.out.println("Done");    }}

4. Dealing withnull

Traditionally, aswitch throwsNullPointerException if the selectorexpression evaluates tonull. This is well-understood behavior and we do notpropose to change it for any existingswitch code.

However, given that there is a reasonable and non-exception-bearing semanticsfor pattern matching andnull values, we have the opportunity to make patternswitch morenull-friendly while remaining compatible with existingswitchsemantics.

First, we introduce a newnullcase label . We then lift the blanketrule that aswitch immediately throwsNullPointerException if the value ofthe selector expression isnull. Instead, we inspect thecase labels todetermine the behavior of aswitch:

For example, given the declaration below, evaluatingtest(null) will printnull! rather than throwNullPointerException:

static void test(Object obj) {    switch (obj) {        case null     -> System.out.println("null!");        case String s -> System.out.println("String");        default       -> System.out.println("Something else");    }}

This new behavior aroundnull is as if the compiler automaticallyenriches the switch block with acase null whose body throwsNullPointerException. In other words, this code:

static void test(Object obj) {    switch (obj) {        case String s  -> System.out.println("String: " + s);        case Integer i -> System.out.println("Integer");        default        -> System.out.println("default");    }}

is equivalent to:

static void test(Object obj) {    switch (obj) {        case null      -> throw new NullPointerException();        case String s  -> System.out.println("String: "+s);        case Integer i -> System.out.println("Integer");        default        -> System.out.println("default");    }}

In both examples, evaluatingtest(null) will causeNullPointerException tobe thrown.

We preserve the intuition from the existingswitch construct that performing aswitch overnull is an exceptional thing to do. The difference in a patternswitch is that you have a mechanism to directly handle this case inside theswitch rather than outside. If you see anull label in a switch block thenthis label will match anull value. If you do not see anull label in aswitch block then switching over anull value will throwNullPointerException, as before.

It is also meaningful, and not uncommon, to want to combine anull case with adefault. To that end we allow anull case label to have an optionaldefault; for example:

Object obj = ...switch (obj) {    ...    case null, default ->        System.out.println("The rest (including null)");}

The value ofobj matches this label if either it is the null reference value,or none of the othercase labels match.

It is a compile-time error for a switch block to have both anullcase label with adefault and adefault label.

5. Errors

Pattern matching can complete abruptly. For example, when matching avalue against a record pattern, the record’s accessor method can completeabruptly. In this case, pattern matching is defined to complete abruptlyby throwing aMatchException. If such a pattern appears as a label in aswitch then theswitch will also complete abruptly by throwing aMatchException.

If a pattern is guarded with awhen expression, and evaluating thewhenexpression completes abruptly, then theswitch completes abruptly for the samereason.

If no label in a patternswitch matches the value of the selectorexpression then theswitch completes abruptly by throwing aMatchException, since pattern switches must be exhaustive.

For example:

record R(int i){    public int i(){      // accessor method for i        return i / 0;    }}static void exampleAnR(R r) {    switch(r) {        case R(var i): System.out.println(i);    }}

The invocationexampleAnR(new R(42)) causes aMatchException to bethrown.

By contrast:

static void example(Object obj) {    switch (obj) {        case R r when (r.i / 0 == 1): System.out.println("It's an R!");        default: break;    }}

The invocationexample(new R(42)) causes anArithmeticExceptionto be thrown.

To align with patternswitch semantics,switch expressions over anenum class now throwMatchException rather thanIncompatibleClassChangeError when no switch label applies at runtime. This is a minor incompatible change to the language.

Future work

Alternatives

Dependencies

This JEP builds on pattern matching forinstanceof (JEP 394) andalso the enhancements offered byswitch expressions (JEP 361). Whenthe Record Patterns preview feature (JEP 432) is finalized, the resultingimplementation will likely make use of dynamic constants (JEP 309).

OpenJDK logo
Installing
Contributing
Sponsoring
Developers' Guide
Vulnerabilities
JDK GA/EA Builds
Mailing lists
Wiki ·IRC
Mastodon
Bluesky
Bylaws ·Census
Legal
Workshop
JEP Process
Source code
GitHub
Mercurial
Tools
Git
jtreg harness
Groups
(overview)
Adoption
Build
Client Libraries
Compatibility & Specification Review
Compiler
Conformance
Core Libraries
Governing Board
HotSpot
IDE Tooling & Support
Internationalization
JMX
Members
Networking
Porters
Quality
Security
Serviceability
Vulnerability
Web
Projects
(overview,archive)
Amber
Babylon
CRaC
Code Tools
Coin
Common VM Interface
Developers' Guide
Device I/O
Duke
Galahad
Graal
IcedTea
JDK 8 Updates
JDK 9
JDK (…,24,25,26)
JDK Updates
JMC
Jigsaw
Kona
Lanai
Leyden
Lilliput
Locale Enhancement
Loom
Memory Model Update
Metropolis
Multi-Language VM
Nashorn
New I/O
OpenJFX
Panama
Penrose
Port: AArch32
Port: AArch64
Port: BSD
Port: Haiku
Port: Mac OS X
Port: MIPS
Port: Mobile
Port: PowerPC/AIX
Port: RISC-V
Port: s390x
SCTP
Shenandoah
Skara
Sumatra
Tsan
Valhalla
Verona
VisualVM
Wakefield
Zero
ZGC
Oracle logo
© 2025 Oracle Corporation and/or its affiliates
Terms of Use · License:GPLv2 ·Privacy ·Trademarks

[8]ページ先頭

©2009-2025 Movatter.jp