Java 17 record class learning

Java Record

A Java record is a class whose sole purpose is to drive programming with immutable data. Let's look at a simple example.

public record Data( int x, int y)

Here we create a record with headers x and y. The x and y here are called the components of the record.
An equivalent class would look like this:

public class Data {

    final private int x;
    final private int y;
    public Data( int x, int y){
        this.x = x;
        this.y = y;
    }

    public boolean equals(Object o) {
        ...
    }

    public int hashCode() {
       ...
    }

    public String toString() {
        ...
    }
}

record initialization

When we declare a normal class without any constructor, the compiler provides a default constructor with no parameters. For records, an implicit specification constructor based on the record component is provided. You could explicitly create the canonical constructor yourself by doing things like validation, but there's a cleaner way to do it. Let's take a look together.

public record Data(int x, int y) {

    public Data {
        if (x >y) {
            throw new IllegalArgumentException();
        }
        x+=100;
        y+=100;
    }
}

In the above record, I perform a simple validation and once it passes, I add another 100 for each validation. The code above is equivalent to the following:

public class Data {

    final private int x;
    final private int y;
    public Data( int x, int y){
        if (x >y) {
            throw new IllegalArgumentException();
        }
        x+=100;
        y+=100;
        this.x = x;
        this.y = y;
    }
}

The record class cannot be extended, nor does it support extensions

The record class does not support extensions. You simply cannot extend this to any other class, not even the record class. The only implicit superclass it has is java.lang.Record, since using extend to explicitly define a record class results in a compilation error.
Also, record classes are implicitly final. They cannot be declared abstract to allow further extension. This means that you cannot have any sub-records of a record.

implement the interface

The record class allows you to implement interfaces. You can implement any interface you want, whether it's a single interface or multiple interfaces.

public record Data( int x, int y) implements Runnable, Serializable

cannot define its own instance variables

When a header is defined, it represents the state of the record class. This means that there cannot be any other instance variables in the record. The only instance variables that will be created are the ones provided in the header component. However, records can contain static variables that can be accessed in the same way as using the record class name.

define your own method

You can define your own methods to use on the record, including your own versions of accessors, equals, and even hashcode methods. However, you need to make sure that you don't make any changes that would break the meaning of immutability.

You can also define static methods and static initializers, similar to how we use it in class declarations.

application note

Now, the important thing about applying annotations is that when defining annotations, we can apply them to the record component. Annotations apply to scopes, depending on the annotation's target scope. Let's look at different cases.

  • If the annotation targets a field, it will be applied to the private instance variable.
  • In cases where the target is a method, it is applied to the accessor method.
  • If the annotations refer to header parameters, they will refer to the parameters of the canonical constructor parameters.
    For example, if you use the @NotNull annotation that actually applies to fields, methods, and constructors, then it will apply to instance variables, accessor methods, and constructors.

However, if annotations are explicitly defined on custom accessor methods or canonical constructors, annotations on those methods or constructors will only be applied to the corresponding method or constructor.

local record

I see a very useful place for record s when we just want to temporarily hold immutable data in a function.

Let me explain this with an example.

public List<Person> sortPeopleByAge(List<Person> people) {
    
    record Data(Person person, int age){};

    return people.stream()
            .map(person -> new Data(person, computeAge(person)))
            .sorted((d1, d2) -> Double.compare(d2.age(), d1.age()))
            .map(Data::person)
            .collect(toList());
}

As you can see, I created a native record class without any manipulation you might need when creating a class. I use it to store intermediate results and then use it for comparisons for more concise and readable code.

end!

Tags: Java jvm graalvm

Posted by mk_silence on Wed, 25 Jan 2023 09:58:44 +0300