Tuesday, February 16, 2021

Restrictions with record classes

In a previous post, we saw how a record class gets converted to a regular class definition during compilation. Though that conceptually shows how a record class behaves, it doesn't represent the conversion accurately. 

Lets see how exactly a record class is getting converted during compilation. We will use the same simple, single line Cat record definition for this analysis

record Cat(String color) {}

Lets compile this record class using the below command

javac --enable-preview --release 15 Cat.java

This will generate the Cat.class file in the same folder. We will analyze this class using reflection. For this purpose, we will use the below reflect utility - which prints out the basic details about the class to the console.

import java.lang.reflect.Modifier;

public class ReflectClass {

    public static void reflect(Class<?> ref) {
        log("Name",ref.getName());
        log("Module",ref.getModule().getName());
        log("PackageName",ref.getPackageName());
        log("Superclass",ref.getSuperclass().getName());
        log("Modifiers",Modifier.toString(ref.getModifiers()));
        log("DeclaredConstructors", ref.getDeclaredConstructors());
        log("DeclaredFields", ref.getDeclaredFields());
        log("DeclaredMethods", ref.getDeclaredMethods());
        
    }

    static void log(String name, Object[] values) {
        log(String.format("****%s****", name));
        int i = 0;
        for (Object val:values) {
            log(name+"-"+i++, val.toString());
        }
    }

    static void log(String name, String value) {
        log(String.format("%-25s: %s", name, value));
    }

    static void log(String data) {
        System.out.println(data);;
    }
    
    public static void main(String args[]) throws ClassNotFoundException {
        reflect(Class.forName(args[0]));
    }

}

Now lets try to analyze the Cat class using this utility by running the below command 

java --enable-preview ReflectClass Cat

This generates the output shown below

Name                     : Cat
Module                   : null
PackageName              :
Superclass               : java.lang.Record
Modifiers                : final
****DeclaredConstructors****
DeclaredConstructors-0   : Cat(java.lang.String)
****DeclaredFields****
DeclaredFields-0         : private final java.lang.String Cat.color
****DeclaredMethods****
DeclaredMethods-0        : public final boolean Cat.equals(java.lang.Object)
DeclaredMethods-1        : public final java.lang.String Cat.toString()
DeclaredMethods-2        : public final int Cat.hashCode()
DeclaredMethods-3        : public java.lang.String Cat.color()

Key details from the output highlighted in yellow. Inferences from this output are

1. The generated Cat class extends java.lang.Record. All record classes extend this class. It implies that a record class cannot extend any other class

2. Also we see the class generated is final - indicating that this record class cannot be extended by any other class

3. The record components gets converted to private final instance variables and there are not setter methods. The record components are immutable and cannot be changed after initialization. Note however that this immutability is shallow and reference attributes within a component may be mutable

4. A significant difference to note when working records is the deviation from the Java convention that the public accessor method is named color() and not getColor(). The generated accessor method does not follow the getter convention

Sample code used in this post can be downloaded from https://github.com/ashokkumarta/awesomely-java/tree/main/2021/02/Language-Features/Record/Restrictions-with-record-classes


No comments:

Post a Comment