Movatterモバイル変換


[0]ホーム

URL:


JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)

OwnerAngelos Bimpoudis
TypeFeature
ScopeSE
StatusClosed / Delivered
Release23
Componentspecification / language
Discussionamber dash dev at openjdk dot org
EffortM
DurationM
Relates toJEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
Reviewed byAlex Buckley, Brian Goetz
Endorsed byBrian Goetz
Created2022/06/15 10:05
Updated2025/02/25 16:30
Issue8288476

Summary

Enhance pattern matching by allowing primitive type patterns in allpattern contexts, and extendinstanceof andswitch to work withall primitive types. This is apreview language feature.

Goals

Non-Goals

Motivation

Multiple restrictions pertaining to primitive types impose friction when usingpattern matching,instanceof, andswitch. Eliminating these restrictionswould make the Java language more uniform and more expressive.

Pattern matching forswitch does not support primitive type patterns

The first restriction is that pattern matching forswitch(JEP 441) does not support primitive typepatterns, i.e., type patterns that specify a primitive type. Only type patternsthat specify a reference type are supported, such ascase Integer i orcase String s. (Since Java 21, record patterns(JEP 440) are also supported forswitch.)

With support for primitive type patterns inswitch, we could improvetheswitch expression

switch (x.getStatus()) {    case 0 -> "okay";    case 1 -> "warning";    case 2 -> "error";    default -> "unknown status: " + x.getStatus();}

by turning thedefault clause into acase clause with a primitivetype pattern that exposes the matched value:

switch (x.getStatus()) {    case 0 -> "okay";    case 1 -> "warning";    case 2 -> "error";    case int i -> "unknown status: " + i;}

Supporting primitive type patterns would also allow guards to inspectthe matched value:

switch (x.getYearlyFlights()) {    case 0 -> ...;    case 1 -> ...;    case 2 -> issueDiscount();    case int i when i >= 100 -> issueGoldCard();    case int i -> ... appropriate action when i > 2 && i < 100 ...}

Record patterns have limited support for primitive types

Another restriction is that record patterns have limited support for primitivetypes. Record patterns streamline data processing by decomposing a record intoits individual components. When a component is a primitive value, the recordpattern must be precise about the type of the value. This is inconvenient fordevelopers and inconsistent with the presence of helpful automatic conversionsin the rest of the Java language.

For example, suppose we wish to process JSON data represented viathese record classes:

sealed interface JsonValue {    record JsonString(String s) implements JsonValue { }    record JsonNumber(double d) implements JsonValue { }    record JsonObject(Map<String, JsonValue> map) implements JsonValue { }}

JSON does not distinguish integers from non-integers, soJsonNumberrepresents a number with adouble component for maximum flexibility.However, we do not need to pass adouble when creating aJsonNumber record; we can pass anint such as30, and the Javacompiler automatically widens theint todouble:

var json = new JsonObject(Map.of("name", new JsonString("John"),                                 "age",  new JsonNumber(30)));

Unfortunately, the Java compiler is not so obliging if we wish todecompose aJsonNumber with a record pattern. SinceJsonNumberis declared with adouble component, we must decompose aJsonNumber with respect todouble, and convert toint manually:

