Java Records

Case Study: A Point Class

Java Records were added to Java in version 14. They are a time-saving method for creating objects whose primary purpose is representing data. Let's remake the Point class we created in the equals page.

Let's make one and inspect it in jshell.


public record Point(int x, int y)
{
}
            

Ooh, lookie! See what we got for free!

jshell> /open Point.java

jshell> Point p = new Point(3,4);
p ==> Point[x=3, y=4]

jshell> Point q = new Point(3,4);
q ==> Point[x=3, y=4]

jshell> Point r = new Point(0,0);
r ==> Point[x=0, y=0]

jshell> p.toString()
$5 ==> "Point[x=3, y=4]"

jshell> p.equals(q)
$6 ==> true

jshell> p.equals(r)
$7 ==> false

jshell> p.equals("Hey, I am a string!");
$8 ==> false

We get an equals() method that meets the Java standard. We also get a toString() method as well.

But there's more!

With a nod to Ron Popeil...


jshell> p.x()
$9 ==> 3

jshell> p.y()
$10 ==> 4

Notice the simple getter methods we got for free.

But what if I don't like the toString() method?

Override it.


public record Point(int x, int y)
{

    @Override
    public String toString()
    {
        return String.format("(%s, %s)", x, y);
    }
}

Here it is in action.


jshell> /open Point.java

jshell> Point p = new Point(3,4);
p ==> (3, 4)

jshell> p.toString();
$3 ==> "(3, 4)"

But what if I want another constructor?

Overload it.


public record Point(int x, int y)
{
    public Point()
    {
        this(0, 0);
    }

    @Override
    public String toString()
    {
        return String.format("(%s, %s)", x, y);
    }
}

Let's inspect our creation.


jshell> /open Point.java

jshell> Point p = new Point();
p ==> (0, 0)

jshell> Point q = new Point(3,4);
q ==> (3, 4)

jshell> p.equals(q)
$4 ==> false

Can I have other methods in a record?

Yes.


public record Point(int x, int y)
{
    public Point()
    {
        this(0, 0);
    }

    @Override
    public String toString()
    {
        return String.format("(%s, %s)", x, y);
    }
    public double distanceTo(Point other)
    {
        return Math.hypot(x - other.x, y - other.y);
    }
}

Now inspect your breathtaking creation.


jshell> /open Point.java


jshell> Point p = new Point();
p ==> (0, 0)

jshell> Point q = new Point(5,12);
q ==> (5, 12)

jshell> p.distanceTo(q)
$4 ==> 13.0

A Final Word

The state in a record is automatically private and final. Since our Point class's state is primitives, our Point objects are immutable.

Records are final classes; this means you cannot subclass them. This forces you to use composition, which is not entirely a bad thing. They are really a tool for representing a particular type of data. Any methods you create should focus on that function.

All Java records are subclasses of java.lang.Record. It's worth a look at this API page.

What not to do Do not put mutable state in a record. The design contract is that records should produce immutable objects. All state should be primitive or immutable.

It is a compiler error to place state variables in a record beyond those declared in the record's header.

You can download the Point class from the navigation area.