The Records Class in Java 14

In this tutorial, we will explore the introduction of a new method called Records for creating classes in Java 14.

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

Java 14 Features that are Highly Recommended to Read

What is the purpose of Java Records?

Java is often criticized for its verbosity, and this is evident when creating a basic POJO class as it involves writing a lot of repetitive code.

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

The excessive use of words is a factor contributing to the strong interest in Kotlin and Project Lombok.

The frustration of writing these generic methods every time has led to the creation of shortcuts in Java IDEs like Eclipse and IntelliJ IDEA.

This screenshot displays the Eclipse IDE feature that allows you to automatically create the required methods for a class.

Eclipse Shortcuts Generate Ceremonial Methods

The purpose of Java Records is to eliminate verbosity by offering a concise way to create POJO classes.

How to Generate Java Records

To create Records in your Java projects, you require two things – Java Records, a preview feature developed as part of JEP 359.

    1. Make sure JDK 14 is installed. If you are using an IDE, it should also have Java 14 support. Eclipse and IntelliJ both have support for Java 14, so we are all set there.

 

    To enable the preview feature, remember that it is disabled by default. In Eclipse, you can enable it by adjusting the Java Compiler settings for the project.
Java 14 Enable Preview Feature In Eclipse

Using the option –enable-preview -source 14 in the command line allows you to activate Java 14 preview features.

Suppose I wish to generate an Employee model class. The code will resemble something like this.

package com.scdev.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 + "]";
	}

}

Wow, that’s over 70 lines of code that was automatically generated. Now, let’s explore how to generate an Employee Record class that essentially offers the same functionalities.

package com.scdev.java14;

import java.util.Map;

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

Oh man, Record classes are awesome! I can’t imagine them being any shorter.

Now, let’s employ the javap command to determine the underlying processes occurring 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

To access additional internal details, execute the javap command with the -v flag.

# javap -v EmpRecord 

Key aspects regarding Record Classes

or

Crucial factors about Record Classes

    1. A Record class cannot be extended because it is marked as final.

 

    1. The Record classes implicitly inherit from the java.lang.Record class.

 

    1. All the fields that are defined in the record declaration will not change.

 

    1. The record fields are immutable at a shallow level based on their respective types. For instance, we are able to modify the addresses field by accessing it and making necessary updates.

 

    1. A constructor is generated automatically with all the fields that are specified in the record definition.

 

    1. Accessor methods for the fields are automatically provided by the Record class. These methods have the same name as the field, unlike the usual generic getter methods.

 

    Furthermore, the Record class also includes implementations for hashCode(), equals(), and toString() methods.

Utilizing Records in Java Application

We will examine a basic illustration of how our EmpRecord class is utilized.

package com.scdev.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);		
	}
}

The result:

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

The Record object functions similarly to other model classes and data objects.

Expanding the Records Constructor

At times, validations or logging may be desired in our constructor. In this case, we need to ensure that employee id and salary values are not negative. The default constructor does not provide this validation. To address this, we can create a concise constructor within the record class. This constructor’s code will be positioned at the beginning of the automatically 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 generate an EmpRecord using the subsequent code:

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

We will encounter a runtime exception like:

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

Do classes that store information have the ability to perform actions?

Certainly, it is possible to generate a method within 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;
	}
}

However, records are designed for carrying data, so it is best to refrain from including utility methods in a record class. Instead, it is more appropriate to create such methods in a separate utility class.

If you believe that a Record class requires a method, it is crucial for you to carefully consider whether you truly need a Record class.

In conclusion

Java Records are a valuable addition to the fundamental programming features. They can be understood as a “named tuple” and enable the creation of concise data carrier objects without the need for excessive code repetition.

 

More tutorials

get pandas DataFrame from an API endpoint that lacks order?(Opens in a new browser tab)

An instructional tutorial on the Jackson JSON Java Parser API.(Opens in a new browser tab)

Java Tutorial for beginners(Opens in a new browser tab)

the ObjectOutputStream in Java(Opens in a new browser tab)

Comprehending Java’s Data Types(Opens in a new browser tab)

Leave a Reply 0

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