if (json instanceof JsonObject(var map)    && map.get("name") instanceof JsonString(String n)    && map.get("age")  instanceof JsonNumber(double a)) {    int age = (int)a;  // unavoidable (and potentially lossy!) cast}

In other words, primitive type patterns can be nested inside record patternsbut are invariant: The primitive type in the pattern must be identical to theprimitive type of the record component. It is not possible to decompose aJsonNumber viainstanceof JsonNumber(int age) and have the compilerautomatically narrow thedouble component toint.

The reason for this limitation is that narrowing might be lossy: The value ofthedouble component at run time might be too large, or have too muchprecision, for anint variable. However, a key benefit of pattern matching isthat it rejects illegal values automatically, by simply not matching. If thedouble component of aJsonNumber is too large or too precise to narrowsafely to anint, theninstanceof JsonNumber(int age) could simply returnfalse, leaving the program to handle a largedouble component in adifferent branch.

This is how pattern matching already works for reference typepatterns. For example:

record Box(Object o) {}var b = new Box(...);if (b instanceof Box(RedBall rb)) ...else if (b instanceof Box(BlueBall bb)) ...else ....

Here the component ofBox is declared to be of typeObject, butinstanceof can be used to try to match aBox with aRedBall component oraBlueBall component. The record patternBox(RedBall rb) matches only ifb is aBox at run time and itso component can be narrowed toRedBall;similarly,Box(BlueBall bb) matches only if itso component can be narrowedtoBlueBall.

In record patterns, primitive type patterns should work as smoothly asreference type patterns, allowingJsonNumber(int age) even if thecorresponding record component is a numeric primitive type other thanint. This would eliminate the need for verbose and potentially lossycasts after matching the pattern.

Pattern matching forinstanceof does not support primitive types

Yet another restriction is that pattern matching forinstanceof(JEP 394) does not support primitive typepatterns. Only type patterns that specify a reference type aresupported. (Since Java 21, record patterns are also supported forinstanceof.)

Primitive type patterns would be just as useful ininstanceof as they are inswitch. The purpose ofinstanceof is, broadly speaking, to test whether avalue can be converted safely to a given type; this is why we always seeinstanceof and cast operations in close proximity. This test is critical forprimitive types because of the potential loss of information that can occurwhen converting primitive values from one type to another.

For example, converting anint value to afloat is performed automaticallyby an assignment statement even though it is potentially lossy — and thedeveloper receives no warning of this:

int getPopulation() {...}float pop = getPopulation();  // silent potential loss of information

Meanwhile, converting anint value to abyte is performed with anexplicit cast, but the cast is potentially lossy so it must bepreceded by a laborious range check:

if (i >= -128 && i <= 127) {    byte b = (byte)i;    ... b ...}

Primitive type patterns ininstanceof would subsume the lossy conversionsbuilt into the Java language and avoid the painstaking range checks thatdevelopers have been coding by hand for almost three decades. In other words,instanceof could check values as well as types. The two examples above couldbe rewritten as follows:

if (getPopulation() instanceof float pop) {    ... pop ...}if (i instanceof byte b) {    ... b ...}

Theinstanceof operator combines the convenience of an assignment statementwith the safety of pattern matching. If the input (getPopulation() ori)can be converted safely to the type in the primitive type pattern then thepattern matches and the result of the conversion is immediately available(pop orb). But, if the conversion would lose information then the patterndoes not match and the program should handle the invalid input in a differentbranch.

Primitive types ininstanceof andswitch

If we are going to lift restrictions around primitive type patterns then itwould be helpful to lift a related restriction: Wheninstanceof takes atype, rather than a pattern, it takes only a reference type, not a primitivetype. When taking a primitive type,instanceof would check if the conversion issafe but would not actually perform it:

if (i instanceof byte) {  // value of i fits in a byte    ... (byte)i ...       // traditional cast required}

This enhancement toinstanceof restores alignment between the semantics ofinstanceof T andinstanceof T t, which would be lost if we allowedprimitive types in one context but not the other.

Finally, it would be helpful to lift the restriction thatswitch can takebyte,short,char, andint values but notboolean,float,double,orlong values.

Switching onboolean values would be a useful alternative to the ternaryconditional operator (?:) because aboolean switch can contain statementsas well as expressions. For example, the following code uses aboolean switchto perform some logging whenfalse:

startProcessing(OrderStatus.NEW, switch (user.isLoggedIn()) {    case true  -> user.id();    case false -> { log("Unrecognized user"); yield -1; }});

Switching onlong values would allowcase labels to belong constants,obviating the need to handle very large constants with separateifstatements:

long v = ...;switch (v) {    case 1L              -> ...;    case 2L              -> ...;    case 10_000_000_000L -> ...;    case 20_000_000_000L -> ...;    case long x          -> ... x ...;}

Description

In Java 21, primitive type patterns are permitted only as nested patterns inrecord patterns, as in:

v instanceof JsonNumber(double a)

To support the more uniform data exploration of a match candidatev with patternmatching, we will:

  1. Extend pattern matching so that primitive type patterns are applicable to awider range of match candidate types. This will allow expressions such asv instanceof JsonNumber(int age).

  2. Enhance theinstanceof andswitch constructs to support primitive typepatterns as top level patterns.

  3. Further enhance theinstanceof construct so that, when used for typetesting rather than pattern matching, it can test against all types, not justreference types. This will extendinstanceof's current role, as theprecondition for safe casting on reference types, to apply to all types.

    More broadly, this means thatinstanceof can safeguard all conversions,whether the match candidate is having its type tested (e.g.,x instanceof int, ory instanceof String) or having its value matched (e.g.,x instanceof int i, ory instanceof String s).

  4. Further enhance theswitch construct so that it works with all primitivetypes, not just a subset of theintegral primitivetypes.

We achieve these changes by altering a small number of rules in the Javalanguage that govern the use of primitive types:

Safety of conversions

A conversion isexact if no loss of information occurs. Whether a conversionis exact depends on the pair of types involved and on the input value:

In brief, a conversion between primitive types is unconditionally exact if itwidens from one integral type to another, or from one floating-point type toanother, or frombyte,short, orchar to a floating-point type, or fromint todouble. Furthermore, boxing conversions and widening referenceconversions are unconditionally exact.

The following table denotes the conversions that are permitted betweenprimitive types. Unconditionally exact conversions are denoted withthe symbol ɛ. The symbol means the identityconversion,ω means a widening primitive conversion,η means a narrowing primitive conversion, andωη means awidening and narrowing primitive conversion. The symbol means noconversion is allowed.

To →byteshortcharintlongfloatdoubleboolean
From ↓
byteɛωηɛɛɛɛ
shortηηɛɛɛɛ
charηηɛɛɛɛ
intηηηɛωɛ
longηηηηωω
floatηηηηηɛ
doubleηηηηηη
boolean

Comparing this table to its equivalent inJLS §5.5,it can be seen that many of the conversions permitted byω in JLS §5.5are "upgraded" to the unconditionally exactɛ above.

instanceof as the precondition for safe casting

Type tests withinstanceof are traditionally limited to reference types. Theclassic meaning ofinstanceof is a precondition check that asks: Would it besafe and useful to cast this value to this type? This question is even morepertinent to primitive types than to reference types. For reference types, ifthe check is accidentally omitted then performing an unsafe cast will likely dono harm: AClassCastException will be thrown and the improperly cast valuewill be unusable. In contrast, for primitive types, where there is noconvenient way to check for safety, performing an unsafe cast will likely causesubtle bugs. Instead of throwing an exception, it can silently lose informationsuch as magnitude, sign, or precision, allowing the improperly cast value toflow into the rest of the program.

To enable primitive types in theinstanceof type test operator, we remove therestrictions (JLS §15.20.2)that the type of the left-hand operand must be a reference type and that theright-hand operand must specify a reference type. The type test operatorbecomes

InstanceofExpression:    RelationalExpression instanceof Type    ...

At run time, we extendinstanceof to primitive types by appealing to exactconversions: If the value on the left-hand side can be converted to the type onthe right-hand side via an exact conversion then it would be safe to cast thevalue to that type, andinstanceof reportstrue.

Here are some examples of how the extendedinstanceof can safeguard casting.Unconditionally exact conversions returntrue regardless of the input value;all other conversions require a run-time test whose result is shown.

byte b = 42;b instanceof int;         // true (unconditionally exact)int i = 42;i instanceof byte;        // true (exact)int i = 1000;i instanceof byte;        // false (not exact)int i = 16_777_217;       // 2^24 + 1i instanceof float;       // false (not exact)i instanceof double;      // true (unconditionally exact)i instanceof Integer;     // true (unconditionally exact)i instanceof Number;      // true (unconditionally exact)float f = 1000.0f;f instanceof byte;        // falsef instanceof int;         // true (exact)f instanceof double;      // true (unconditionally exact)double d = 1000.0d;d instanceof byte;        // falsed instanceof int;         // true (exact)d instanceof float;       // true (exact)Integer ii = 1000;ii instanceof int;        // true (exact)ii instanceof float;      // true (exact)ii instanceof double;     // true (exact)Integer ii = 16_777_217;ii instanceof float;      // false (not exact)ii instanceof double;     // true (exact)

We do not add any new conversions to the Java language, nor change existingconversions, nor change which conversions are allowed in existing contexts suchas assignment. Whetherinstanceof is applicable to a given value and type isdetermined by whether a conversion is allowed in a casting context and whetherit is exact. For example,b instanceof char is never allowed ifb is aboolean variable, because there is no casting conversion fromboolean tochar.

Primitive type patterns ininstanceof andswitch

A type pattern merges a type test with a conditional conversion. This avoidsthe need for an explicit cast if the type test succeeds, while the uncast valuecan be handled in a different branch if the type test fails. When theinstanceof type test operator supported only reference types, it was naturalthat only reference type patterns were allowed ininstanceof andswitch;now that theinstanceof type test operator supports primitive types, it isnatural to allow primitive type patterns ininstanceof andswitch.

To achieve this, we drop the restriction that primitive types cannot be used ina top level type pattern. As a result, the laborious and error-prone code

int i = 1000;if (i instanceof byte) {    // false -- i cannot be converted exactly to byte    byte b = (byte)i;       // potentially lossy    ... b ...}

can be written as

if (i instanceof byte b) {    ... b ...               // no loss of information}

becausei instanceof byte b means "test ifi instanceof byte and, if so,casti tobyte and bind that value tob".

The semantics of type patterns are defined by three predicates: applicability,unconditionality, and matching. We lift restrictions on the treatmentof primitive type patterns as follows:

Exhaustiveness

Aswitch expression, or aswitch statement whosecase labels arepatterns, is required to beexhaustive: All possible values of the selectorexpression must be handled in theswitch block. Aswitch is exhaustive ifit contains an unconditional type pattern; it can be exhaustive for otherreasons as well, such as covering all possible permitted subtypes of a sealedclass. In some situations, aswitch can be deemed exhaustive even when thereare possible run-time values that will not be matched by anycase; in suchsituations the Java compiler inserts a syntheticdefault clause to handlethese unanticipated inputs. Exhaustiveness is covered in greater detail inPatterns: Exhaustiveness, Unconditionality, and Remainder.

With the introduction of primitive type patterns, we add one new rule to thedetermination of exhaustiveness: Given aswitch whose match candidate is awrapper typeW for some primitive typeP, a type patternT t exhaustsWifT is unconditionally exact onP. In that case,null becomes part ofthe remainder. In the following example, the match candidate is a wrapper typeof the primitive typebyte and the conversion frombyte toint isunconditionally exact. As a result the followingswitch is exhaustive:

Byte b = ...switch (b) {             // exhaustive switch    case int p -> 0;}

This behavior is similar to the exhaustiveness treatment of record patterns.

Just asswitch uses pattern exhaustiveness to determine if the casescover all input values,switch uses dominance to determine if thereare any cases that will match no input values.

One patterndominates another pattern if it matches all the values that theother pattern matches. For example, the type patternObject o dominates thetype patternString s because everything that would matchString s wouldalso matchObject o. In aswitch, it is illegal for acase label with anunguarded type patternP to precede a case label with type patternQ ifPdominatesQ. The meaning of dominance is unchanged: A type patternT tdominates a type patternU u ifT t would be unconditional on a matchcandidate of typeU.

Expanded primitive support inswitch

We enhance theswitch construct to allow a selector expression of typelong,float,double, andboolean, as well as the corresponding boxedtypes.

If the selector expression has typelong,float,double, orboolean,any constants used in case labels must have the same type as the selectorexpression, or its corresponding boxed type. For example, if the type of theselector expression isfloat orFloat then anycase constants must befloating-point literals (JLS §3.10.2)of typefloat. This restriction is required because mismatches betweencaseconstants and the selector expression could introduce lossy conversions,undermining programmer intent. The followingswitch is legal, but it would beillegal if the0f constant were accidentally written as0.

float v = ...switch (v) {    case 0f -> 5f;    case float x when x == 1f -> 6f + x;    case float x -> 7f + x;}

The semantics of floating-point literals incase labels is defined in termsofrepresentation equivalence at compile time and run time. It is acompile-time error to use two floating-point literals that are representationequivalent. For example, the followingswitch is illegal because the literal0.999999999f is rounded up to1.0f, creating a duplicatecase label.

float v = ...switch (v) {    case 1.0f -> ...    case 0.999999999f -> ...    // error: duplicate label    default -> ...}

Since theboolean type has only two distinct values, aswitch that listsboth thetrue andfalse cases is considered exhaustive. The followingswitch is legal, but it would be illegal if there were adefault clause.

boolean v = ...switch (v) {    case true -> ...    case false -> ...    // Alternatively: case true, false -> ...}
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