Java 14 - Pattern Matching for instanceof (JEP 305)

Before Java 14

In our Java codes, we often need to compare a reference variable to a type by using the instanceof operator. If the result is true, the next step is to cast it to the type that we used to compare it, since often we need to access its members, as in following example:

BeforeJDK14Sample1.java
package com.dariawan.jdk14;

public class BeforeJDK14Sample1 {

   public static void main(String[] args) {
        Object obj1 = "1";
        
        // Before JDK 14
        if (obj1 instanceof String) {
            String str = (String) obj1;
            System.out.printf("String: %s\n", str);
        } else {
            System.out.printf("Not a string\n");
        }
        
        if (obj1 instanceof Integer) {
            Integer i = (Integer) obj1;
            System.out.printf("Integer: %s\n", i);
        } else {
            System.out.printf("Not an Integer\n");
        }
        
        Object obj2 = 1;
        
        if (obj2 instanceof String) {
            String str = (String) obj2;
            System.out.printf("String: %s\n", str);
        } else {
            System.out.printf("Not a string\n");
        }
        
        if (obj2 instanceof Integer) {
            Integer i = (Integer) obj2;
            System.out.printf("Integer: %s\n", i);
        } else {
            System.out.printf("Not an Integer\n");
        }
    }
}
                    

String: 1
Not an Integer
Not a string
Integer: 1

Operator instanceof also can be used to tests your variable against your custom classes:

BeforeJDK14Sample2.java
package com.dariawan.jdk14;

import com.dariawan.jdk14.dto.Robot;
import com.dariawan.jdk14.dto.RobotLawnMower;
import com.dariawan.jdk14.dto.RobotVacuumCleaner;
import com.dariawan.jdk14.dto.RobotVendingMachine;

public class BeforeJDK14Sample2 {

    public void hiRobot(Robot robot) {
        if (robot instanceof RobotVacuumCleaner) {
            RobotVacuumCleaner vc = (RobotVacuumCleaner) robot;
            vc.clean();
        } 
        else if (robot instanceof RobotLawnMower) {
            RobotLawnMower lw = (RobotLawnMower) robot;
            lw.mow();
        } 
        else if (robot instanceof RobotVendingMachine) {
            RobotVendingMachine vm = (RobotVendingMachine) robot;
            vm.serve();
        }
        else {
            System.out.println("Unregistered robot...");
        }
    }
    
    public static void main(String[] args) {
        BeforeJDK14Sample2 sample = new BeforeJDK14Sample2();
        sample.hiRobot(new RobotVacuumCleaner());
        sample.hiRobot(new RobotLawnMower());
        sample.hiRobot(new RobotVendingMachine("orange juice"));
    }
}
                    

RobotVacuumCleaner cleaning
RobotLawnMower mowing
RobotVendingMachine serving orange juice

Three things are happening here, let see the first if in the above example:

  • test if variable is in certain type (is robot instanceof RobotVacuumCleaner),
  • if true, cast variable to the type that we compare to (RobotVacuumCleaner vc = (RobotVacuumCleaner) robot)
  • After that, we can use the members of the variable (vc.clean())

For me personally, this operator is very useful if you working with programs that take advantage of OOP in Java, especially Inheritance.

As we get from two examples above, instanceof operator only used to tests whether the given object is of the given type. This is the state before Java 14.

Java 14 - instanceof With Pattern Matching

With JEP 305, the instanceof operator is improved in Java 14 to tests the parameter and assigns it to a binding variable with proper type. Let's revisit two examples above. In my JEP305Example1, those two examples will be simplified (for compactness, I removed some lines in two previous samples with little values, and combine it into one program):

JEP305Example1.java
package com.dariawan.jdk14;

import com.dariawan.jdk14.dto.Robot;
import com.dariawan.jdk14.dto.RobotLawnMower;
import com.dariawan.jdk14.dto.RobotVacuumCleaner;
import com.dariawan.jdk14.dto.RobotVendingMachine;

public class JEP305Example1 {

    public void hiRobot(Robot robot) {
        if (robot instanceof RobotVacuumCleaner vc) {
            vc.clean();
        } 
        else if (robot instanceof RobotLawnMower lw) {
            lw.mow();
        } 
        else if (robot instanceof RobotVendingMachine vm) {
            vm.serve();
        }
        else {
            System.out.println("Unregistered robot...");
        }
    }
    
