Type Patterns
A type pattern consists of a type along with a single pattern variable. It's used to test whether a value is an instance of the type appearing in the pattern.
Type Patterns with Reference Types
The pattern variable of a type pattern can be any reference type.
The following switch
statement matches the selector
expression obj
with type patterns that involve a class type, an enum
type, a record type, and an int[]
array type:
record Point(int x, int y) { }
enum Color { RED, GREEN, BLUE; }
//...
static void typeTester(Object obj) {
switch (obj) {
case null -> System.out.println("null");
case String s -> System.out.println("String");
case Color c -> System.out.println("Color with " + c.values().length + " values");
case Point p -> System.out.println("Record class: " + p.toString());
case int[] ia -> System.out.println("Array of int values of length" + ia.length);
default -> System.out.println("Something else");
}
}
Type Patterns with Primitive Types
You can specify a primitive type instead of a reference type in a type pattern.
Note:
Primitive types in type patterns is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.See JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) for additional information.
The following example uses type patterns to test whether a value is a
float
, double
, or int
:
float v = 1000.01f;
if (v instanceof float f) System.out.println("float value: " + f);
if (v instanceof double d) System.out.println("double value: " + d);
if (v instanceof int i) System.out.println("int value: " + i);
It prints output similar to the following:
float value: 1000.01
double value: 1000.010009765625
See the section Decimal ↔ Binary Conversion Issues
in the Double JavaDoc API documentation for an
explanation why the converted double
value is different than the
original float
value in this example.
Tip:
Using pattern matching with primitives is particularly useful for ensuring type safety when casting values. See Safe Casting with instanceof and switch for more information.The case labels in the following switch
expression use type
patterns that involve float
:
String floatToRating(float rating) {
return switch(rating) {
case 0f -> "0 stars";
case 2.5f -> "Average";
case 5f -> "Best";
case float f -> "Invalid rating: " + f;
};
}
Type Patterns with Parameterized Types
As described in Safe Casting with instanceof and switch, you can use the instanceof
operator to test if the
right operand's type can be cast to left operand's type. This still applies if the right
operand is a type pattern that contains one or more parameterized types. However,
because of type erasure (see Type Erasure in The Java
Tutorials), there's no parameterized type information at run time. Consequently, the
runtime can't fully validate some casts between types that contain parameterized types.
The compiler generates warnings or errors for these situations. However, the compiler
will generate a error if it can't fully validate casts between
instanceof
operands that contain parameterized types.
Consider the following example that tries to cast a List to a List<String>:
List myList = null;
List<String> stringList = (List<String>) myList;
The compiler generates this warning with the -Xlint:unchecked
option:
warning: [unchecked] unchecked cast
List<String> stringList = (List<String>) myList;
^
required: List<String>
found: List
Consider the following example where the instanceof
operator tests
whether myList
, a List, has the type
List<String>:
List myList = null;
if (myList instanceof List<String> sl) {
System.out.println("myList is a List<String>, size " + sl.size());
}
The compiler generates an error for the second instanceof
expression:
error: List cannot be safely cast to List<String>
if (myList instanceof List<String> sl) {
^
The following contains additional examples of instanceof
expressions
containing type patterns with parameterized types.
public class Box<T> {
private T t;
public Box() { }
public Box(T t) { this.t = t; }
public void set(T t) { this.t = t; }
public T get() { return t; }
}
public class Shoebox<T> extends Box<T> {
public Shoebox(T t) { super(t); }
}
// ...
Shoebox<String> sb = new Shoebox<>("a pair of new shoes");
if (sb instanceof Box<String> s) {
System.out.println("Box<String> contains: " + s.get());
}
if (sb instanceof Shoebox<String> s) {
System.out.println("Shoebox<String> contains: " + s.get());
}
if (sb instanceof Shoebox<?> s) {
System.out.println("Shoebox<?> contains: " + s.get());
}
if (sb instanceof Shoebox<Object> s) {
// error: incompatible types: Shoebox<String> cannot be
// converted to Shoebox<Object>
System.out.println("Shoebox<Object> contains: " + s.get());
}
The following expressions return true
. The type parameters
on both sides of the instanceof
pattern matching operator are the
same:
sb instanceof Box<String> s
sb instanceof Shoebox<String> s
The expression sb instanceof Shoebox<?> s
returns
true
. The wildcard type (?
) matches all types.
However, the compiler can't fully validate at runtime whether
Shoebox<Object>
can be cast to
Shoebox<String>
even though String
is a
subtype of Object
. This parameterized type information won't exist at
run time because of type erasure. Consequently, the compiler generates an error for the
following expression:
sb instanceof Shoebox<Object> s
Note:
The previous example usesinstanceof
as the pattern matching operator. If you use
instanceof
as the type comparison operator, the same issues about
parameterized types and type erasure still apply. For example, the compiler generates an
error for this
expression:sb instanceof Shoebox<Object>