Records
are classes to define immutable data. This is added as a preview feature in
Jdk15.
Let’s
learn more about records using an example.
Without
records
Prior to
records, we define an immutable class like below.
Employee.java
package com.sample.app.recrods;
import java.util.Objects;
public final class Employee {
private final int id;
private final String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
return id == other.id && Objects.equals(name, other.name);
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + "]";
}
}
With records,
we can create same Employee immutable class in a compact way.
public record Employee(int id, String name) {
}
The
declaration of record specifies
a.
Name
-> Employee
b.
Header
-> (int id, String name)
c.
Body
-> {}
Name
specifies the record name, Header specifies the properties of the record.
Let’s
decompile the Employee class file and explore further
Compile
Employee.java class by enabling preview features.
$javac --release 15 --enable-preview Employee.java
Note: Employee.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
Let’s
decompile Employee.cass file.
$javap Employee.class
Compiled from "Employee.java"
public final class com.sample.app.recrods.Employee extends java.lang.Record {
public com.sample.app.recrods.Employee(int, java.lang.String);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public int id();
public java.lang.String name();
}
From the
above snippet, you can deduce.
a.
Records
implicitly extends java.lang.Records class.
b.
For
each component or variable in the record header (id, name),
1.
A
private final field is defined.
2.
A
public access method with the same name as variable name is defined.
c.
Override
the implementation of hashCode, equals and toString methods.
How to
get an instance of Employee record?
Similar to
a class, a record is instantiated with the new keyword.
Employee
emp1 = new Employee(1, "Krishna");
RecordDemo.java
package com.sample.app;
import com.sample.app.recrods.Employee;
public class RecordDemo {
public static void main(String[] args) {
Employee emp1 = new Employee(1, "Krishna");
System.out.println("id = %d".formatted(emp1.id()));
System.out.println("name = %s".formatted(emp1.name()));
}
}
Output
id = 1
name = Krishna
When
you define a record, compiler provide some default implementation to hashCode,
equals and toString methods.
You can
confirm the same from below application.
RecordDemo1.java
package com.sample.app;
import com.sample.app.recrods.Employee;
public class RecordDemo1 {
public static void main(String[] args) {
Employee emp1 = new Employee(1, "Krishna");
Employee emp2 = new Employee(2, "Krishna");
Employee emp3 = new Employee(1, "Krishna");
System.out.println("id = %d".formatted(emp1.id()));
System.out.println("name = %s".formatted(emp1.name()));
System.out.println("\nemp1.hashCode() = %d".formatted(emp1.hashCode()));
System.out.println("emp1.toString() = %s".formatted(emp1.toString()));
System.out.println("\nemp2.hashCode() = %d".formatted(emp2.hashCode()));
System.out.println("emp2.toString() = %s".formatted(emp2.toString()));
System.out.println("\nemp3.hashCode() = %d".formatted(emp3.hashCode()));
System.out.println("emp3.toString() = %s".formatted(emp3.toString()));
System.out.println("\nemp1.equals(emp2) = %s".formatted(emp1.equals(emp2)));
System.out.println("emp1.equals(emp3) = %s".formatted(emp1.equals(emp3)));
}
}
Output
id = 1
name = Krishna
emp1.hashCode() = 1207521705
emp1.toString() = Employee[id=1, name=Krishna]
emp2.hashCode() = 1207521736
emp2.toString() = Employee[id=2, name=Krishna]
emp3.hashCode() = 1207521705
emp3.toString() = Employee[id=1, name=Krishna]
emp1.equals(emp2) = false
emp1.equals(emp3) = true
How to
add validations to the record properties?
we can
define a canonical constructor that perform some validations.
A
canonical constructor signature is the same as the header, and which
assigns each private field to the corresponding argument from the new
expression which instantiates the record;
Employee.java
package com.sample.app.recrods;
public record Employee(int id, String name) {
public Employee(int id, String name) {
if (id <= 0) {
throw new IllegalArgumentException("id must be > 0");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name should not be null or blank");
}
this.id = id;
this.name = name;
}
}
Compact
canonical constructor
A compact
canonical constructor omit the list of formal parameters as they are declared
implicitly, and the private fields corresponding to record components cannot be
assigned in the body but are automatically assigned to the corresponding formal
parameter (this.id = id;) at the end of the constructor.
For
example, following snippet use a compact canonical constructor that validates
its (implicit) formal parameters.
Employee.java
package com.sample.app.recrods;
public record Employee(int id, String name) {
public Employee {
if (id <= 0) {
throw new IllegalArgumentException("id must be > 0");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name should not be null or blank");
}
}
}
Constructor
rules are different for a class and a record
The rules
for constructors are different in a record than in a normal class. A normal
class without any constructor declarations is automatically given a default
constructor. In contrast, a record without any constructor declarations is
automatically given a canonical constructor that assigns all the private fields
to the corresponding arguments of the new expression which instantiated the
record.
Can I
overload the constructors in a record?
Yes, you
can overload the constructors. But keep in mind that a non-canonical
constructor must start with an explicit invocation to a constructor.
package com.sample.app.recrods;
public record Employee(int id, String name) {
public Employee() {
this(Integer.MAX_VALUE, "");
}
public Employee(int id) {
this(id, "");
}
public Employee(String name) {
this(Integer.MAX_VALUE, name);
}
}
Can a
record is nested in another record?
Yes, a
record can be declared top level or nested, and can be generic. If a record is
itself nested, then it is implicitly static
package com.sample.app.recrods;
public record Employee(int id, String name) {
public record Person(){
}
}
Can a record
define static methods, static blocks and static fields?
Yes, it
can.
Employee.java
package com.sample.app.recrods;
public record Employee(int id, String name) {
static {
System.out.println("Employee record loaded");
}
private static int count = 0;
public Employee {
count++;
}
public static int empCount() {
return count;
}
}
RecordDemo3.java
package com.sample.app;
import com.sample.app.recrods.Employee;
public class RecordDemo3 {
public static void main(String[] args) {
Employee emp1 = new Employee(1, "Krishna");
Employee emp2 = new Employee(2, "Ram");
System.out.println("Total employees : "+ Employee.empCount());
}
}
Output
Employee record loaded
Total employees : 2
Can a
record has instance methods?
Yes, you
can define instance methods in a record. A record can explicitly declare public
accessor methods which correspond to components, and can also declare other
instance methods.
Employee.java
package com.sample.app.recrods;
public record Employee(int id, String name) {
static {
System.out.println("Employee record loaded");
}
private static int count = 0;
public Employee {
count++;
}
public static int empCount() {
return count;
}
public String empDetails() {
return this.toString();
}
}
In the
above snippet, empDetails is an instance method.
Can a
record implement an interface?
Yes, a
record can implement interfaces.
public record Employee(int id, String name) implements Serializable {
}
Can a
record declare nested types?
A record
can declare nested types, including nested records
What
are the limitations on records?
a.
Since
a Record extends java.lang.Record implicitly, a record can’t extend any class explicitly.
b.
Since
a record is implicitly final, it can’t be abstract.
c.
The
implicitly declared fields corresponding to the record components of a record
class are final and moreover are not modifiable via reflection (doing so will
throw IllegalAccessException).
d.
A
record cannot declare native methods.
References
https://openjdk.org/jeps/384
https://openjdk.org/jeps/12
Previous
Next
Home