Lombok: Problem with hashCode() on null attribute when used on hash based structure

Created on 19 Jul 2019  ·  3Comments  ·  Source: projectlombok/lombok

The @EqualsAndHashCode annotation that use Long attribute such as pk can cause some unspected behavor when use with hash based structure (like HashSet).

eg.:

@Entity
@EqualsAndHashCode(of = "pk")
class Foo {

    @Id
    private Long pk;

    private LocalDate createdAt;

}
@Entity
class Foo {

    @Id
    private Long pk;

    private LocalDate createdAt;

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $pk = this.getPk();
        int result = result * 59 + ($pk == null ? 43 : $pk.hashCode());
        return result;
    }

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
}

So when the instance with pk = null and other with pk = 43, it will return the same hash code and it may be considered as same on hash based structure, wich they aren't.

And in the case to use OmniFaces ListConverter that use toString we have the same problem.

Maybe return a hashcode of the object instance rather than an hardcoded constant

Did you already take this scenario, or do you know some way to workaround this situation?!

Version info

  • Lombok => 1.18.8
  • Platform => Windows 10 JDK 11.0.4 IntelliJ

Most helpful comment

43 is probably as good as any other number, and better than -1, 0 and 1. Making it configurable puts adds maintenance load for a very low added value.

Using the Identity hashcode for null would break the equals contract.

All 3 comments

So when the instance with pk = null and other with pk = 43, it will return the same hash code

This doesn't matter. Moreover, there'll be always hash collisions, no matter what you choose.

Note that Arrays.hashCode, String.hashCode and many others produce tons of collisions, rather needlessly.

Maybe return a hashcode of the object instance rather than an hardcoded constant

In my manually written code, I'm asserting that it doesn't get called before an id gets assigned. That's surely strange, but so are all alternatives.

It would be possible to create an annotation parameter that could tell which value should be used when the PK is null, in this case. For default 43.

This would give more control to the developer.

@EqualsAndHashCode(of = "pk", ifAnyNull = -1)

So it will use -1 instead 43, for any field that was null

43 is probably as good as any other number, and better than -1, 0 and 1. Making it configurable puts adds maintenance load for a very low added value.

Using the Identity hashcode for null would break the equals contract.

Was this page helpful?
0 / 5 - 0 ratings