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.010009765625See 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: ListConsider 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> ssb 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> sNote:
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>