Java : Basics of Record class

Record classes make it concise to declare classes that have simple data structures. The record class was added as a language specification in Java 16.

This article provides the basic usage of the record class.

Note:


Comparing normal classes and record classes

Normal class example

Let's think of a very simple class.

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }

    public String getTitle() {
        return title;
    }

    public int getTotalPage() {
        return totalPage;
    }
}

The Book class has title and totalPage fields and getter methods to get them.

public class Book {
    private final String title;
    private final int totalPage;
...

All fields are private final. They are not allowed to modify.

final var book = new Book("TITLE!", 100);

System.out.println(book.getTitle()); // TITLE!
System.out.println(book.getTotalPage()); // 100

The Book class works just fine.

Record class example

The Book class as the normal class can be declared concisely using the record class.

public record Book(String title, int totalPage) {
}

It's very concise!

The record class uses the record keyword instead of the class keyword. And you don't need to declare constructors, fields, and getter methods.

final var book = new Book("TITLE!", 100);

System.out.println(book.title()); // TITLE!
System.out.println(book.totalPage()); // 100

You can create a record object with the new keyword. It is the same as normal classes. And you can get field values with getter methods that have the same name as the parameter names in the header(e.g. title, totalPage).

Features of Record Classes

Auto-declared fields

public record Book(String title, int totalPage) {
}

A record class has auto-declared fields. These fields are private final, with the same name and declared type as the header parameters.

This record class is equivalent to the following normal class :

public class Book {
    private final String title;
    private final int totalPage;
...

You can also add methods to the record class to access auto-declared fields.

public record Book(String title, int totalPage) {

    public void printAll() {
        System.out.println("title : " + title);
        System.out.println("totalPage : " + totalPage);
    }
}
final var book = new Book("TITLE!", 100);
book.printAll();

// Result
// ↓
//title : TITLE!
//totalPage : 100

Auto-declared constructor

public record Book(String title, int totalPage) {
}

A record class has an auto-declared constructor. This constructor initializes the fields with the given parameters.

This record class is equivalent to the following normal class :

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }
...

If you want to validate parameters of the constructor, you can use a compact constructor.

public record Book(String title, int totalPage) {
    public Book {
        if (title == null || title.isEmpty()) {
            throw new IllegalArgumentException("title is invalid! : " + title);
        }
        if (totalPage < 0) {
            throw new IllegalArgumentException("totalPage is invalid! : " + totalPage);
        }
    }
}
try {
    var _ = new Book(null, 100);
} catch (IllegalArgumentException e) {
    System.out.println(e.getMessage());
}

// Result
// ↓
//title is invalid! : null

try {
    var _ = new Book("TITLE!", -1);
} catch (IllegalArgumentException e) {
    System.out.println(e.getMessage());
}

// Result
// ↓
//totalPage is invalid! : -1

This record class is equivalent to the following normal class :

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        if (title == null || title.isEmpty()) {
            throw new IllegalArgumentException("title is invalid! : " + title);
        }
        if (totalPage < 0) {
            throw new IllegalArgumentException("totalPage is invalid! : " + totalPage);
        }

        this.title = title;
        this.totalPage = totalPage;
    }
...

Auto-declared getter methods

public record Book(String title, int totalPage) {
}
final var book = new Book("TITLE!", 100);

System.out.println(book.title()); // TITLE!
System.out.println(book.totalPage()); // 100

A record class has auto-declared methods for getting field values. These method names have the same names as the header parameters of the record class.

This record class is equivalent to the following normal class :

public class Book {
...
    public String title() {
        return title;
    }

    public int totalPage() {
        return totalPage;
    }
...

Auto-implemented equals and hashCode methods

public record Book(String title, int totalPage) {
}
final var book1 = new Book("TITLE!", 100);
final var book2 = new Book("TITLE!", 100);

System.out.println(book1.equals(book2)); // true

System.out.println(book1.hashCode()); // -589266789
System.out.println(book2.hashCode()); // -589266789

A record class has auto-implemented equals method and hashCode method of the Object class.

The book1 and book2 objects have the same content, so the equals method returns true. The hashCode method also returns the same value. This is useful when you use record classes as Map keys.

If a normal class does not override the equals method, it returns false even if objects have the same content. The hashCode method may not return the same value.

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }
}
final var book1 = new Book("TITLE!", 100);
final var book2 = new Book("TITLE!", 100);

System.out.println(book1.equals(book2)); // false

System.out.println(book1.hashCode()); // 366252104
System.out.println(book2.hashCode()); // 1889057031

Auto-implemented toString method

public record Book(String title, int totalPage) {
}
final var book = new Book("TITLE!", 100);

final var str = book.toString();
System.out.println(str); // Book[title=TITLE!, totalPage=100]

A record class has an auto-implemented toString method of the Object class. The implemented toString method returns the record class name and each field as a string. It's useful when debugging.

If a normal class does not override the toString method, it returns a simple string representation of the object.

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }
}
final var book = new Book("TITLE!", 100);

final var str = book.toString();
System.out.println(str); // Book@2235eaab

Official documentation links

7 Record Classes
Record classes, which are a special kind of class, help to model plain data aggregates with less ceremony than normal classes.

Record classes are introduced in the Java Language updates documentation. There are some features that are not covered in this article, it would be better to check it.


8.10. Record Classes
A record declaration specifies a new record class, a restricted kind of class that defines a simple aggregate of values.

For more information, please check the Java Language Specification.

Conclusion

Personally, I often create classes with simple data structures, so record classes are a very nice feature addition. It's also nice that it automatically implements equals, hashCode, and toString methods.

Using record classes would make your code simpler and more readable. Let's make good use of it.


Related posts

To top of page