49

Assuming the following example:

public record SomeRecord(int foo, byte bar, long baz)
{ }

Do I need to override hashCode and equals if I were to add said object to a HashMap?

shavit
  • 842
  • 1
  • 7
  • 17
  • 5
    No, I'm fairly certain the compiler does that for you. See this article: https://aboullaite.me/java-14-records/. javap reveals that equals, hashCode, toString, getters, and setters are all made for you. It's pretty awesome – user May 10 '20 at 22:35
  • 5
    This is _possible_, because there are corner cases where you would need to, but in general, you don't need to -- and probably don't want to. If you do, make sure to implement the refined semantics of `equals()` and `hashCode()` outlined in `java.lang.Record`. – Brian Goetz May 11 '20 at 13:56
  • 2
    @BrianGoetz when you are here, is there a reason that something that screams loudly “value types” like this `record` feature gets a public constructor, like the ones for `Integer`, `Double`, etc. that just got marked deprecated, instead of a factory method allowing sharing at the JRE’s discretion like `valueOf(…)`? – Holger May 11 '20 at 15:17
  • 1
    @Holger Yes, but I suspect that's a much longer conversation. Can you bring that to the OpenJDK amber-dev list? – Brian Goetz May 11 '20 at 15:43

3 Answers3

45

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.

sprinter
  • 27,148
  • 6
  • 47
  • 78
  • 3
    It calculates the hashCode over all record components. – Johannes Kuhn May 10 '20 at 22:56
  • 1
    @sc0der technically, how `Record` implements `hashCode` is a detail left to the implementation (remembering that Java is a language specification). However in practice I would expect implementers would generally just hash all the members. Collisions are dealt with in the implementation of the collection. The openjdk implementation of HashMap has quite a sophisticated handling that changes the structure depending on number of collisions. – sprinter May 10 '20 at 22:57
  • 1
    See the javadoc for [Record.hashCode()](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Record.html#hashCode()) – Johannes Kuhn May 10 '20 at 22:59
  • 1
    @JohannesKuhn I expect all Java implementations (Of which Oracle's is one) would take that approach. But the specification does not force it as far as I can tell. I can imagine an implementation taking a shortcut if (for example) static analysis of the code shows that one component has a fixed value and, therefore, can be excluded from the hash. – sprinter May 10 '20 at 23:04
  • 2
    A valid (but stupid) implementation of `hashCode()` is `return 1;`. It obeys the contract of both Object.hashCode() and Record.hashCode(). (There is a flag that makes System.identityHashCode(Object) always return 1: `-XX:hashCode=2`) – Johannes Kuhn May 10 '20 at 23:14
  • 1
    Also see the note on the Javadoc for Record: Implementation Requirements: The implicitly provided implementation returns a hash code value derived by combining the hash code value for all the components, according to Object.hashCode() for components whose types are reference types, or the primitive wrapper hash code for components whose types are primitive types. – Johannes Kuhn May 10 '20 at 23:16
  • So you cannot annotate a field so that it's automatically excluded? – Didier A. Dec 23 '22 at 21:50
  • 1
    @DidierA. no I don't believe there are any built-in annotations that exclude a record component from the generated `equals` and `hashCode`. You would need to write your own methods for that. – sprinter Dec 23 '22 at 22:06
  • 1
    @tgdavies that is a good point and I agree that the example is incorrect. I will delete it from the answer. – sprinter Aug 26 '23 at 02:07
6

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.)

Naman
  • 27,789
  • 26
  • 218
  • 353
3

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

Community
  • 1
  • 1
0xh3xa
  • 4,801
  • 2
  • 14
  • 28