You are currently viewing Going beyond Java 8: pattern matching for instanceof

Going beyond Java 8: pattern matching for instanceof

According to some surveys such as that of JetBrains, version 8 of Java is currently the most used by developers all over the world, despite being a 2014 release. What you are reading is the one in a series of articles titled “Going beyond Java 8”, inspired by the contents of my book “Java for Aliens”. These articles will guide the reader step by step to explore the most important features introduced starting from version 9. The aim is to make the reader aware of how important it is to move forward from Java 8, explaining the enormous advantages that the latest versions of the language offer.

In this article we will see an interesting novelty introduced in version 14 as a preview feature (see related article), and definitively made official as a standard feature with Java 16. This is the first part of a complex feature known as pattern matching. It will affect various programming constructs in the future, for now only the pattern matching concerning the instanceof operator is available. This will radically change the way we use this operator.

 

The instanceof operator

The instanceof binary operator, like comparison operators, returns a boolean value. The peculiarity of this operator is that it uses a reference as the first operand, and a complex type as the second operand. It returns true, if, at runtime, the reference (which defines the first operand) points to an object instantiated by the type (which defines the second operand). It returns true, even if the reference points to an object instantiated by a subclass of the type specified with the second operand. If one of these two conditions is not met, the instanceof operator returns false.

 

Example

Suppose we want to create a system that establishes the salaries of employees of a company, considering the following classes:

public class Employee {
    private String name;
    private int salary;
    private int number;
    private String birthDate;
    private String hireDate;
    // setter and getter methods omitted
}

public class Programmer extends Employee {
    private String knownLanguages;
    private int yearsOfExperience;
    // setter and getter methods omitted
}

public class Manager extends Employee {
    private String workingTime;
    // setter and getter methods omitted
}

public class SalesAgent extends Employee {
    private String [] customerPortfolio;
    private int commissions;
    // setter and getter methods omitted
}

Suppose we have to pay the salaries to all types of employees, we could begin to group all the employees of the company, within a heterogeneous collection:

Employee [] arr = new Employee [180];
arr[0] = new Manager();
arr[1] = new Programmer();
arr[2] = new SalesAgent();
//... other assignments omitted

To manage employee salary, we could create the following method:

public void payEmployee(Employee emp) {
    if (emp instanceof Programmer) {
        emp.setSalary(1500);
    }
    else if (emp instanceof Manager) {
        emp.setSalary(3000);
    }
    else if (emp instanceof SalesAgent) {
        emp.setSalary(1000);
    }
    System.out.println(emp.getClass().getName() + " - Salary = " 
        + emp.getSalary());
        //. . .
} 

It uses the instanceof operator to test which type of the polymorphic parameter emp is pointing to, and set the salary accordingly.

Now we can call this method within a foreach loop (of 180 iterations), passing all of the elements of the heterogeneous collection, and thus reach our purpose:

//...
for (Employee employee : arr) {
    payEmployee(employee);
    //...
}

 

Object Cast

In the previous example, we observed that the instanceof operator allows us to test which type of instance a reference points to. However, we already know that a superclass reference pointing to an object instantiated by a subclass cannot access the members declared in the subclass. For example, suppose a programmer’s salary depends on the number of years of experience. In this situation, after testing that the reference emp points to a Programmer instance, we will need to access the yearsOfExperience variable. If we tried to access it using the syntax:

emp.getYearsOfExperience();

we would get an error in compilation:

error: cannot find symbol
        emp.getYearsOfExperience();
           ^
  symbol:   method getYearsOfExperience()
  location: variable emp of type Employee

In fact, the reference emp, being of the Employee type, will not be able to access the methods that are declared in the subclasses. To overcome this obstacle, we can use the object cast mechanism. In practice, we must declare a Programmer type reference and make it point to the memory address where the reference emp points, using the cast to confirm the pointing range. The new reference, being of the Programmer type, will allow us to access any member of the Programmer instance.

The cast of objects uses a syntax very similar to the cast between primitive data:

