Java 14 Records Class

Filed Under: Java
Java 14 Records Class

Java 14 introduced a new way to create classes called Records. In this tutorial, we will learn:

  • Why do we need Java Records
  • How to create Records and use it
  • Overriding and extending Records classes

Recommended Reading: Java 14 Features

Why do we need Java Records?

One of the common complaints with Java has been its verbosity. If you have to create a simple POJO class, it requires the following boiler-plate code.

  • Private fields
  • Getter and Setter Methods
  • Constructors
  • hashCode(), equals(), and toString() methods.

This verbosity is one of the reasons for high interest in Kotlin and Project Lombok.

In fact, the sheer frustration of writing these generic methods each and every time lead to the shortcuts to create them in Java IDEs such as Eclipse and IntelliJ IDEA.

Here is the screenshot showing Eclipse IDE option to generate the ceremonial methods for a class.

Eclipse Shortcuts Generate Ceremonial Methods
Eclipse Shortcuts to Generate Ceremonial Methods

Java Records are meant to remove this verbosity by providing a compact structure to create the POJO classes.

How to Create Java Records

Java Records is a preview feature, which is developed under JEP 359. So, you need two things to create Records in your Java projects.

  1. JDK 14 installed. If you are using an IDE, then it must provide support for Java 14 too. Both Eclipse and IntelliJ already provide support for Java 14, so we are good here.
  2. Enable Preview Feature: By default, the preview features are disabled. You can enable it in Eclipse from the Project Java Compiler setting.
Java 14 Enable Preview Feature In Eclipse
Java 14 Enable Preview Feature In Eclipse

You can enable Java 14 preview features in the command line using the --enable-preview -source 14 option.

Let’s say I want to create a Employee model class. It will look something like the following code.

package com.journaldev.java14;

import java.util.Map;

public class Employee {

	private int id;
	private String name;
	private long salary;
	private Map<String, String> addresses;

	public Employee(int id, String name, long salary, Map<String, String> addresses) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
		this.addresses = addresses;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public long getSalary() {
		return salary;
	}

	public Map<String, String> getAddresses() {
		return addresses;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((addresses == null) ? 0 : addresses.hashCode());
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + (int) (salary ^ (salary >>> 32));
		return result;
	}

	@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;
		if (addresses == null) {
			if (other.addresses != null)
				return false;
		} else if (!addresses.equals(other.addresses))
			return false;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (salary != other.salary)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + ", addresses=" + addresses + "]";
	}

}

Phew, that’s 70+ lines of auto-generated code. Now let’s see how to create an Employee Record class, which essentially provides the same features.

package com.journaldev.java14;

import java.util.Map;

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}

Wow, this can’t go any shorter than this. I am already loving Record classes.

Now, let’s use the javap command to figure out what is happening behind the scene when a Record is compiled.

# javac --enable-preview -source 14 EmpRecord.java
Note: EmpRecord.java uses preview language features.
Note: Recompile with -Xlint:preview for details.

# javap EmpRecord      
Compiled from "EmpRecord.java"
public final class EmpRecord extends java.lang.Record {
  public EmpRecord(int, java.lang.String, long, java.util.Map<java.lang.String, java.lang.String>);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int id();
  public java.lang.String name();
  public long salary();
  public java.util.Map<java.lang.String, java.lang.String> addresses();
}
# 
Java Record Class Details
Java Record Class Details

If you want more internal details, run the javap command with -v option.

# javap -v EmpRecord 

Important Points about Record Classes

  1. A Record class is final, so we can’t extend it.
  2. The Record classes implicitly extend java.lang.Record class.
  3. All the fields specified in the record declaration are final.
  4. The record fields are “shallow” immutable and depend on the type. For example, we can change the addresses field by accessing it and then making updates to it.
  5. A single constructor is created with all the fields specified in the record definition.
  6. The Record class automatically provides accessor methods for the fields. The method name is the same as the field name, not like generic and conventional getter methods.
  7. The Record class provides hashCode(), equals(), and toString() implementations too.

Using Records in Java Program

Let’s look at a simple example of using our EmpRecord class.

package com.journaldev.java14;

public class RecordTest {

	public static void main(String[] args) {
		
		EmpRecord empRecord1 = new EmpRecord(10, "Pankaj", 10000, null);
		EmpRecord empRecord2 = new EmpRecord(10, "Pankaj", 10000, null);

		// toString()
		System.out.println(empRecord1);
		
		// accessing fields
		System.out.println("Name: "+empRecord1.name()); 
		System.out.println("ID: "+empRecord1.id());
		
		// equals()
		System.out.println(empRecord1.equals(empRecord2));
		
		// hashCode()
		System.out.println(empRecord1 == empRecord2);		
	}
}

Output:

EmpRecord[id=10, name=Pankaj, salary=10000, addresses=null]
Name: Pankaj
ID: 10
true
false

The Record object works in the same way as any model class, data object, etc.

Extending Records Constructor

Sometimes, we want to have some validations or logging in our constructor. For example, employee id and salary should not be negative. The default constructor won’t have this validation. We can create a compact constructor in the record class. The code of this constructor will be placed at the start of the auto-generated constructor.

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
	
	public EmpRecord {
		if (id < 0)
			throw new IllegalArgumentException("employee id can't be negative");

		if (salary < 0)
			throw new IllegalArgumentException("employee salary can't be negative");
	}

}

If we create an EmpRecord like the following code:

EmpRecord empRecord1 = new EmpRecord(-10, "Pankaj", 10000, null);

We will get runtime exception as:

Exception in thread "main" java.lang.IllegalArgumentException: employee id can't be negative
	at com.journaldev.java14.EmpRecord.<init>(EmpRecord.java:9)

Can Records Classes Have Methods?

Yes, we can create method in records.

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {

	public int getAddressCount() {
		if (this.addresses != null)
			return this.addresses().size();
		else
			return 0;
	}
}

But, records are meant to be data carriers. We should avoid having utility methods in a record class. For example, the above method can be created in a utility class.

If you think that having a method is must for your Record class, think carefully if you really need a Record class?

Conclusion

Java Records are a welcome addition to the core programming features. You can think of it as a “named tuple”. It’s meant to create a data carrier object with compact structure, avoiding all the boiler-plate code.

Leave a Reply

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

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages