Java Records (Part 2)

Java records - image of old-fashioned record player

This week we continue to look at Java records: a new feature in Java 14. In last week’s post, we looked at the rationale behind records, and we looked at a simple record:

record Person(String name, int age, boolean gender) {}

This week we’ll look at some of the additional features, such as overloading constructors and methods, creating static fields and methods, using generic types with records, and some restrictions when using records.

Before we get started, let’s create a Gender enum that models a person’s gender with more flexibility than a primitive boolean field. We’ll use that in our Person record:

enum Gender { MALE, FEMALE, OTHER }

record Person(String name, int age, Gender gender) {}

Constructors

A public constructor with the declared arguments is automatically generated for us. We can override this constructor and/or create a number of overloaded constructors in a similar way to normal Java classes.

We should do this only for validation, and we should keep the code as simple as possible. For example, we can ensure that the name and gender arguments are not null by using the java.util.Objects.requireNonNull() method.

If we create constructors, we need to call the default/canonical constructor (the one used when defining the record) with the this() syntax. We also need to manually initialise every field. As with normal class constructors and methods, the instance fields can be referenced with the this keyword.

import java.util.Objects;

public record Person(String name, int age, Gender gender) {

    public Person() {
        this("Blank", 0, Gender.OTHER);
    }

    public Person(String name, int age) {
        this(name, age, Gender.OTHER);
    }

    public Person(String name, int age, Gender gender) {
        Objects.requireNonNull(name, "Name cannot be null");
        Objects.requireNonNull(gender, "Gender cannot be null");
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}

If we override the canonical constructor, we can leave out the parameters and the field assignments:

import java.util.Objects;

public record Person(String name, int age, Gender gender) {

    public Person {
        Objects.requireNonNull(name, "Name cannot be null");
        Objects.requireNonNull(gender, "Gender cannot be null");
    }
    // other code
}

Additional Members

We cannot define any extra instance fields in a record. The instance fields are limited to those specified in the record definition. However, we can define static fields. We can also define both static and instance methods.

import java.util.Objects;

public record Person(String name, int age, Gender gender) {

    private static int personCount;

    public Person {
        Objects.requireNonNull(name, "Name cannot be null");
        Objects.requireNonNull(gender, "Gender cannot be null");
        personCount++;
    }

    public static int getPersonCount() {
        return personCount;
    }

    public String allCapsName() {
        return name.toUpperCase();
    }
    // other code
}

We can override the default implementation of the generated methods: the constructor (as shown), and the equals()hashCode(), and toString() methods.

Generics and Java Records

Records can accept generics in the constructor argument list.

The following record models a Parcel that can contain any object, along with its dimensions and weight. This could be used to calculate shipping charges, etc.

public record Parcel(T contents, double weight, 
                     double length, double width, double height) {}

It would be used as follows:

Parcel<Book>   p1 = new Parcel<>(new Book("Design Patterns"), 
         0.95, 240, 190, 40);     // 0.95kg x 240mm x 190mm x 40 mm
Parcel<Camera> p2 = new Parcel<>(new Camera("Nikon Coolpix B500"), 
         0.53, 115, 95, 80); // 0.53kg x 115mm x 95mm x 80 mm

Restrictions on Java Records

Records have some restrictions, including the following:

  • Records extend java.lang.Record, so they cannot extend any other class. However, they can implement interfaces.
  • Records are implicitly final, so they can’t be abstract, nor can any class inherit from them.
  • The instance fields of a record specified in the record declaration are implicitly final.
  • Records cannot declare additional instance fields, but can declare static fields.

Other than those restrictions, records behave like normal classes:

  • Records can implement interfaces.
  • Records are instantiated with the new keyword.
  • Records can be declared inside a class; nested records are implicitly static.
  • We can create generic records.
  • We can declare constructors, instance methods, and nested types inside a record.
  • We can declare static fields, methods and initializers inside a record.
  • We can annotate records and a record’s individual components.

What’s Next?

For more information about records, see JEP 359.

Next week we’ll look at the other side of the record coin: how can we easily create mutable class definitions in a similar way to records? For this, we’ll look at the third party Lombok class library.

Don’t forget to share your comments and throughts – they help me to write better tips.

As always, please share your comments and questions.

Leave a Comment

Your email address will not be published. Required fields are marked *

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.