Site icon Experiences Unlimited

Java 16 – Records and Constructor

In my previous post, I introduced you to the new construct called Records which was introduced in Java 16.

In this post, I will go a bit deeper into how you can override the default constructor of a record which is different from the no-arg default constructor of a class. Default constructor in a record is the one that takes all the record parameters as the arguments to the constructor method and it is called a canonical constructor.

Let me create a SimpleProfileRecord which will override the canonical constructor:

record SimpleProfileRecord(String firstName, String lastName){
    public SimpleProfileRecord(String firstName, String lastName){
        System.out.println("This is called");
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

So when I create an instance of this record and print its contents I would get:

The output of System.out.println(new SimpleProfileRecord("Mohamed", "Sanaulla"));

If I try to provide other non-canonical constructors, I would get an error as shown in the image below which means any constructor defined in the record should end up initializing all the properties. This makes total sense because all the fields of a record are declared as final which mandates that these should be initialized before the object is eligible for use.

Showing the error when we try to provide a non-canonical constructor

Instead of redeclaring the complete constructor definition Java provides a less verbose way to override the canonical constructor which I have shown in the code below:

record UppercaseProfileRecord(String firstName, String lastName){
    UppercaseProfileRecord {
        firstName = firstName.toUpperCase();
        lastName = lastName.toUpperCase();
    }
}

The compiler implicitly derives the arguments to the constructor block from the record definition. Sometimes I can add custom code to initialize certain properties based on some conditions like shown below:

record AnotherProfileRecord(String firstName, String lastName, String name){
    AnotherProfileRecord{
        if(isEmpty(firstName)){
            throw new IllegalArgumentException("First name cannot be empty");
        }
        if(isEmpty(lastName)){
            throw new IllegalArgumentException("Last name cannot be empty");
        }
        if(Objects.isNull(name) || name.trim().length() == 0){
            name = firstName.concat(" ").concat(lastName);
        }
    }

    boolean isEmpty(String str){
        return Objects.isNull(str) || str.trim().length() == 0;
    }
}

In the above code snippet name is initialized from firstName and lastName only if the value passed to it was empty. Also, look at how I have provided a custom method isEmpty in the record.

Records cannot extend other classes or records but they can implement interfaces as shown in the code below:

record FileServiceResponse(InputStream inputStream, Long fileSize, URL presignedUrl)
        implements AutoCloseable{

    @Override
    public void close() throws Exception {
        if(inputStream != null){
            inputStream.close();
        }
    }
}
Exit mobile version