if (emp instanceof Programmer) {
    Programmer pro = (Programmer) emp;
    if (pro.getYearsOfExperience() > 2)
//...

Note that we are now able to access the encapsulated variable yearsOfExperience.

In Figure 1 we ideally outline the situation with a graphical representation, where the object is pointed to by the Employee type emp reference and the Programmer type pro reference.

 

Figure 1 – Two different types of access to the same object.

In figure 1 the two references point to the same object, but with a different pointing range. It is essential to use the object cast only after checking its validity with the instanceof operator. In fact, the compiler cannot determine if a certain object resides at a certain address instead of another. It is only at runtime that the Java Virtual Machine can use the instanceof operator to resolve any doubts.

 

What’s the problem?

The combined use of the instanceof operator and the object cast cannot be defined as a Java best practice, but it is certainly a well-known and widely used programming pattern. What is evident however, is that this practice is undoubtedly verbose. It is also repetitive; in the previous example we named the Programmer type three times in two lines. Finally, after a test with instanceof, the next statement is obviously assumed to be a cast. In fact, some IDEs allow us to automate this practice. However, if we do not automate the coding with an IDE, it is not unlikely to rely on a copy and paste if this code is repeated several times, and this could also imply a ClassCastException at runtime.

 

Pattern matching for instanceof

Java 16 introduced the first part of a complex feature known as pattern matching. In this feature, a pattern (not to be confused with a design pattern) is composed of:

a predicate: a test that looks for the matching of an input with its operand;

one or more binding variables: these are extracted from the operand depending on the test result.

A pattern is therefore defined as a synthetic way of expressing a complex solution.

When this feature will be fully implemented, it will also affect other topics such as record types, and the switch construct. In version 15, pattern matching is still presented as a feature preview, and the only part implemented concerns the pattern that involves the instanceof operator and the object cast that we have just seen. In practice, by enabling the feature preview, we can use an alternative syntax:

if (emp instanceof Programmer pro) {
    if (pro.getYearsOfExperience() > 2)
    //...
}
//...

This syntax specifies a Programmer-type reference pro alongside the instanceof operation. This reference can be used immediately, and no cast is required since it is already a Programmer type reference. The syntax is therefore very simple, but some observations must be made.

 

Binding Scope

In particular, the reference pro is defined as a binding variable, that is a particular local variable with a new type of constrained scope that depends on the pattern. Like a local variable, it can coexist with instance variables with the same identifier. The difference consists in the fact that its scope depends on the result of the predicate, that is if the result of the instanceof test is true or false. In the previous example, the reference pro was visible only in the code block relating to the if construct. If we added the else clause and prefixed the NOT operator to the instanceof test in the following way:

if (emp instanceof Programmer pro) {
    if (pro.getYearsOfExperience() > 2)
    //...
}
//...

then the reference pro will be visible only in the code block of the else clause, and not in the if code block.

 

Binding Constants?

Up to version 15 a binding variable was implicitly final, and its addressing cannot be changed. For example:

if (emp instanceof Programmer pro) {
    pro = new Programmer();
    //...
}

would have caused the following compile-time error:

error: pattern binding pro may not be assigned
            pro = new Programmer();
            ^
1 error

This was the behavior of binding variables in Java 15, where pattern matching for instanceof was still a feature preview. But in version 16 the pattern matching for instanceof feature has been formalized as a standard Java feature and this limitation has been permanently removed.

In any case, through a binding variable, being a reference, it is still possible to change the internal state of the object it points to. For example, the following code is valid:

if (dip instanceof Programmer pro) {
    pro.setYearsOfExperience(8);
}

in fact, a new address is not assigned to the reference pro, but only a method is invoked on it.

 

Conclusions

In this article we have seen how Java 14 introduced pattern matching for instanceof as a feature preview. This new feature introduces a new type of scope for binding variables, and will forever change the way we use the instanceof operator and object cast. The pattern matching for instanceof, therefore, represents another small step forward for the language.

 

Author Notes

Even ignoring the increased security offered by the latest versions of the JDK, there are plenty of reasons to upgrade your knowledge of Java, or at least your own Java runtime installations. My book “Java for Aliens“, which inspired the ” Going beyond Java 8″ series, contains all the information you need to learn Java from scratch, and uses a well-tested teaching method that has been perfected over 20 years of experience, which makes learning simple and exciting. It is also structured to deepen the topics and have superior knowledge that can make a difference to your career.

This article is mainly inspired by section 8.3 of chapter 8 of the book “Java for Aliens

For more information visit https://www.javaforaliens.com.

Leave a Reply