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:
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:
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):
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:
- No need for casting, if Type tested with
instanceof
is true, we can assign a variable for those Type. - 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.
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:
--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
:
package com.dariawan.jdk14.dto;
public abstract class Robot {
protected String getName() {
return this.getClass().getSimpleName();
}
}
package com.dariawan.jdk14.dto;
public class RobotVacuumCleaner extends Robot {
public void clean() {
System.out.println(this.getName() + " cleaning");
}
}
package com.dariawan.jdk14.dto;
public class RobotLawnMower extends Robot{
public void mow() {
System.out.println(this.getName() + " mowing");
}
}
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);
}
}