    public static void main(String[] args) {
        // After JDK 14
        
        Object obj1 = "1";
        if (obj1 instanceof String str) {
            System.out.printf("String: %s\n", str);
        } else {
            System.out.printf("Not a string\n");
        }

        Object obj2 = 1;
        if (obj2 instanceof Integer i) {
            System.out.printf("Integer: %s\n", i);
        } else {
            System.out.printf("Not an Integer\n");
        }
        
        JEP305Example1 sample = new JEP305Example1();
        sample.hiRobot(new RobotVacuumCleaner());
        sample.hiRobot(new RobotLawnMower());
        sample.hiRobot(new RobotVendingMachine("instant noodle"));
    }
}
                    

String: 1
Integer: 1
RobotVacuumCleaner cleaning
RobotLawnMower mowing
RobotVendingMachine serving instant noodle

As you can see, the test and type casting are more simplified:

  1. No need for casting, if Type tested with instanceof is true, we can assign a variable for those Type.
  2. The scope of variable is only inside the if block.

Let's move to our next example. In our next example, we can use && operator conditions if instanceof test and assignment is true.

JEP305Example2.java
package com.dariawan.jdk14;

import com.dariawan.jdk14.dto.Robot;
import com.dariawan.jdk14.dto.RobotLawnMower;
import com.dariawan.jdk14.dto.RobotVacuumCleaner;
import com.dariawan.jdk14.dto.RobotVendingMachine;
import java.util.Arrays;
import java.util.List;

public class JEP305Example2 {

    private void hiRobot(Robot robot) {
        if (robot instanceof RobotVacuumCleaner vc) {
            vc.clean();
        } 
        else if (robot instanceof RobotLawnMower lw) {
            lw.mow();
        } 
        else if (robot instanceof RobotVendingMachine vm) {
            vm.serve();
        }
        else {
            System.out.println("Unregistered robot...");
        }
    }
    
    private void listRobot(Object o) {
        if (o instanceof List l && !l.isEmpty()) {
            for (Object obj : l) {
                if (obj instanceof Robot robot) {
                    hiRobot(robot);
                }
            }
        }
        else {
            System.out.println("Not a List or List is empty");
        }
    }

    public static void main(String[] args) {        
        JEP305Example2 sample = new JEP305Example2();
        
        List l = null;
        sample.listRobot(l);
        
        l = Arrays.asList();
        sample.listRobot(l);
        
        l = Arrays.asList(new RobotVacuumCleaner(), new RobotLawnMower(), new RobotVendingMachine("hot coffee"));
        sample.listRobot(l);
    }
}
                    

Not a List or List is empty
Not a List or List is empty
RobotVacuumCleaner cleaning
RobotLawnMower mowing
RobotVendingMachine serving hot coffee

Using instanceof in equals(...) Method

We often create equals() method for checking the equality of objects:

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (obj instanceof RobotVendingMachine) {            
        final RobotVendingMachine other = (RobotVendingMachine) obj;
        if (!Objects.equals(this.item, other.item)) {
            return false;
        }
    }
    else {
        return false;
    }
    return true;
}
                    

Using this feature, we can make equals() methods more simplified as shown in the example below:

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (obj instanceof RobotVendingMachine other) {
        if (!Objects.equals(this.item, other.item)) {
            return false;
        }
    }
    else {
        return false;
    }

    return true;
}
                    

Enable Preview Feature

The pattern matching for instanceOf in Java 14 is still a preview feature, means it could change and evolve in upcoming releases. To unlock preview features, use the --enable-preview command line flag. You can use it in java and javac command, in maven pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <release>14</release>
        <compilerArgs>
            <arg>--enable-preview</arg>
        </compilerArgs>
    </configuration>
</plugin>
                    

Or you can configure this in your IDE, like what I configured for NetBeans IDE:

Java 14 Project --enable-preview

--enable-preview in Apache NetBeans IDE 10.0

And for completeness of this article, here the four supporting class for this article: Robot, RobotVacuumCleaner, RobotLawnMower, and RobotVendingMachine:

Robot.java
package com.dariawan.jdk14.dto;

public abstract class Robot {
    
    protected String getName() {
        return this.getClass().getSimpleName();
    }
}
                    

RobotVacuumCleaner.java
package com.dariawan.jdk14.dto;

public class RobotVacuumCleaner extends Robot {
    
    public void clean() {
        System.out.println(this.getName() + " cleaning");
    }
}
                    

RobotLawnMower.java
package com.dariawan.jdk14.dto;

public class RobotLawnMower extends Robot{
    
    public void mow() {
        System.out.println(this.getName() + " mowing");
    }
}
                    

RobotVendingMachine.java
public class RobotVendingMachine extends Robot {
    
    private String item;
    
    public RobotVendingMachine(String item) {
        this.item = item;
    }
    
    public void serve() {
        System.out.println(this.getName() + " serving " + this.item);
    }
}