Unnamed Patterns and Variables (JEP 456)
Sometimes, Java syntax requires you to specify a variable name even when you never refer to it. JEP 456 allows you to use an underscore in many of these cases. This feature is probably most useful in record patterns, where an underscore can even replace a type + name. This article shows all situations where you can use the “unnamed” underscore.
Both in record patterns and variable declarations, you specify variables by providing a type (or var
) and a name. It can happen that you don’t need the name, or even the type. Under certain circumstances, you can then use an underscore _
placeholder for the name, or for both type and name.
The variable name _
was deprecated for removal in Java 8 and, as part of JEP 213, removed in Java 9. This pertains only to a single underscore. Identifiers containing underscores, such as UTF_8
, continue to be fine.
If you have ancient source code where _
is used as a variable name, chances are good that you used _
for exactly the use case that unnamed variables are meant to address. Then you need to do nothing. If you need to update your code, you can always use two underscores __
or, if you like, any Connecting Punctuation Character. My favorite choice is ﹏, U+FE4F WAVY LOW LINE.
Record Patterns
When matching components of a record pattern, you may not need the names of all variables. In this example, we classify the location of points:
record Point(int x, int y) {} ... Point p = new Point(3, 4); String description = switch (p) { case Point(var x, var y) when x == 0 && y == 0 -> "origin"; case Point(var x, var _) when x == 0 -> "on y-axis"; case Point(var _, var y) when y == 0 -> "on x-axis"; case Point(var _, var _) -> "somewhere else"; };
To find out whether a point is on the x- or y-axis, we only need to look at one component. The _
replace variable names that are not needed for the match. You can have more than one _
in the same pattern, as in the last case of the preceding example.
You can abbreviate var _
to _
, simplifying the example further:
description = switch (p) { case Point(var x, var y) when x == 0 && y == 0 -> "origin"; case Point(var x, _) when x == 0 -> "on y-axis"; case Point(_, var y) when y == 0 -> "on x-axis"; case Point(_, _) -> "somewhere else"; };
This is called an “unnamed pattern”.
The unnamed pattern can only occur inside record patterns, not at the top level of a switch
. There, just use default
.
The same syntax holds for instanceof
patterns:
if (p instanceof Point(_, var y) && y == 0) System.out.println("on x-axis");
In all preceding examples, I used var
for the type, but you can also specify the type rather than have it inferred:
if (p instanceof Point(int _, int y) && y == 0) System.out.println("on x-axis");
This sandbox shows unnamed variables and the unnamed pattern with records.
Type Patterns
You can use unnamed variables with type patterns:
case Double _, Float _ -> "a floating-point number";
Here is a more complex situation, with nested type patterns and an unnamed pattern:
record Box<T>(T value) {} ... Box<Number> box = new Box(...); String description = switch (box) { case Box(Float _), Box(Double _) -> "a boxed floating-point number"; case Box(_) -> "something other than a floating-point number in this box"; };
In the first case, the type is used for matching. This is different from the example of the preceding section, where the type of the component was known.
There are a couple of subtleties. When you have two cases, as in the first example, and you add a when
clause, it applies to both cases:
case Box(Float _), Box(Double _) when box.floatValue() > 0 -> "a boxed positive floating-point number";
You can’t apply a when
clause to separate cases:
case Point(var x, _) when x = 0, Point(_, var y) when y = 0 // Error
More subtly, as always with switch
, we have to talk about null
. What happens to the switch
when box
is null
?. For backwards compatibility, a switch
without case null
is “null-hostile” and throws a NullPointerException
What if box
is new Box(null)
? Do we get a NullPointerException
? A MatchException
? No—Box(_)
matches new Box(null)
. The null-hostility only applies to the top level.
This sandbox shows type patterns with unnamed variables.
Exceptions
The use of _
as a “don’t care” pattern is easy and compelling, but there are other situations where we truly don’t care about a variable name. Sometimes, when catching an expression, you never look at the exception. With JEP 443, you don’t have to come up with a variable name for the unused object. This sandbox shows a typical example.
Try with Resources
Consider a typical “try
with resources” statement:
try (var obj = someAutoCloseable()) { ... }
In almost all cases, you need to refer to obj
inside the block. For example:
try (var out = new PrintWriter(...)) { out.println(...); out.println(...); } // out.close() called here
But what if you don’t? Then you can use
try (var _ = ...) { ... } // unnamedVariable.close() called here
The following sandbox shows an example with a DynamicScope
object that manages nested assignments from names to values. When you open a new scope, its names shadow those of previous assignments. When the scope is closed, the new assignments are forgotton. The whole point of the try
block is to call the close
method that implements the relinquishment of old assignments. The name of the object is immaterial, as long as the close
method is called.
This is not a common situation, but it is nevertheless plausible. Seemingly, the language implementors were on the lookout for any situations where a variable name is not required.
Lambdas
When a method has a parameter whose type is a functional interface, it can happen that you pass a lambda expression where not all arguments are needed. For example, the Map::computeIfAbsent
method lazily computes an initial value when a key is absent:
var index = new TreeMap<String, TreeSet<Integer>>(); ... index.computeIfAbsent(word, _ -> new TreeSet<>()).add(pageNumber);
If the map does not contain the key word
, it is added, with an empty set as value. The lambda that computes the empty set does not use its argument.
Here is another example, splitting up a list into two random sublists:
Map<Boolean, List<String>> sublists = list.stream().collect(Collectors.partitioningBy( _ -> Math.random() < 0.5))
Normally, we partition by some property of the elements, but here, we don’t care.
Here is a sandbox with these examples:
Variable Declarations
If you want to be explicit about the fact that you are ignoring the return value of a method call, you can assign it to an unnamed variable:
Scanner in = ...; while (in.hasNext()) { count++; var _ = in.next(); }
I don’t know if I would ever do this, but it might be useful to placate an overly zealous linting tool.
You can even use anonymous variables in a for
loop. In this enhanced for
loop, we don’t care about the elements in an iterable (presumably, one without a size
method):
for (var _ : iterable) count++;
With a classic for
loop, you can initialize unnamed variables with an expression that you evaluate for its side effect:
for (Scanner words = new Scanner("Mary had a little lamb"), _ = words.useDelimiter("\\s+"); words.hasNext(); words.next()) { count++; }
Note that all variables that are declared in the loop header must have the same type. In this case, by good fortune (or actually, low cunning by the author), the return type of Scanner::useDelimiter
is Scanner
, but you won’t usually be so lucky.
This final sandbox shows these marginally useful examples of unnamed variable declarations.