java 14 records
We can create a class with instance fields, constructor, toString(), hashCode(), equals() and getters with a single line of code ...

Check out the Official documentation
We can create a class with instance fields, constructor, toString(), hashCode(), equals() and getters. That requires a lot of boilerplate code:
final class RecordsExample {
private final String name;
private final String shape;
public RecordsExample(String name, String shape) {
this.name = name;
this.shape = shape;
}
@Override
public String toString() {
return "RecordsExample{" +
"name='" + name + '\'' +
", shape='" + shape + '\'' +
'}';
}
@Override
public int hashCode() {
return Objects.hash(name, shape);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RecordsExample that = (RecordsExample) o;
return name.equals(that.name) &&
shape.equals(that.shape);
}
public String getName() {
return name;
}
public String getShape() {
return shape;
}
}
In jdk 14 we can have the same result with records.
record RecordExample(String name, String shape) {}
The record can be without components (arguments).
record Base() {}
Instance fields are not allowed in records. Even if we try, no setter will be generated as records supposed to be shallowly immutable.
@Setter
record Bill(String address) {
// private final String x;
}
A record can have static fields, static and instance methods.
record Restaurant () {
private static int instanceCounter = 0;
public Restaurant {
instanceCounter++;
}
public static int getInstanceCounter() {
return instanceCounter;
}
public void testInstanceVariableAccessibility() {
System.out.println("instance method are allowed in records");
}
}
Records can have a compact constructor with no parameters. When we use a compact constructor we do not need to assign input parameters to private members. Record’s generated methods can be overridden (ctrl + o in Intellij).
record Person(String firstName, String secondName) {
public Person {
if (firstName.length() < 4 || secondName.length() < 4) {
throw new IllegalArgumentException("first or second name length is less then 4");
}
// this.firstName = firstName;
// this.secondName = secondName;
}
@Override
public int hashCode() {
return 18;
}
}
We can also generate a canonical constructor. Canonical constructor has all parameters. Initialization of fields required. alt + Insert for generation in Intellij.
record Animal(String x, String y) {
public Animal(String x, String y) {
this.x = x;
this.y = y;
}
}
Records have a custom constructor, that is not required to have all parameters, but has to call a canonical constructor.
record Administrator(String firstName, String secondName) {
public Administrator(String firstName) {
this(firstName, null);
}
}
Records can be generic. We can create record the same way as a class:
Shape<Triangle> triangleShape = new Shape<>(new Triangle());
/**
* Auxiliary class to be used with generic record example below.
*/
class Triangle {
String shapeName = "triangle";
}
record Shape<T>(T shape) {
}
Nested records are declared static.
class ExampleWithNestedRecord {
record NestedRecord(){}
}
New methods has been added to the Class to support new Record type:
- isRecord() and
- getRecordComponents().
class NewReflectionApi {
/**
* RecordComponent class has information about accessor method, annotations, field name and so on.
*/
public static RecordComponent[] getRecordComponents(Class clazz) {
if (clazz.isRecord()) {
return clazz.getRecordComponents();
}
return new RecordComponent[]{};
}
}
A tuple is a collection of elements of different (not necessarily the same) type. Tuples are an alternative to java records. Java records have a few advantages over tuples:
- records have meaningful class and members names;
- a class can support state validation in their constructor.
class RecordsAlternative {
public static void main(String[] args) {
Pair<String, String> personTuple = new Pair<>("John", "Doe");
System.out.println(personTuple);
Person personRecord = new Person("John", "Doe");
System.out.println(personRecord);
}
}