Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Patterns in instanceof

Pattern matching is a way to check whether a value has a certain shape and pull the parts you need out of it in the same step. Java gained it gradually, and the first step was patterns in the instanceof operator, available since Java 16.

How it used to be

The instanceof operator checks whether an object belongs to a type. But after the check you still had to cast the object by hand — write the type name in parentheses:

void main() {
    Object value = "Hello";
 
    if (value instanceof String) {
        String text = (String) value;
        IO.println(text.length());
    }
}

Output:

5

The type String is mentioned three times here, even though there is only one check.

A type pattern

With a pattern the check and the cast are combined: you write the name of a new variable after the type. If the check passes, the variable already has the right type:

void main() {
    Object value = "Hello";
 
    if (value instanceof String text) {
        IO.println(text.length());
    }
}

Output:

5

String text is called a type pattern, and text is a pattern variable. It comes into existence only if the object really turned out to be a string.

Different types

Patterns are especially handy when a value can be of different types and each needs its own handling:

String describe(Object value) {
    if (value instanceof Integer number) {
        return "an integer, doubled: " + number * 2;
    } else if (value instanceof String text) {
        return "a string of " + text.length() + " characters";
    } else if (value instanceof List<?> list) {
        return "a list of " + list.size() + " elements";
    }
    return "something else";
}
 
void main() {
    IO.println(describe(21));
    IO.println(describe("Oleg"));
    IO.println(describe(List.of(1, 2, 3)));
    IO.println(describe(3.14));
}

Output:

an integer, doubled: 42
a string of 4 characters
a list of 3 elements
something else

There's an even handier tool for chains like this — patterns in switch, covered in the next lesson.

A pattern in a condition

You can use the pattern variable later in the same condition after &&: the right side runs only if the left side is true, which means the variable already exists:

void main() {
    Object input = "  ";
 
    if (input instanceof String s && !s.isBlank()) {
        IO.println("Non-blank string: " + s);
    } else {
        IO.println("Not a string or a blank string");
    }
}

Output:

Not a string or a blank string

Negation

If you check "is not", the pattern variable is available after the if block — provided the block exits the method. This is a neat way to filter out unsuitable values at the start of a method:

void printUpper(Object value) {
    if (!(value instanceof String text)) {
        IO.println("A string is required");
        return;
    }
    IO.println(text.toUpperCase());
}
 
void main() {
    printUpper("anna");
    printUpper(42);
}

Output:

ANNA
A string is required

The compiler understands: if we reached the last line, value must be a string.

Common mistake

With || the pattern variable isn't available: the right side runs exactly when the type check did not pass, so there is no variable:

void main() {
    Object value = 10;
 
    if (value instanceof String s || s.isEmpty()) {
        IO.println("Empty");
    }
}

Output:

Main.java:4: error: cannot find symbol
    if (value instanceof String s || s.isEmpty()) {
                                     ^
  symbol:   variable s
  location: class Main
1 error
error: compilation failed

A pattern variable exists only where the compiler knows for sure that the check passed. Use it after && or inside the if block.

Thanks for reading!

Donate to the site — we work hard for you.