Contents

Using record

Written by: David Vlijmincx

What is a record

A record class is a concise way to define an object that is shallowly immutable*. The values inside a record are called record components. These are declared in the header of the record like this: record Person(String firstname, String lastname){} here firstname and lastname are both record components. A record header consists of the record keyword, name, and record components.

*Shallowly immutable means that the references that the immutable instance hold cannot change, but the values inside the referred instance can change.

Creating a Record class

Below is an example of all you need to create a record. This will create a constructor and getter methods for the record components and implement the toString(), hashcode() and equals(Object obj) for you.

1
record Person(String firstname, String lastname){}

These are all the methods you could call on our Person record.

1
2
3
4
5
6
Person person = new Person("David", "Vlijmincx");
String firstname = person.firstname();
String lastname = person.lastname();
person.equals(person);
person.toString();
person.hashCode(); 
  • equals(Object obj) Compares two instances based on their record components
  • toString() Prints all the record components
  • hashcode() returns a hashcode based on the record components

Creating a constructor for record classes

Record classes need to have a canonical constructor, which is a constructor with all the record components. This one is generated for you, but there are two ways to override it.

This is the compact version. This is great if you only need to call some methods during construction.

1
2
3
4
record Person(String firstname, String lastname){
    Person {
    }
}

If you need more control, you can create a more familiar constructor. This gives you access to the properties before they are set.

1
2
3
4
5
6
record Person(String firstname, String lastname){
    Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }
}

Creating non-canonical constructors

Creating a constructor with not all the record components results in a Non-canonical record constructor must delegate to another constructor error message. As the error states, we need to call a constructor that is canonical.

Below is an example of a non-canonical constructor (Person(String firstname)) calling a canonical constructor (Person(String firstname, String lastname)) using this().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
record Person(String firstname, String lastname){

    Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    Person(String firstname) {
        this(firstname, null);
    }
}

Calling a method before setting the canonical constructor

Calling a constructor is the first thing you have to do this or super inside a non-canonical constructor. This causes an issue if you want to change a value before assigning it to a field. You cannot do it afterward because a record is immutable. You can call a static method inside the this or super call to circumvent this issue.

In the example below, we call the reverseString method inside the this call of our non-canonical constructor. This way, we can still derive values for our constructor inside the record.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
record Person(String firstname, String lastname){

    Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    Person(String firstname) {
        this(firstname, reverseString(firstname));
    }
    
    private static String reverseString(CharSequence value){
        StringBuilder stringBuilder = new StringBuilder();
        return stringBuilder
                .append(value)
                .reverse()
                .toString();
    }
}

Further reading