JEP draft: Enhanced Local Variable Declarations (Preview)
| Owner | Angelos Bimpoudis |
| Type | Feature |
| Scope | SE |
| Status | Submitted |
| Component | specification / language |
| Discussion | amber dash dev at openjdk dot org |
| Effort | M |
| Duration | S |
| Reviewed by | Alex Buckley, Brian Goetz |
| Endorsed by | Brian Goetz |
| Created | 2025/05/21 13:08 |
| Updated | 2026/03/31 21:47 |
| Issue | 8357464 |
Summary
Enhance local variable declarations to enable the extraction of multiple values from a single source value using pattern matching. This allows data-oriented computations to be expressed concisely and safely without added control flow. This is a preview language feature.
Goals
- Enable the extraction of multiple values at once, from a single source value, improving clarity and intent.
- Rely on pattern matching's applicability and exhaustiveness checks to make information extraction safer and reduce match failures in well-typed code.
- Make enhanced
forloops more expressive by combining iteration with extraction of relevant values from each element, reducing boilerplate. - Support smoother refactoring, by aligning existing local variable declarations with enhanced local variable declarations where feasible.
Non-Goals
- It is not a goal to change the behavior of pattern matching in
instanceoforswitch.
Motivation
Declaring local variables is straightforward in Java, but it becomes inconvenient when you need to extract several related values from a single source value. For example, suppose you wish to compute the bounding box of a circle:
record Circle(Point center, double radius) {}
record Point(int x, int y) {}
void boundingBox(Circle c) {
if (c != null) { // ┐
Point ctr = c.center(); // │ laborious / mechanical:
if (ctr != null) { // │ - null guards
int x = ctr.x(), y = ctr.y(); // │ - extraction with accessors
double radius = c.radius(); // ┘
int minX = (int) Math.floor(x - radius), maxX = (int) Math.ceil(x + radius);
int minY = (int) Math.floor(y - radius), maxY = (int) Math.ceil(y + radius);
... use minX, maxX, etc ...
}
}
}
The code to extract data from the single source value c is laborious and dominates the actual computations: null checks and calls to accessor methods that force readers to scan through multiple lines are wrapped in conditionals which are further appearing nested to reach the main logic.
Pattern matching lets you extract multiple values from a single source value and initialize local variables without the labor. For example, the boundingBox method could be written as:
void boundingBox(Circle c) {
if (c instanceof Circle(Point(int x, int y), double radius)) {
int minX = ..., maxX = ...
int minY = ..., maxY = ...
... use minX, maxX, etc ...
}
}
This code tries to match c against a record pattern and, if the match succeeds, initializes the three local variables declared in the pattern all at once. The pattern extracts components from multiple objects starting at c: x and y from c's center Point, and radius from c itself.
However, patterns can currently only be used in conditional constructs: in instanceof, as shown above, and in the case labels of a switch, which causes the main logic to again be nested:
switch (c) {
case Circle(Point(int x, int y), double radius) -> {
int minX = ..., maxX = ...
int minY = ..., maxY = ...
... use minX, maxX, etc ...
}
}
Pattern Matching for switch (JEP 441) introduced the concept of an exhaustive set of patterns; i.e., whether it covers all (reasonable) cases of the selector. It would be convenient if a pattern that is exhaustive for the static type of the value c could be used in other, non-conditional constructs. This would let you declare and initialize multiple local variables at once in ordinary block code, without introducing additional control flow around the main computation.
We propose to enhance local variable declarations to allow the use of an exhaustive record pattern instead of a traditional variable declarator like int x. This means the boundingBox method can be written as:
void boundingBox(Circle c) {
Circle(Point(int x, int y), double radius) = c;
int minX = ..., maxX = ...
int minY = ..., maxY = ...
... use minX, maxX, etc ...
}
The first line in the method is an enhanced local variable declaration statement. It matches c against the exhaustive record pattern on the left of =. The = operator in this enhanced local variable declaration statement performs pattern matching, not assignment. Because the pattern must be applicable and exhaustive for the static type of c, ordinary shape mismatches are ruled out statically; the remaining run-time failures are residual cases discussed below, which is exactly what you want when your code is asserting a strong invariant about the shape of the data at that program point.
Similarly, we propose to allow enhanced local variable declarations in the header of an enhanced for loop. This means the loop header can both range over a collection and extract the relevant values from each element:
for (Circle(Point(int x, int y), double radius) : circles) {
... x ... y ... radius ...
}
Description
An enhanced local variable declaration P [ = e ] introduces a record pattern that is required to be exhaustive for the static type of the value it is matched against, for the purpose of initializing the pattern variables contained in P. The declaration may contain an expression e to be pattern matched, or the expression is provided by the construct that the declaration appears in.
An enhanced local variable declaration declares the local variables in the pattern P and it initializes them by matching P against the value of the expression e.
An enhanced local variable declaration statement includes an enhanced local variable declaration and takes the following form: P = e ; where e always appears in the declaration and the declaration itself is followed by ;. This allows us to extract the state of an object into multiple variables in one operation:
Circle(Point center, double radius) = getCircle();
... center ... radius ...
An enhanced local variable declaration statement deconstructs a record instance on the right-hand side into its components. Record patterns can be nested to further deconstruct those components into their subcomponents:
Circle(Point(int x, int y), double radius) = getCircle();
... x ... y ... radius ...
You can use _ in the pattern to elide uninteresting components. As in other pattern contexts, var _ is also permitted; for example, if you only wanted the x coordinate of the center point:
Circle(Point(int x, var _), _) = getCircle();
... x ...
This is a preview language feature, disabled by default
To try out the changes described here, you must enable preview features:
-
Compile your program with
javac --release 27 --enable-preview Main.java, and run it withjava --enable-preview Main; or, -
When using the source code launcher, run your program with
java --enable-preview Main.java; or -
When using
jshell, start it withjshell --enable-preview.
Syntax and semantics
Enhanced local variable declarations are a new form of declaration and enhanced local variable declaration statements are a new kind of block statement in the Java grammar.
BlockStatement:
LocalClassOrInterfaceDeclaration
LocalVariableDeclarationStatement
EnhancedLocalVariableDeclarationStatement
Statement
EnhancedLocalVariableDeclarationStatement:
EnhancedLocalVariableDeclaration ;
EnhancedLocalVariableDeclaration:
RecordPattern [ = Expression ]
where:
RecordPattern:
ReferenceType ( [ComponentPatternList] )
ComponentPatternList:
ComponentPattern {, ComponentPattern }
ComponentPattern:
Pattern
_
An enhanced local variable declaration P [ = e ] captures the author's intent that the record pattern P is guaranteed to match the record instance from e. The rules for an enhanced local variable declaration P [ = e ] are as follows: (assuming record Point(int x, int y) {...} in examples)
-
Pmust be a record pattern with the same arity as the record type named inP, -
Pmust be applicable at the type ofe, meaning that whether the pattern is allowed to match the expression is checked at compile time; and -
P(the singleton set{ P }) is exhaustive fore, meaning that whether the pattern is able to match all possible values on the right-hand side is checked at compile time.
The rules for an enhanced local variable declaration statement P = e; are as follows:
-
An enhanced local variable declaration statement always has an initializer,
e. For example, you cannot writePoint(int x, int y);on its own; it must be followed by=and an expression. -
An enhanced local variable declaration statement introduces new local variables with the names declared in the pattern. Those names must not shadow variables that are already in scope at the point of declaration, as with pattern variables in
instanceofandswitch. Once declared, the variables are ordinary local variables subject to the usual scoping and assignment rules. For example:Point(int x, int y) = p; x = 42; // okbut you cannot reuse an existing local variable name:
int x = 42; Point(int x, int y) = p; // error -
An enhanced local variable declaration statement can appear anywhere a traditional local variable declaration statement can appear: the body of a method, or directly in the block of a statement such as
if,while,for, etc. This allows pattern matching to be intermixed freely with other code. The local variables declared by an enhanced local variable declaration statement are in scope throughout the rest of the enclosing block, just like local variables declared by a local variable declaration statement.Circle resize(Circle c, int factor) { Circle(Point center, double radius) = c; assert center != null; Point(int x, int y) = center; return new Circle(new Point(x+factor, y+factor), radius+factor); } -
An enhanced local variable declaration statement does not declare a name for the record pattern itself. If both the whole value and its components are needed, declare the whole value separately and then deconstruct it:
Circle wholeCircle = getCircle(); Circle(Point center, double radius) = wholeCircle; // data-oriented operations wholeCircle.method(); // any other possible computationsThis keeps the data-oriented operations of extraction in the pattern, while leaving any other use of the whole value explicit. You cannot write
Circle(Point center, double radius) wholeCircle = getCircle();. This would be a named record pattern, which is not part of this JEP and remains under consideration as a separate feature, generally in pattern matching; see the discussion. -
An enhanced local variable declaration statement does not have an expression form, like traditional local variable declaration statements. That means that you cannot write
Point p = (Point(int x, int y) = e);just as you can not writePoint p = (Point p2 = point);.
An enhanced local variable declaration statement is more powerful than a traditional local variable declaration statement: You can initialize multiple variables in a single local variable declaration statement, but each variable must supply its own initializer, e.g., int x = center.getX(), y = center.getY();. In contrast, an enhanced local variable declaration statement initializes multiple variables with values extracted from a single initializer, e.g., Point(int x, int y) = center;.
An enhanced local variable declaration statement also inherits the compositionality of patterns: instead of progressively extracting nested state with multiple statements, alternatively, you can use a nested record pattern to express the extraction in one declaration. For example:
Circle(Point(int x, int y), double radius) = c;
instead of:
Circle(Point center, double radius) = c;
assert center != null;
Point(int x, int y) = center;
Nested patterns do not have to fit on one physical line; when necessary, they can be formatted across multiple lines in the same way as other Java constructs.
Analogy between enhanced local variable declaration statements and pattern matching in switch
An enhanced local variable declaration statement P = e; can be thought of as equivalent to a switch statement with e as the selector and a switch block with a single case P:
switch (e) {
case P: ...
}
because the rules for switch already require that:
- pattern
Pis applicable at the type ofe, and - the singleton set
{ P }is exhaustive fore.
For example,
Circle(Point center, double radius) = getCircle();
is legal for the same reason that this switch is legal:
switch (getCircle()) {
case Circle(Point center, double radius) -> ... center ... radius ...
}
A switch with more than one case, or with one case and a default or a case null, has no analog with an enhanced local variable declaration statement.
Exceptions in the Enhanced Local Variable Declaration Statement
An enhanced local variable declaration statement differs from a traditional local variable declaration statement when the initializer to the right of = evaluates to null. In a traditional local variable declaration, the null value is assigned to the local variable:
Point p = getPoint(); // if getPoint() returns null, p is null
but in an enhanced local variable declaration statement, a NullPointerException is thrown because the statement performs pattern matching on the value of the initializer, and no record pattern matches null at the top level:
Point(int x, int y) = getPoint(); // if getPoint() returns null, throw NPE
If null is a valid result, the user can structure the code to handle it explicitly, and only perform the enhanced local variable declaration statement when a non-null value is present:
if (p == null) {
// handle null case
} else {
Point(int x, int y) = p;
// use x, y
}
As with pattern matching in switch, an enhanced local variable declaration can still fail to match at run time because of remainder values. A set of remainder values contains values that are not matched by the pattern even though the statement was considered exhaustive at compile time. When the statement finishes executing without matching the value on the right-hand side because that value is in the remainder set, it throws MatchException. There are two such situations, plus a third in which the statement completes abruptly during deconstruction and also throws MatchException.
-
When
nullis in the remainder set, due to a nested record pattern:Consider this statement, where the
Circleinstance on the right-hand side has a null center:Circle(Point(int x, int y), double r) = new Circle(null, 2.0); // throws MatchExceptionNested record patterns like
Point(int x, int y)always havenullin their remainder set; they can never matchnull. As a result, the statement does not match theCircleinstance and throws aMatchExceptionsince the enhanced local variable declaration finished executing without matching the value on the right-hand side.While nested record patterns never match
null, some type patterns can, if it is proven at compile time that they will always match all possible run time values of that type. Consider the statement above, but with a type patternPoint pinstead of a record patternPoint(int x, int y):Circle(Point p, double r) = new Circle(null, 2.0);This statement does not throw an exception; the type pattern
Point pcan match allPointvalues of the corresponding component (includingnull). Consequently, the statement matches the right-hand side value andpwill be initialized tonull. -
When values of a new type are in the remainder set, due to a separate compilation issue:
Remainder values can arise when sealed class hierarchies have evolved. Consider the method below which is compiled against the interface
S, with only one permitted implementation:// library V1 sealed interface S permits Only { } record Only(int v) implements S { } // user code compiled against V1 static void m(S s) { Only(int v) = s; System.out.println(v); }If someone permits a second implementation later:
// later library evolves to V2 without recompiling meth sealed interface S permits Only, Other {} record Only(int v) implements S { } record Other(int w) implements S {} // a new subtypebut the method is not recompiled, then all values of type
Otherare added to the remainder set. Since the enhanced local variable declaration statement in the method does not match values of typeOther, the statement throwsMatchException, and the caller will have to deal with it:m(new Other(42)); // throws MatchException -
When deconstruction throws an exception during the extraction of data from the right-hand side.
Assume the following record which overrides the accessor for the component
xso that it does not return the component value.record Point(int x, int y) { @Override public int x() { throw new IllegalStateException("bad x"); } }Attempting to access the
xcomponent using a regular method invocation, throws the expected exception:Point exceptional = new Point(1, 2); exceptional.x(); // throws IllegalStateExceptionSince the enhanced local variable declaration statement determines each record component by invoking the corresponding accessor method, if the accessor throws an exception, the statement throws a
MatchExceptionwitheas the cause:Point exceptional = new Point(1, 2); Point(int x, int y) = exceptional; // throws MatchException
A unified LHS = RHS model and straightforward refactoring
A local variable declaration statement T x = e; is traditionally read as declaring one new local variable x of type T and initializing it from the value of e. If e evaluates to null, then x is initialized to null.
A local variable declaration and a type pattern have the same form -- T x -- and the same purpose: to declare one new variable initialized from some instance. Accordingly, a local variable declaration statement T x = e; can also be viewed as a switch with a single case whose top-level pattern is the type pattern T x:
switch (e) {
case T x -> ...
}
This gives a useful unified mental model for statements of the form LHS = RHS: they initialize the declared variables from the value of the right-hand side, or they fail. One important exception is the treatment of null. A local variable declaration statement T x = e; still initializes x with null, while the corresponding switch statement would throw NullPointerException.
This JEP extends this reading of LHS = RHS by allowing the left-hand side to be not only a traditional local variable declaration but also richer pattern forms. In particular, an enhanced local variable declaration statement P(...) = e; can also be viewed as a switch statement with a single case whose top-level pattern is the record pattern P(...):
switch (e) {
case P(...) -> ...
}
The record pattern P(...) goes one step further by extracting, recursively, the state of an instance into one or more new variables. Moreover, if e evaluates to null, then an enhanced local variable declaration statement P(...) = e; throws NullPointerException and does not initialize any of the pattern variables in P. This suggests a mental model that enhanced local variable declaration statements are a generalization of local variable declaration statements: instead of introducing one local name for one value, the left-hand side can introduce multiple local names for multiple values extracted from a single source value.
This generalization implies straightforward refactoring. Consequently, the traditional and enhanced forms should align wherever feasible. Without that alignment, a straightforward refactoring can make a cast appear or disappear merely because the left-hand side changed shape. Using sealed classes and interfaces (JEP 409) lets library authors expose a stable contract while retaining strict control over behavior and invariants. In particular, library authors can declare an abstract sealed class or a sealed interface for their public API and permit exactly one subclass: their own. It is a common programming pattern that is followed by the need to immediately "recover" that subclass in local code, typically via an explicit downcast. As an example, consider the sealed hierarchy:
sealed interface Foo permits FooImpl {
String data();
}
record FooImpl(String data) implements Foo { }
Within the library, the library author will often write local variable declaration statements that downcast to the permitted subclass, such as:
public void libraryMethod(Foo f) {
FooImpl fi = (FooImpl) f;
String s = fi.data();
... s ...
}
This is boilerplate: it communicates an invariant already implied by the sealed hierarchy. Such explicit downcast is also error prone. If the sealed hierarchy is changed to support more permitted subtypes, such invariant is violated but the downcast would not fail to compile; the only failure signal would be a potential ClassCastException at run time.
Given the unified LHS = RHS model, it should also be possible to express the same operation with an enhanced local variable declaration statement, directly extracting the component:
public void libraryMethod(Foo f) {
FooImpl(String s) = f;
... s ...
}
This is legal because the pattern FooImpl(String s) is exhaustive for Foo. In pattern matching, the fact that Foo is sealed with a single permitted implementation is given greater weight than the possibility that Foo might be changed independently to permit more implementations.
If FooImpl(String s) = f; is legal because FooImpl is exhaustive for Foo, then FooImpl fi = f; should also be legal when the programmer wants to name the whole value rather than immediately deconstruct it.
Without corresponding support for traditional local variable declarations, however, refactoring between the two forms would require the insertion or removal of a cast for no semantic reason. We therefore propose to strengthen the language's static checking by letting the compiler use sealed single-implementation information when checking such assignments: an instance which implements an interface can be assigned to a variable of a class which implements the interface, as long as the interface is sealed with only that class as the permitted implementation. This means the library author can use a local variable declaration statement without downcasting:
public void libraryMethod(Foo f) {
FooImpl fi = f; // previously: (FooImpl) f
... fi.data() ...
}
This makes the ordinary form align better with exhaustive type patterns, and makes refactoring between naming the whole value and naming its components straightforward. It is also safer than the explicit cast, because if the sealed hierarchy later grows, recompilation of either form yields a compile-time error instead of leaving a stale cast in place:
public void libraryMethod(Foo f) {
FooImpl fi = f;
String s = fi.data();
... s ...
}
public void libraryMethod(Foo f) {
FooImpl(String s) = f;
... s ...
}
If the declaration of sealed interface Foo is later changed to permit an additional implementation, such as:
sealed interface Foo permits FooImpl, BarImpl {
String data();
}
then FooImpl(...) is no longer exhaustive for Foo, so recompiling the local variable declaration statement FooImpl fi = f; or the enhanced local variable declaration statement FooImpl(String s) = f; gives a compile-time error instead of leaving a stale explicit cast in place. If, instead of recompiling, you simply run the old class files as-is, the local variable declaration statement FooImpl fi = f; behaves as if the checked cast (FooImpl) f had been written explicitly and throws ClassCastException; it is not translated into switch or into an enhanced local variable declaration statement. The enhanced local variable declaration statement FooImpl(String s) = f; throws MatchException because the match candidate f refers to a remainder value.
The ability to assign a Foo expression to a FooImpl variable does not "lift" to array types: You cannot assign a Foo[] expression to a FooImpl[] variable even if Foo is a sealed interface with FooImpl as the sole, permitted implementation. For example, if fs has type Foo[], then FooImpl[] fis = fs; is illegal. The reason is that the JVM reifies array types and enforces array covariance: You cannot ever downcast a Foo[] object to FooImpl[], even if the elements of the Foo[] object are all FooImpl objects.
A consequence of strengthening assignment typing in this way is that downcasts from an interface or superclass to a single permitted implementation are no longer required in these locations:
-
Assigning using the simple assignment operator
=:FooImpl fi = ... fi = f; // previously: (FooImpl) f; -
Assigning in the enhanced
for:for(FooImpl fi : fs) { } // error before, no way to cast -
Assigning to array elements:
Foo f = ... FooImpl[] fi = new FooImpl[1]; fi[0] = f; // previously: (FooImpl) f; -
Assigning through array initializers:
Foo f = ... FooImpl[] fis = { f, f, f }; -
Method return:
FooImpl m() { Foo f = ... return f; } -
Method references:
record Box(Foo foo) {} Box b = new Box(new FooImpl("test")); Supplier<FooImpl> sup = b::foo; -
Lambda expressions:
Supplier<FooImpl> sup = () -> { Foo f = new FooImpl("test"); return f; };
This improvement also applies to classes that are abstract and sealed with just one subclass as the permitted implementation.
sealed abstract class A permits B {}
final class B extends A {}
void m(A a) {
B b = a; // now legal
}
One limitation of this unified reading (apart from null as the RHS) arises with primitive patterns (see Dependencies for the proposal that adds primitive types in patterns, instanceof and switch). A local variable declaration statement T x = e; when T is a primitive type may not have an equivalent to a switch with a single case. A local variable declaration statement includes special constant-conversion rules that do not align with pattern matching's exhaustiveness. For example, byte b = 42; is allowed due to constant narrowing from a constant expression of int to byte since it is known statically that 42 is representable in byte's range. However, in the switch below, case byte b is not exhaustive for the 42 selector and a default is required:
switch (42) { // error: not exhaustive
case byte b -> {...}
}
Enhanced local variable declarations in enhanced for statements
Enhanced local variable declaration statements can be used in the body of an enhanced for statement, e.g.
for (Circle c : circles) {
Circle(Point(int x, _), double radius) = c;
... x ... radius ...
}
The enhanced local variable declaration statement will often use the loop variable (c) on the right-hand side. This JEP proposes that enhanced local variable declarations can be used directly in the header of the loop:
for (Circle(Point(int x, _), double radius) : circles) {
... x ... radius ...
}
The grammar of the enhanced for statement is extended accordingly:
EnhancedForStatement:
for ( LocalVariableDeclaration : Expression ) Statement
for ( EnhancedLocalVariableDeclaration : Expression ) Statement
Integrating pattern matching in the enhanced for header supports direct, data-oriented extraction at the iteration point rather than introducing a new "loop element" name just to immediately take it apart with accessors. In the common case, any name for the loop element would be redundant: once the record pattern has bound the components of interest (and ignored those that are not), the element itself is no longer needed to express the computation.
If the enhanced local variable declaration is used in the header of an enhanced for statement, then the declaration is treated as if it had an expression e to be matched whose type is the iteration type of the enhanced for statement. This is not a filtering or partial-match mechanism; the same applicability and exhaustiveness requirements as for the statement form apply here.
An enhanced local variable declaration in the header of the enhanced for statement, just like in an enhanced local variable declaration statement, declares new local variables and cannot declare variables that shadow existing local variables.
At run time, execution of an enhanced for statement for (P : e) ... performs pattern matching on every loop iteration, matching P against successive values of e. The exceptions that may arise at run time are the same as in the enhanced local variable declaration statement.
As a result, the enhanced for statement that performs pattern matching, compared to a traditional enhanced for statement, is intolerant of null values produced by the Iterable expression: trying to match null will cause NPE to be thrown. If you wish to handle null values differently, e.g., by skipping them, you must explicitly check for null and then use an enhanced local variable declaration statement in the loop body:
for (Circle c : circles) {
if (c != null) {
Circle(Point(int x, _), double radius) = c;
... x ... radius ...
}
}
Dependencies
Enhanced local variable declarations depend on the rules of applicability and exhaustiveness to ensure correctness. "Primitive Types in Patterns" (JEP 530) enhances these rules by allowing all types – primitive and reference – in type patterns in instanceof and switch. With the combination of these two preview features a user can have finer control over the nested patterns on the left-hand side:
record Point(int x, int y) {}
Point p = ...
Point(long x, long y) = p;
Alternatives
An alternative would be to introduce a new form of statement with a fresh keyword like let to denote a declaration involving a pattern, e.g.,
let Circle(...) = e;
Historically, rather than introducing new keywords for each new idiom, Java tends to grow by reusing and extending existing constructs at a higher level over time (pattern matching via instanceof and switch is a good example). In that spirit, we prefer to enhance local variable declarations statements instead of adding a new "let" statement.
A fresh keyword would also scale poorly to other places where local variable declarations appear today. For example, a new syntax would add visual noise and reduce readability in the enhanced for statement (e.g., for (let Circle(...) : circles) ...) and would complicate any future enhancement of local variable declarations in any of the other locations that is currently permitted.
Finally, we should plan for growth and consistency. By enhancing local variable declaration statements, we can also strengthen traditional local variable declarations by letting them benefit from the same exhaustiveness-based reasoning, which makes refactoring from old to new forms smoother. Introducing a new keyword for the new construct (and not strengthening the old) would create an avoidable asymmetry in the language.
Risks and Assumptions
A risk of adding enhanced local variable declaration statements to the Java language is that they appear to "overload” the = operator. Traditionally, = denotes initialization in local variable declarations (JLS 14.4.2) and assignment in expressions, field declarations, and annotation elements (JLS 15.26.1; e.g., @Foo(x=1, y="Hello")). In the enhanced local variable declaration statement, = denotes pattern matching. We expect confusion to be minimal for two reasons:
-
The left-hand side of an enhanced local variable declaration is a record pattern, which is visually distinct from a single variable.
-
Like a traditional local variable declaration, an enhanced local variable declaration has no expression form. Consequently, you cannot write
bar(Point(int x, int y) = e);to declare and initializexandyin the caller (just as you cannot writebar(int p = e);to declare a local variablep). By contrast, Java’s assignment operator produces an assignment expression (x = e) that assigns to an existing variable and can be embedded in larger expressions (e.g.,foo(x = 5);); it can also appear as an expression statement (x = e;).
There is a risk that migrating code from a traditional local variable declaration statement to an enhanced local variable declaration statement can introduce a behavioral change for null values. The former can safely initialize a variable to null, whereas the latter performs pattern matching and will throw a NullPointerException if the right-hand side expression evaluates to null. For example, refactoring Point p = getPoint(); to Point(int x, int y) = getPoint(); changes behavior when getPoint() returns null, potentially causing previously working code paths to fail at run time. We assume IDEs and automated refactoring tools will help by suggesting appropriate guards to preserve intent.
Future Work
Having introduced enhanced local variable declarations in statements and in the header of enhanced for statements we may then consider the following:
-
A non-exhaustive counterpart, where matching failure is not ruled out by the declaration itself.
-
Additional enhanced local variable declaration contexts, such as resource specifications in
try-with-resources, exception parameters incatchclauses, and lambda parameters. -
If record patterns gain named deconstruction, enhanced local variable declarations would adopt that capability automatically.
-
Similarly, if record patterns later gain array and vararg patterns, enhanced local variable declarations would adopt that capability automatically, reducing the need to spell out long tails of uninteresting components with
_.