Summary
Enhance pattern matching by allowing primitive type patterns to be used in all pattern contexts, align the semantics of primitive type patterns with instanceof, and extend switch to allow primitive constants as case labels. This is a preview language feature.
Problem
Nested primitive type patterns are limited
Disaggregation with type patterns that involve primitive types is currently invariant. For example, assume a JSON model can be encoded with a sealed hierarchy according to its specification as follows:
sealed interface JsonValue {
record JsonString(String s) implements JsonValue { }
record JsonNumber(double d) implements JsonValue { }
record JsonNull() implements JsonValue { }
record JsonBoolean(boolean b) implements JsonValue { }
record JsonArray(List<JsonValue> values) implements JsonValue { }
record JsonObject(Map<String, JsonValue> pairs) implements JsonValue { }
}
With respect to numbers JSON does not distinguish integers from non-integers, so
in JsonNumber
we represent all numbers with double
values as recommended by the specification.
Given a JSON payload of
{ "name" : "John", "age" : 30 }
we can construct a corresponding JsonValue
via
var json = new JsonObject(Map.of("name", new JsonString("John")
"age", new JsonNumber(30)));
For each key in the map, this code instantiates an appropriate record for the
corresponding value. For the first, the value "John"
has the same type as the
record's component, namely String
. For the second, however, the Java compiler
applies a widening primitive conversion to convert the int
value, 30, to a
double
.
What we would really like to do is use int
directly in the JsonNumber
pattern such that the pattern matches only when the double
value inside the
JsonNumber
object can be converted to an int
without loss of information,
and when it does match it automatically narrows the double
value to an int
:
if (json instanceof JsonObject(var map)
&& map.get("name") instanceof JsonString(String name)
&& map.get("age") instanceof JsonNumber(int age))
{
return new Customer(name, age);
}
Primitive type patterns are not permitted in top-level context of instanceof
Primitive types patterns cannot be used at top-level contexts of instanceof
at
all; only type patterns of reference types are allowed in top-level contexts.
Since the pattern matching instanceof
operator safeguards cast conversions
about reference types, lifting restrictions to primitive type patterns, means
that instanceof
can now be able to safeguard any cast conversion supported
by Java (JLS 5.5), at
top-level too. In the following example, instanceof
with a primitive type
pattern byte b
implies that instanceof
safeguards whether i
can be safely
cast to byte
without loss of information. If instanceof
returns true, it
means that (byte) i
will not lead to loss of information about e.g., magnitude
and sign:
int i = ...
if (i instanceof byte b) {
... b ...
}
In this particular case of these two types, checking whether int
can be safely
cast to byte
necessitates a run-time test of exactness. There are other
pairs that do not need a run-time check and a conversion is guaranteed only by
inspecting the two types involved. For example an int
to long
is always
safe. A conversion between these two is unconditionally exact.
Primitive types are not permitted in type comparisons
Extending instanceof
as the pattern matching operator means that the semantics
for the type comparison operator instanceof
, can be enhanced, symmetrically.
int i = ...
if (i instanceof byte) {
...
}
Primitive type patterns are not permitted in top-level context of switch
At present, primitive type patterns are not allowed at a top-level context of a
switch either. For example, with a top-level primitive type pattern Java users could
rewrite the switch
expression (JEP 361)
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
default -> "unknown status: " + x.getStatus();
}
more clearly as:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
Here, the case int i
label matches any status value not previously matched,
making the switch
expression exhaustive so that no default
label is
required.
switch
does not support all primitive types
Prior to this JEP, switch
expressions and switch
statements could switch on
some primitive types — but not boolean
, float
, double
, or long
. For
example a switch involving long
s:
long v = ...;
switch (v) {
case 0x01 -> ...;
case 0x02 -> ...;
case 10_000_000_000L -> ...;
case 20_000_000_000L -> ...;
case long l -> ... l ...;
}
With float
values in case
labels users will be able to switch on
floating-point match candidates:
float f = ...;
switch (f) {
case Float.NaN -> ...;
case Float.POSITIVE_INFINITY -> ...;
case Float.NEGATIVE_INFINITY -> ...;
case float g -> ... g ...;
}
With this proposal a switch
-boolean will also be possible:
boolean u = user.isLoggedIn();
switch (u) {
case true -> user.id();
case false -> { log("Unrecognized user"); yield -1; }
}
Solution
The Java language is enhanced as follows:
- Allow primitive type patterns to be used in nested positions, even if they do
not spell-out the same type of the corresponding record component:
- Derive the semantics of primitive type patterns (and reference type patterns on targets of primitive type) from casting conversions (A primitive type pattern T is applicable to a type U if there is a casting conversion).
- Allow
switch
to support primitive types:- Add support for constant expressions of
long
,float
,double
,boolean
, and their boxes. - Allow boolean switches to be exhaustive when both true and false cases are listed.
- Incorporate primitive patterns in the exhaustiveness check: a) when a boolean type pattern is used and b) when a primitive type pattern is unconditional to the underlying type of a match candidate which has a boxed type.
- Add support for constant expressions of
- Allow
instanceof
to support primitive types:- Update the grammar for
instanceof
as a type comparison operator to accept any type as its RHS. - Add support for
instanceof
according to all supported pairs of conversions under a casting context.instanceof
is the precondition test for safe casting in general (of an exact conversion). - Define exactness of conversions:
- Define unconditionally exact conversions for some pair of types (no run-time action needed).
- Define the run-time actions to decide exactness for the rest.
- Update the grammar for
- Translate
instanceof
inTransPatterns
to invoke the exactness tests when needed. - Allow the
switch
translation inSwitchBootstraps
to call the exactness tests when needed.
Specification
The updated JLS draft for primitive types in patterns, instanceof, and switch is linked as https://cr.openjdk.org/~abimpoudis/instanceof/latest/ and attached as JLS-instanceof.pdf.
The proposed API enhancements are attached as 8304487.specdiff..zip
The changes to the specification and API are a subject of change until the CSR is finalized.
- csr of
-
JDK-8303374 Implement JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)
-
- Resolved
-