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.