JavaRush /Java Blog /Random EN /Coffee break #230. What are Records in Java and how do th...

Coffee break #230. What are Records in Java and how do they work?

Published in the Random EN group
Source: JavaTechOnline This article provides an in-depth look at the concept of Records in Java with examples, including their syntax, how to create them, and how to use them. Coffee break #230.  What are Records in Java and how do they work - 1Records in Java is one of the great features that was first introduced in Java 14 as a preview feature and finally came out in Java 17 release. Many developers actively use it, which helps them to successfully reduce a huge amount of boilerplate code. Moreover, thanks to records, you don't have to write a single line of code to make a class immutable.

When to use Record in Java?

If you want to pass immutable data between different layers of your application, then using Record can be a good choice. By default, Records in Java are immutable, which means that we cannot change their properties once they are created. As a result, this helps us avoid errors and improves the reliability of the code. Simply put, using Record in Java, we can automatically generate classes.

Where can I use Record in Java?

Generally, we can use records in any situation where we need to declare simple data containers with immutable properties and automatically generated methods. For example, below are a few use cases where records can be useful: Data transfer objects (DTO): We can use Record to declare simple data transfer objects that contain data. This is useful when transferring data between different application layers, such as between the service layer and the database layer. Configuration Objects : Record can be used to declare configuration objects that contain a set of configuration properties for an application or module. These objects typically have immutable properties, making them thread-safe and easy to use. Value objects. Record can be used to declare value objects that contain a set of values ​​that represent a specific concept or domain model. API Responses : When creating a REST API, data is typically returned in the form of JSON or XML. In such cases, you may need to define a simple data structure that represents the API response. Records are ideal for this because they allow you to define a lightweight and immutable data structure that can be easily serialized to JSON or XML. Test data. When writing unit tests, you often need to create test data that represents a specific scenario. In such cases, you may need to define a simple data structure that represents the test data. Records can be ideal for this because they allow us to define a lightweight and immutable data structure with minimal boilerplate code. Tuple-like objects : Records can be used to declare Tuple-like objects that contain a fixed number of associated values. This can be useful when returning multiple values ​​from a method or when working with collections of related values.

What is Record in Java?

Record in Java is a class designed to store data. It is similar to a traditional Java class, but is lighter and has some unique features. Records are immutable by default, which means their state cannot be changed once they are created. This makes them ideal for storing immutable data, such as configuration parameters or values ​​returned from a database query. It is also a way to create a custom data type consisting of a set of fields or variables, as well as methods for accessing and modifying those fields. Record in Java makes working with data easier by reducing the amount of boilerplate code that developers have to write over and over again. The syntax for creating an entry in Java is:
record Record_Name(Fields....)

How to create and use Record in Java with examples?

Let's see how to create and use a record in Java programmatically.

Creating a Record

Programmatically creating a record is not very similar to creating a regular class in Java. Instead of class we use the record keyword . You also need to declare fields with data types in parentheses of the record name. Here is a code example that demonstrates how to create an entry in Java:
public record Book(String name, double price) { }
In this example, we created a record called Book with two fields: name and price . The public keyword indicates that this entry can be accessed outside the package in which it is defined.

Using Record

To use a record in Java, we can instantiate it using the new keyword, just like we do with a regular class. Here's an example:
Book book = new Book( "Core Java" , 324.25);
Here a new Book record instance is created with the name field set to Core Java and the price field set to 324.25 . Once a record is created, its fields can be accessed using the name of the field itself. There are no get or set methods. Instead, the field name becomes the method name.
String name = book.name();

double price = book.price();
Here we see the value of the name and price fields being retrieved from the Book record . In addition to fields, records also have some built-in methods that you can use to manipulate them. For example, toString() , equals() and hashCode() . Remember that Java records are immutable by default, which means that once they are created, their state cannot be changed. Let's look at another example of how to create and use records in Java. Let's take a case where we need a record to store information about a student.
public record Student(String name, int age, String subject) {}
This creates a record called Student with three fields: name , age and subject . We can create an instance of this entry as follows:
Student student = new Student("John Smith", 20, "Computer Science");
We can then get the field values ​​using the field name:
String name = student.name();
int age = student.age();
String subject= student.subject();

What does the Record look like after compilation?

Since Record is just a special kind of class, the compiler also converts it into a regular class, but with some restrictions and differences. When the compiler converts a record (Java file) into bytecode after the compilation process, the generated .class file contains some additional declarations of the Record class . For example, below is the bytecode generated for the Student entry by the Java compiler:
record Student(String name, int age) {   }

Writing to .class file (after compilation)

We can also find the below converted class if we use the javap tool and apply the below command from the command line. It's important to note that the Java command line includes the javap tool , which you can use to view information about the fields, constructors, and methods of a class file. >javap Student Conclusion: If we check the bytecode carefully, we may have some observations:
  • The compiler replaced the Record keyword with class .
  • The compiler declared the class final . This indicates that this class cannot be extended. This also means that it cannot be inherited and is immutable in nature.
  • The converted class extends java.lang.Record . This indicates that all records are a subclass of the Record class defined in the java.lang package .
  • The compiler adds a parameterized constructor.
  • The compiler automatically generated the toString() , hashCode() and equals() methods .
  • The compiler has added methods for accessing fields. Pay attention to the method naming convention - they are exactly the same as the field names, there should not be a get or set before the field names .

Fields in Records

Records in Java define their state using a set of fields, each with a different name and type. The fields of a record are declared in the record header. For example:
public record Person(String name, int age) {}
This entry has two fields: name of type String and age of type int . The fields of a record are implicitly final and cannot be reassigned after the record is created. We can add a new field, but this is not recommended. A new field added to a record must be static. For example:
public record Person(String name, int age) {

   static String sex;
}

Constructors in Records

Like traditional class constructors, record constructors are used to create record instances. Records has two concepts of constructors: the canonical constructor and the compact constructor . The compact constructor provides a more concise way to initialize state variables in a record, while the canonical constructor provides a more traditional way with more flexibility.

Canonical constructor

The default Java compiler provides us with an all-argument constructor (all-field constructor) that assigns its arguments to the corresponding fields. It is known as the canonical constructor. We can also add business logic such as conditional statements to validate the data. Below is an example:
public record Person(String name, int age) {

       public Person(String name, int age) {
           if (age < 18) {
              throw new IllegalArgumentException("You are not allowed to participate in general elections");
           }
      }
}

Compact designer

Compact constructors ignore all arguments, including parentheses. The corresponding fields are assigned automatically. For example, the following code demonstrates the concept of a compact constructor in Records :
public record Person(String name, int age) {

      public Person {
          if (age < 18) {
              throw new IllegalArgumentException("You are not allowed to participate in general elections");
          }
     }
}
In addition to the compact constructor, you can define regular constructors in Record , just like in a regular class. However, you need to make sure that all constructors initialize all fields of the record.

Methods in Records

Records in Java automatically generate a set of methods for each field in the record. These methods are called accessor methods and have the same name as the field they are associated with. For example, if a record has a field named price , then it will automatically have a method called price() that returns the value of the price field . In addition to the automatically generated accessor methods, we can also define our own methods in an entry, just like in a regular class. For example:
public record Person(String name, int age) {
   public void sayHello() {
      System.out.println("Hello, my name is " + name);
   }
}
This entry has a sayHello() method that prints out a greeting using the name field .

How Record helps reduce boilerplate code

Let's look at a simple example to see how Java notations can help eliminate boilerplate code. Let's say we have a class called Person that represents a person with name, age and email address. This is how we define a class in the traditional way. Code without using Record :
public class Person {

    private String name;
    private int age;
    private String email;

    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public String getName() {

       return name;
    }

    public int getAge() {
       return age;
    }

    publicString getEmail() {
       return email;
    }

    public void setName(String name) {
       this.name = name;
    }

    public voidsetAge(int age) {
       this.age = age;
    }

    public void setEmail(String email) {
       this.email = email;
    }

    @Override
    public String toString() {
       return "Person{" +
         "name='" + name + '\'' +
         ", age=" + age +
         ", email='" + email + '\'' +
      '}';
    }
}
As we can see, this class requires a lot of boilerplate code to define the fields, constructor, getters, setters, and toString() method . Additionally, suppose we want to make this class immutable. To do this we will have to do some additional steps such as:
  • Declare the class as final so that it cannot be extended.
  • Declare all fields private and final so that they cannot be changed outside the constructor.
  • Don't provide any setter methods for fields.
  • If any of the fields are mutable, you should return a copy of them instead of returning the original object.
