Do you need to override hashCode() and equals() for records?

The answer to whether you need it or not would really be - it depends on the implementation of the entity you decide to create as a Record. There are no restrictions at compile or runtime to constraint you form doing so either and that has been always the case for classes extending Object anyway.

heads

On the other hand, one of the primary motivations for the proposal has been the "low-value, repetitive, error-prone code:constructors, accessors, equals(), hashCode(), toString() etc". In a data carrier, this implies quite often in today's Java programming. Hence the decision as stated further was to prefer semantic goals an

...: modeling data as data. (If the semantics are right, the boilerplate will take care of itself.) It should be easy, clear, and concise to declare shallowly-immutable, well-behaved nominal data aggregates.

tails

So, the boilerplate has been taken care of, but do note, you might still for some reason want one of your record components to be not treated as part of the process of comparison between two different objects and that is where you might want to override the default implementation of equals and hashCode provided. Also, there is no doubt in my thoughts around the fanciness that is sometimes desired of a toString and therefore the need to override it as well.

The above mostly cannot be categorized as a compile or runtime failure, but the proposal itself reads the risk that it comes along with:

Any of the members that are automatically derived from the state description can also be declared explicitly. However, carelessly implementing accessors or equals/hashCode risks undermining the semantic invariants of records.

(Note: The latter is mostly my opinion, such that consumers would desire all sorts of flexibilities so that they can use the latest features but in a manner, the existing implementation used to work. You see, backward compatibility matters to a greater extent as well during upgradations.)


No you do not need to define your own hashCode and equals. You may do so if you wish to override the default implementation.

See section 8.10.3 of the specification for details https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10

Note, specifically, the caveat on implementing your own version of these:

All the members inherited from java.lang.Record. Unless explicitly overridden in the record body, R has implicitly declared methods that override the equals, hashCode and toString methods from java.lang.Record.

Should any of these methods from java.lang.Record be explicitly declared in the record body, the implementations should satisfy the expected semantics as specified in java.lang.Record.

In particular, a custom equals implementation must satisfy the expected semantic that a copy of a record must equal the record. This is not generally true for classes (e.g. two Car objects might be equals if their VIN value is the same even if owner fields are different) but must be true for records. This restriction would mean that there is rarely any reason to override equals.


What Is a Java Record? One of the most common complaints about Java is that you need to write a lot of code for a class to be useful. Quite often you need to write the following:

  1. toString()
  2. hashCode()
  3. equals()
  4. Getter methods
  5. A public constructor

For simple domain classes, these methods are usually boring, repetitive, and the kind of thing that could easily be generated mechanically (and IDEs often provide this capability), but as of now, the language itself doesn’t provide any way to do this.

The goal of records is to extend the Java language syntax and create a way to say that a class is “the fields, just the fields, and nothing but the fields.” By you making that statement about a class, the compiler can help by creating all the methods automatically and having all the fields participate in methods such as hashCode().

The records come with a default implementation for hashCode(), equals() and toString() for all attributes inside the record

The default implementation of hashCode()

The record will use the hash code of all attributes inside the record

The default implementation equals()

The record will use all attributes to decide if tow records are equals or not

So any hash implementations e.g. HashSet, LinkedHashSet, HashMap, LinkedHashMap,

etc will use hashCode() and in-case of any collision will use equals()

Default implementation or a custom one?

If you want to use all attributes in hashCode() and equals() then don't override

Do you need to override hashCode() and equals() for records?

It's up to you to keep the default implementation or select only some attributes for that

anything, but if you want custom attribute you can override to decide which attributes decide equality and attributes make the hashCode

How the hashCode is calculated in the default implementation?

will use hashCode of integer and string like this:

    int hashCode = 1 * 31;
    hashCode = (hashCode + "a".hashCode()) & 0x7fffffff;

, The code below with the default implementation for hashCode(), equals()

and toString().

the HashSet didn't add the two of them because the two records have the same hashCode and equals

    static record Record(int id, String name) {

    }

    public static void main(String[] args) {
        Record r1 = new Record(1, "a");
        Record r2 = new Record(1, "a");

        Set<Record> set = new HashSet<>();
        set.add(r1);
        set.add(r2);
        System.out.println(set);

        System.out.println("Hashcode for record1: " + r1.hashCode());
        System.out.println("Hashcode for record2: " + r2.hashCode());

        int hashCode = 1 * 31;
        hashCode = (hashCode + "a".hashCode()) & 0x7fffffff;
        System.out.println("The hashCode: " + hashCode);
    }

, output

[Record[id=1, name=a]]
Hashcode for record1: 128
Hashcode for record2: 128
The hashCode: 128

, Resources:

blogs oracle