Java 21 – Record Patterns

Pattern Matching is a new area where Java has been introducing quite a few changes recently.

It started with instanceof operator.

Previously in Java , if you had to access an object whose instance type you didnt know prior, you had to use instanceof operator this way:

if (obj instanceof String) {
    String s = (String)obj;
     // use s here
}

You had to do a cast operation as shown above.

Java then got rid of the cast and allowed you to put the reference variable along with the statement including instanceof operator like this:

if (obj instanceof String s) {
 //use s here
}

No casting!

And less number of code.

Java internally matched the pattern and assigned the object to the reference variable.

This was extended to records.

Like the below example:

record Position(int xCoordinate, int yCoordinate) {}

static void printSum(Object object) {
    if (object instanceof Position position) {
        int xCoordinate = position.xCoordinate();
        int yCoordinate = position.yCoordinate();
        int sum = xCoordinate + yCoordinate;
        System.out.println("The sum of x and y coordinates is: " + sum);
    }
}

As you see , the record Position is assigned a reference variable “position” directly in the if statement.

The position object can then be accessed inside the if loop directly.

Java further improved the above code for records.

Starting Java 20 , You can remove the reference variable altogether and still be able to access the fields of the record object:

record Position(int xCoordinate, int yCoordinate) {}

void printSum(Object obj) {
    if (obj instanceof Position(int xCoordinate, int yCoordinate)) {
        System.out.println(xCoordinate+yCoordinate);
    }
}

There is no need of a reference variable to the Position record to access its fields!

This also applies to nested records:

record Company(String name, Employee e){}
record Employee(String name, Role role){}
record Role(String name , Department department){}
record Department(String name)


void printDepartment(Object obj){

        if(obj instanceof Company(String name , Employee(String name, Role(String name , Department(String name)))){
                   System.out.println("Department name is "+name);
   }
} 
}

As you see “Company” is a record which contains a nested record “Employee” which contains a nested record “Role” which contains a nested record “Department” which contains a field named “name”.

And we can directly access the name field value using Pattern Matching!

Parameterized vs Raw Types:

If a record class is generic , then it can be used in a record pattern as a raw type or a parameterized type.

Consider the below code :

record Person<T>(T t) {}

static void test(Person<String> p) {
    if (p instanceof Person<String>(var s)) {
        System.out.println("Name" + s);
    }
}

It uses parameterized type using the < > symbol.

It can also be written as :

static void test(Person<String> p) {
    if (p instanceof Person(var s)) {
        System.out.println("Name" + s);
    }
}

Exhaustive Switch:

You can also use record patterns in switch case labels just like if statements given in the previous examples.

There is one case of switch which you need to be careful about.

If you are using parameterized records like :

record Pair<T>(T x , T y) {}

and you use that in switch case labels,

Then you have to include case labels for all possible matches.

Else the compiler will throw error.

For example for the above record ,

Let’s create a class A and then pass it as the parameterized type.

class A{}

So we can create a record this way:

A a1 = new A();
A a2 = new A();
var p = new Pair<A>(a1,a2);

Now let’s say there is a class B which extends A:

class B extends A{}

Now you can create the Pair record in three ways by passing the class A as a parameter.

A a1 = new A();
A a2 = new A();
B b1 = new B();
B b2 = new B();

var p1 = new Pair<A>(a1,a2);
var p2 = new Pair<A>(a1,b1); //since b extends A it can also be passed in place of A
var p3 = new Pair<A>(b1,a1);

Now if you use a switch statement which checks for the Pair record , you need to make sure all the above scenarios are covered using case labels.

So the below code will break:

switch(p){

    case Pair<A>(A a,B b):

              ....
              break;
     case Pair<A>(A a1,A a2):

                 ...
              break;
}

We missed one case label in the above switch statement.

We did not cover the possibility of

case Pair<A>(B b, A a)

It should also be included in the switch statement else the compiler will throw error.

That’s it!

Reference:

https://openjdk.org/jeps/440


Posted

in

, , ,

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading