We will start this article with record classes.
Record Classes
Consider the below Java class:
public final class MaxMin {
private final int max;
private final int min;
public MaxMin(int max, int min) {
this.max = max;
this.min = min;
}
public int max() { return this.max; }
public int min() { return this.min; }
// Implementation of equals() and hashCode()
public boolean equals...
public int hashCode...
// An implementation of toString() that returns a string
public String toString() {...}
}
The above MaxMin class basically a data aggregate that carrying two data values: max and min. This class is serving as a data carrier. Instead of writing one public constructor with all the data fields, a public accessor method for each data field, equals method, hashCode method, and toString method, if we can do all these automatically just based on the data fields, it will be great and help us write less code for such immutable data class. Exactly here comes record classes.
A record class is special kind of class in Java language. Records are transparent carrier of data. The java.lang.Record is the common base class of all Java language record classes. Let’s have a look at one example.
public class RecordClassDemo {
record MaxMin(int max, int min) {}
public static void main(String[] args) {
MaxMin mm = new MaxMin(10,4);
System.out.println("Max: " + mm.max());
System.out.println("Max: " + mm.min());
}
}
In the above example, MaxMin represents one record class. It has two record components: max and min. Record components are the variables that make up the record’s state.
A record class declaration consists of:
- A name
- optional type parameters
- A record header
- A body
In our case, name is MaxMin, record header is (int max, int min), and body is { }. The list of record components declared in the record header form the record descriptor.
A record class declares the below things automatically:
- For each component in the record header, a
public
accessor method with the same name as the record component and the same return type as the component type. - For each component in the record header, a
private
final
field with the same name and type as the record component. - A canonical constructor whose signature is the same as the record header.
- Implementation of
equals
andhashCode
methods which guarantee that two record objects are equal if they are of the same type and contain equal component values. - An implementation of
toString
method that returns a string representation of all the record class’s components, along with their names.
For the current article, the above understanding on record classes will be good enough. Now we will focus on pattern matching for instanceof operator.
Pattern Matching for instanceof Operator
Consider the below code-snippet:
Number obj = 20;
if (obj instanceof Integer) {
Integer i = (Integer)obj;
System.out.println(i);
}
if (obj instanceof Integer i) {
System.out.println(i);
}
In the first if-block, the instanceof operator tests whether the given object is of type Integer. But in the second if-block, the instanceof operator tests the given object with a type pattern. Here pattern matching occurs. First, for the current example, instanceof operator checks whether the given object has the structure of an Integer. If structure matches, then data gets extracted from the given object and is assigned to i. Here, i is called the pattern variable.
Note: Pattern matching for instanceof operator was introduced in Java 16.
Next we will focus on understanding the record patterns.
Record Patterns
Consider the below code snippet:
Code Snippet 1
public class RecordPatternDemo1 {
record MaxMin(int max, int min) {}
private static void printMaxMin(Object obj) {
if (obj instanceof MaxMin mm) {
int max = mm.max();
int min = mm.min();
System.out.println("Max: " + max);
System.out.println("Min: " + min);
}
}
public static void main(String[] args) {
MaxMin mm = new MaxMin(10,4);
printMaxMin(mm);
}
}
In the above code, type pattern is “MaxMin mm” (look at the printMaxMin static method). In the if-block, we are checking whether the given object is of MaxMin record type. If so, we are extracting the component values from the record object. It would be better if we could not only test if the given object is type of MaxMin
but also extract the max and min components from the given record object directly. We can achieve that using a record pattern. Have a look at the below code for your understanding:
Code Snippet 2
public class RecordPatternDemo1 {
record MaxMin(int max, int min) {}
private static void printMaxMin(Object obj) {
if (obj instanceof MaxMin(int max, int min)) {
System.out.println("Max: " + max);
System.out.println("Min: " + min);
}
}
public static void main(String[] args) {
MaxMin mm = new MaxMin(10,4);
printMaxMin(mm);
}
}
Record patterns help us write concise pattern matching code. They help us directly extract the fields of the record instead of declaring local variables and calling the accessor methods as we did in the Code Snippet 1. A record pattern deconstructs a record object into its components.
Nested Record Patterns
We can use record patterns in nested levels also. Let’s consider the below record classes.
record CoreRecord(int data1, int data2) {}
record InnerRecord(CoreRecord c, int data3) {}
record OuterRecord(InnerRecord innerData) {}
If we want to extract the data3 from the innerData, we could write a method like this, for example:
private static void printData3(Object obj) {
if (obj instanceof OuterRecord(InnerRecord innerData)) {
System.out.println("Data 3: " + innerData.data3());
}
}
Here, InnerRecord is itself a record. We can decompose it further, if we want. Here comes nested record patterns. Below code snippet explains the idea of nesting.
private static void printData3(Object obj) {
if (obj instanceof OuterRecord(InnerRecord(CoreRecord c, int data3))) {
System.out.println("Data 3: " + data3);
}
}
Again CoreRecord is a record. So, we can decompose it further.
private static void deconstructNestedReccord_3(Object obj) {
if (obj instanceof OuterRecord(
InnerRecord(
CoreRecord(int data1, int data2), int data3))
) {
System.out.println("Data 1: " + data1);
System.out.println("Data 2: " + data2);
System.out.println("Data 3: " + data3);
}
}
That’s all for this article. We will revisit record patterns in upcoming article on Pattern Matching for switch. To learn more, you can have a look at JEP 440.
If you find any significant errors or want to give me some feedback, feel free to contact me at maliksanjoykumar[@]gmail.com.
Sanjoy Kumar Malik is an experienced software architect and technologist. He is passionate about Cloud Computing, Software Architecture, and System Design. Apart from technology and software, he is an avid LinkedIn networker. You can join his 5.5+ lacs supporters on LinkedIn.