Code using Record :
public record Person(String name, int age, String email) {}
That's all! With just one line of code, we have defined a class that has the same fields, constructor, getters, setters, and toString() method as a traditional class. The Record syntax takes care of all the boilerplate code for us. From the above example, it is clear that using records in Java can help eliminate boilerplate code by providing a more concise syntax for defining classes with a fixed set of fields. Less code is required to define and use records than the traditional approach, and records provide direct access to fields using methods to update the fields. This makes the code more readable, maintainable, and less error-prone. Additionally, with Record syntax , we don't need to do anything extra to make the class immutable. By default, all fields in a record are final and the record class itself is immutable. Therefore, creating an immutable class using write syntax is much simpler and requires less code than the traditional approach. With records, we don't need to declare fields as final , provide a constructor that initializes the fields, or provide getters for all fields.

Common Record Classes

Java can also define generic Records classes . A generic entry class is an entry class that has one or more type parameters. Here is an example:
public record Pair<T, U>(T first, U second) {}
In this example , Pair is a generic record class that takes two parameters of type T and U . We can create an instance of this record as follows:
Pair<String, Integer>pair = new Pair<>( "Hello" , 123);

Nested class inside Record

You can also define nested classes and interfaces within an entry. This is useful for grouping related classes and interfaces, and can help improve the organization and maintainability of your codebase. Here is an example of an entry containing a nested class:
public record Person(String name, int age, Contact contact){

    public static class Contact {

       private final String email;
       private final String phone;

       public Contact(String email, String phone){
           this.email = email;
           this.phone = phone;
       }

       public String getEmail(){
          return email;
       }

       public String getPhone(){
          return phone;
       }
    }
}
In this example, Person is an entry containing a nested Contact class . In turn, Contact is a static nested class that contains two private final fields: email address and phone. It also has a constructor that accepts an email and a phone number, and two getter methods: getEmail() and getPhone() . We can create a Person instance like this:
Person person = new Person("John",30, new Person.Contact("john@example.com", "123-456-7890"));
In this example, we created a new Person object named John , age 30, and a new Contact object with email john@example.com and phone 123-456-7890 .

Nested interface inside Record

Here is an example entry containing a nested interface:
public record Book(String title, String author, Edition edition){
    public interface Edition{
       String getName();
   }
}
In this example, Book is the entry containing the nested Edition interface . In turn, Edition is an interface that defines a single getName() method . We can create a Book instance as follows:
Book book = new Book("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", new Book.Edition() {

   public String getName() {

      return "Science Fiction";
   }
});
In this example, we create a new Book object with the title The Hitchhiker's Guide to the Galaxy by Douglas Adams and a new anonymous implementation of the Edition interface that returns the name Science Fiction when the getName() method is called .

What else can Records do?

  • Entries can define custom constructors. Records support parameterized constructors, which can call a default constructor with provided parameters in their bodies. In addition, records also support compact constructors, which are similar to default constructors but can include additional functionality such as checks in the body of the constructor.
  • Like any other class in Java, Record can define and use instance methods. This means that we can create and call methods specific to the recording class.
  • In Record , defining instance variables as class members is not allowed because they can only be specified as constructor parameters. However, records support static fields and static methods, which can be used to store and access data common to all instances of the record class.

Can a record implement interfaces?

Yes, writing in Java can implement interfaces. For example, the code below demonstrates the concept:
public interface Printable {
   void print();
}
public record Person(String name, int age) implements Printable {
   public void print() {
      System.out.println("Name: " + name + ", Age: " + age);
   }
}
Here we have defined a Printable interface with a single print() method . We also defined a Person entry that implements the Printable interface . The Person record has two fields: name and age , and overrides the Printable interface's print method to print the values ​​of these fields. We can instantiate a Person entry and call its print method like this:
Person person = new Person("John", 30);
person.print();
This will output Name: John, Age: 30 to the console . As shown in the example, Java records can implement interfaces just like regular classes. This can be useful for adding behavior to an entry or for ensuring that an entry conforms to a contract defined by an interface.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION