JEP 375: Pattern Matching for instanceof (Second Preview)
Author | Brian Goetz |
Owner | Jan Lahoda |
Type | Feature |
Scope | SE |
Status | Closed / Delivered |
Release | 15 |
Component | specification / language |
Discussion | amber dash dev at openjdk dot java dot net |
Effort | M |
Duration | M |
Relates to | JEP 305: Pattern Matching for instanceof (Preview) |
JEP 394: Pattern Matching for instanceof | |
Reviewed by | Alex Buckley |
Endorsed by | Mark Reinhold |
Created | 2019/12/02 15:00 |
Updated | 2021/08/28 00:19 |
Issue | 8235186 |
Summary
Enhance the Java programming language with pattern matching for the instanceof
operator. Pattern matching allows common logic in a program, namely the conditional extraction of components from objects, to be expressed more concisely and safely. This is a preview language feature in JDK 15.
History
Pattern matching for instanceof
was proposed by JEP 305 in mid 2017, and targeted to JDK 14 in late 2019 as a preview language feature. This JEP proposes to re-preview the feature in JDK 15, with no changes relative to the preview in JDK 14, in order to gather additional feedback.
Motivation
Nearly every program includes some sort of logic that combines testing if an expression has a certain type or structure, and then conditionally extracting components of its state for further processing. For example, all Java programmers are familiar with the instanceof-and-cast idiom:
if (obj instanceof String) {
String s = (String) obj;
// use s
}
There are three things going on here: a test (is obj
a String
?), a
conversion (casting obj
to String
), and the declaration of a new
local variable (s
) so we can use the string value. This pattern is
straightforward and understood by all Java programmers, but is
suboptimal for several reasons. It is tedious; doing both the type
test and cast should be unnecessary (what else would you do after an
instanceof
test?). This boilerplate -- in particular, the three
occurrences of the type String
--- obfuscates the more significant
logic that follows. But most importantly, the repetition provides
opportunities for errors to creep unnoticed into programs.
Rather than reach for ad-hoc solutions, we believe it is time for Java to embrace pattern matching. Pattern matching allows the desired 'shape' of an object to be expressed concisely (the pattern), and for various statements and expressions to test that 'shape' against their input (the matching). Many languages, from Haskell to C#, have embraced pattern matching for its brevity and safety.
Description
A pattern is a combination of (1) a predicate that can be applied to a target, and (2) a set of binding variables that are extracted from the target only if the predicate successfully applies to it.
A type test pattern consists of a predicate that specifies a type, along with a single binding variable.
The instanceof
operator
(JLS 15.20.2)
is extended to take a type test pattern instead of just a type. In the
code below, the phrase String s
is the type test pattern:
if (obj instanceof String s) {
// can use s here
} else {
// can't use s here
}
The instanceof
operator "matches" the target obj
to the type test
pattern as follows: if obj
is an instance of String
, then it is cast
to String
and assigned to the binding variable s
. The binding variable is in
scope in the true block of the if
statement, and not in the false block of the if
statement.
The scope of a binding variable, unlike the scope of a local variable, is determined by the semantics of the containing expressions and statements. For example, in this code:
if (!(obj instanceof String s)) {
.. s.contains(..) ..
} else {
.. s.contains(..) ..
}
the s
in the true block refers to a field in the enclosing class, and the s
in the false block refers to the binding variable introduced by the instanceof
operator.
When the conditional of the if
statement grows more complicated than a single instanceof
, the scope of the binding variable grows accordingly. For example, in this code:
if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..}
the binding variable s
is in scope on the right hand side of the &&
operator, as well as in the true block. (The right hand side is only evaluated if instanceof
succeeded and assigned to s
.) On the other hand, in this code:
if (obj instanceof String s || s.length() > 5) {.. s.contains(..) ..}
the binding variable s
is not in scope on the right hand side of the || operator, nor is it in scope in the true block. (s
at these points refers to a field in the enclosing class.)
There are no changes to how instanceof
works when the target is null.
That is, the pattern will only match, and s
will only be assigned, if obj
is not null.
The use of pattern matching in instanceof
should dramatically reduce
the overall number of explicit casts in Java programs. Moreover, type test
patterns are particularly useful when writing equality methods.
Consider the following equality method taken from Item 10 of the
Effective Java book:
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString) &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
Using a type test pattern means it can be rewritten to the clearer:
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString cis) &&
cis.s.equalsIgnoreCase(s);
}
The instanceof
grammar is extended accordingly:
RelationalExpression:
...
RelationalExpression instanceof
ReferenceType
RelationalExpression instanceof
Pattern
Pattern:
ReferenceType Identifier
Future Work
Future JEPs will enhance the Java programming language with pattern
matching for other language constructs, such as switch
expressions
and statements.
Alternatives
The benefits of type-test patterns could be obtained by
flow typing in if
statements, or by a type switch construct.
Pattern matching generalizes both of these constructs.