Java SE 15: Records
Started by Java SE 14 a new feature Records has be introduced in review state (see JEP-359, Java versions and releases details), continue Java SE 15 the Records feature evolved into the 2nd preview release (see JEP-384). The motivation of the Records it to provide compact syntax for declaring classes that are transparent holders of shallowly immutable components. The records help to avoid normally required ceremony of defining kind of low value classes ( example data transfer object – DTO) Such classes are nothing more than “data carriers”.
Issues with “data carriers” classes:
- a lot repeating accessors, error-prone, low-value, not maintainable code
- overriding equals(), hashCode(), toString() methods
- possible “unexpected behaviour” due to issues above
The Records do not intent to address a problems with mutable classes using JavaBeans naming convention nor metaprogramming, properties or annotation driven code generation.
What is Java Records?
The records help to reduce a boilerplate code by improved language semantic. The records can be seen as data aggregates. Java Record is defined by a new type of keyword “records” in Java language. It is restricted type of class, similar like the enum. The “records” type declares its representation by shallowly immutable fields. The compiler automatically generates getter methods, toString(), hashCode() and equals() methods. The record has a name and state of its internal field components:
- a private final access modifier for each internal state field
- public constructor with all internal state fields
- public read accessor for each internal state field
- implementation of hashCode() and equals(): two records are qual if the internal fields contains the same state fields
- toString() method implementation with all internal field components and their names
The java records also comes with few restrictions:
- can not extend any other class
- can define only private final internal fields, any other field must be declared as static
- can not be abstract
- records are implicitly final
- can implement an interface
- can be declared on top level or nested
- initiated by the “new” keyword
- the records body can declare: static methods, fields, constructors and nested types
- individual components may be annotated
- if records is nested then is implicitly static
How to use Records ?
The usage of the records is very similar to the normal classes. It requires only less code. Let’s consider the following a simple Vehicle (vehicle has by the definition many types) example:
record definition:
public record VehicleRecordSimple(String name, String type) {
}
usage:
VehicleRecordSimple vehicleSimple1 = new VehicleRecordSimple("skoda", "octavia");
VehicleRecordSimple vehicleSimple2 = new VehicleRecordSimple("skoda", "octavia");
VehicleRecordSimple vehicleSimple3 = new VehicleRecordSimple("skoda", null);
if(vehicleSimple1.equals(vehicleSimple2)){
System.out.println("EQUALS");
System.out.println("Vehicle1:" + vehicleSimple1);
System.out.println("Vehicle2:" + vehicleSimple2);
}
if(!vehicleSimple3.equals(vehicleSimple2)){
System.out.println("NOT EQUALS");
System.out.println("Vehicle1:" + vehicleSimple1);
System.out.println("Vehicle3:" + vehicleSimple3);
}
output:
EQUALS
Vehicle1:VehicleRecordSimple[name=skoda, type=octavia]
Vehicle2:VehicleRecordSimple[name=skoda, type=octavia]
NOT EQUALS
Vehicle1:VehicleRecordSimple[name=skoda, type=octavia]
Vehicle3:VehicleRecordSimple[name=skoda, type=null]
Previously in the text has been mentioned that the main intend of the records is to reduce a boilerplate code that may be produced by communication with databases, remote services, reading data from the files or similar.
Let’s consider another example that demonstrates also mentioned records restrictions. The example implements the interface and custom internals field of enum type:
record definition:
public record VehicleRecord(String name, VehicleType type) implements GeneralVehicle {
public VehicleRecord(String name, String type) {
this(name, Stream.of(VehicleType.values())
.filter(v -> v.name().equals(type.toUpperCase()))
.findFirst().orElse(VehicleType.NONE));
}
}
usage:
VehicleRecord vehicle1 = new VehicleRecord("bmw", VehicleType.MOTOR_CYCLE);
VehicleRecord vehicle2 = new VehicleRecord("bmw", VehicleType.CAR);
VehicleRecord vehicle3 = new VehicleRecord("bmw", VehicleType.MOTOR_CYCLE);
if(vehicle1.equals(vehicle3)){
System.out.println("EQUALS");
System.out.println("Vehicle1:" + vehicle1);
System.out.println("Vehicle3:" + vehicle3);
}
if(!vehicle2.equals(vehicle3)){
System.out.println("NOT EQUALS");
System.out.println("Vehicle2:" + vehicle2);
System.out.println("Vehicle3:" + vehicle3);
}
output:
EQUALS
Vehicle1:VehicleRecord[name=bmw, type=MOTOR_CYCLE]
Vehicle3:VehicleRecord[name=bmw, type=MOTOR_CYCLE]
NOT EQUALS
Vehicle2:VehicleRecord[name=bmw, type=CAR]
Vehicle3:VehicleRecord[name=bmw, type=MOTOR_CYCLE]
Main: Java tutorials