Skip to content
Theory · Interfaces, enums and records · lesson 7 of 8

Records

Picture an application form with the boxes "name", "date" and "signature". Nobody edits a form once it is filled in and handed over: if something must change, you fill in a new one. On the other hand, you can compare two forms box by box: if every box says the same thing, the applications are the same.

Remember the Point class from the theme on classes. To create a point with its coordinates at once, print it and compare it, you would have to add a constructor, getters, toString and a comparing method. That is dozens of lines, although the point itself is only two numbers. For classes that only hold data, Java 16 brought records:

JAVA
public class Main {
    public static void main(String[] args) {
        Point a = new Point(3, 4);
        Point b = new Point(3, 4);
        System.out.println(a);           // Point[x=3, y=4]
        System.out.println(a.x());       // 3
        System.out.println(a == b);      // false
        System.out.println(a.equals(b)); // true
    }
}

record Point(int x, int y) {}

Let's go through the program step by step:

  1. record Point(int x, int y) {} is the whole declaration. After the name, the parentheses list the components, the boxes of the form, and the body in braces is empty.
  2. new Point(3, 4) calls a constructor, even though nobody wrote one.
  3. println(a) prints Point[x=3, y=4]: the record's name and every box.
  4. a.x() reads the component x. It is a method, and it is named like the component, without get.
  5. a == b gives false: these are two different objects, two separate forms.
  6. a.equals(b) gives true: records are compared by their components, and both hold 3 and 4.

What the compiler writes

A record is a class in which you declare only the data, and the compiler writes the rest for you. The line record Point(int x, int y) {} gives:

  • the fields private final int x and private final int y;
  • a constructor with the parameters x and y in the same order;
  • the accessor methods x() and y();
  • toString in the form Point[x=3, y=4];
  • equals, which compares the components one by one, and hashCode, which gives equal records the same number.

Interviewers like to ask this: how does a record differ from an ordinary class? The answer: a record holds immutable data, and the compiler writes its constructor, accessor methods, equals, hashCode and toString.

A record can implement an interface, like an ordinary class. But it cannot extend another class, and no class can extend it either.

A record cannot be changed

The fields of a record are final: after the constructor nobody changes them, the form is handed over. To "change" a point, you create a new one:

JAVA
Point p = new Point(3, 4);
p = new Point(5, p.y());
System.out.println(p); // Point[x=5, y=4]

The second line builds a new record: its x is 5, and its y is carried over from the old one through p.y(). The variable p now refers to the new object, and the old point stays as it was.

Here the form picture is not exact. If a record component is an array, the box holds not the row of lockers but a ticket with its address. The ticket cannot be replaced, but the contents of the lockers can be changed. Only the boxes themselves are immutable.

The compact constructor

Sometimes the data must be checked or fixed while the form is not handed over yet. For that, a record has a compact constructor: no parameter list, only the record's name and a body:

JAVA
public class Main {
    public static void main(String[] args) {
        System.out.println(new Range(5, 1)); // Range[from=1, to=5]
    }
}

record Range(int from, int to) {
    Range {
        if (from > to) {
            int t = from;
            from = to;
            to = t;
        }
    }
}

Let's see how it works:

  1. Range { … } is the compact constructor: its parameters from and to are the same as the record's.
  2. If from is greater than to, the body swaps the parameters through the variable t.
  3. After the body, the compiler itself adds the assignments this.from = from; and this.to = to;, so the fields get the values already fixed.
  4. new Range(5, 1) prints as Range[from=1, to=5].

Common mistake

Someone tried to change a coordinate of a record:

JAVA
Point p = new Point(3, 4);
p.x = 5; // ✗
TEXT
error: x has private access in Point

The program does not compile. The fields of a record are private, so from outside you read them only through the method p.x(). Inside the record you cannot assign to a field either: it is final, and the compiler answers cannot assign a value to final variable x. To get a point with a different x, create a new record, as in the section above.

Key points

  • record Point(int x, int y) {} declares the data, and the compiler writes the constructor, the methods x() and y(), toString, equals and hashCode.
  • A record's equals compares the components: two records with the same data are equal, even though == gives false for them.
  • The fields of a record are private final: you do not change a record, you create a new one.
  • A compact constructor checks or fixes the values before they reach the fields.