3

@Edit: I'm using this library http://jqno.nl/equalsverifier/ to check if equals and hashCode are written properly.

Let's say we have this class:

final class Why {
    private final int id;
    private final String name;

    Why(final int id, final String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (!(o instanceof Why)) return false;

        final Why why = (Why) o;

        if (id != why.id) return false;
        return name != null ? name.equals(why.name) : why.name == null;
    }

    @Override
    public int hashCode() {
        return id;
    }
}

In hashCode I'm relaying only on id field, because this will give me pretty good, non colliding hashes. Worth noticing is this hash method comply with all rules for equals-hashCode. I don't want do some fancy tricks with summing hashes, i.e. this:

@Override
public int hashCode() {
    int result = id;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    return result;
}

So could you explain me why EqualsVerifer by default demand using all fields from equals method in hashCode method?

java.lang.AssertionError: Significant fields: equals relies on subValue, but hashCode does not.
MAGx2
  • 3,149
  • 7
  • 33
  • 63
  • 5
    I've never heard of `EqualsVerifier`. But it's the wrong way round. Any field you use in `hashCode` _must_ be used in `equals`; but there's no good reason why any field you use in `equals` needs to be used in `hashCode`. – Dawood ibn Kareem Nov 09 '17 at 22:27
  • Totally forgot to write that I'm using Equals Verifier - I've putted it only in tags. http://jqno.nl/equalsverifier/ – MAGx2 Nov 09 '17 at 22:27
  • 1
    Yeah, so maybe it's a default setting in `EqualsVerifier`. If it's not possible to override it, I'd consider that a bug. – Dawood ibn Kareem Nov 09 '17 at 22:28
  • It looks like an over-zealous check to me. There is no reason why using id and name in equals should require you to use them both in the hashcode. Using just id is consistent with equals (but it would reduce the chance of collision to use name too). – Andy Turner Nov 09 '17 at 22:28
  • I can turn it off by checking flag ```STRICT_HASHCODE```, but I'm just curious why it's default behaviour. – MAGx2 Nov 09 '17 at 22:30
  • Well the requirement is that if two objects are equal, then they must have the same hashcode. Not using the same fields for hashcode and equals will break this covenant – smac89 Nov 09 '17 at 22:30
  • @DawoodibnKareem I think you would still lose it if it's part of `equals()`. – shmosel Nov 09 '17 at 22:30
  • You'd have to ask the makers of `EqualsVerifier` I guess. – Dawood ibn Kareem Nov 09 '17 at 22:30
  • @smac89 yes, but there is no requirement for objects with the same hashcode to be equal. – Andy Turner Nov 09 '17 at 22:30
  • @shmosel, Hmm, you're right. Sorry. – Dawood ibn Kareem Nov 09 '17 at 22:31
  • Just use `Objects.hash(...)`, and put all your fields in there – smac89 Nov 09 '17 at 22:32
  • @smac89 All that covenant implies is that the fields used for `hashCode` must be a subset of the fields used for `equals` (or that the covenant is enforced in some other fashion). – Dawood ibn Kareem Nov 09 '17 at 22:32
  • Most likely, it's the default behaviour because you want it in _most_ cases, and the most common reason for the fields of `equals` not showing up in `hashCode` is that the developer has made a mistake. So long as you can override it if you really have a case where `hashCode` should use fewer fields than `equals`, it seems to me like a reasonable kind of default option. – Dawood ibn Kareem Nov 09 '17 at 22:34
  • Unfortunately, it seems this is hardcoded into the library: https://github.com/jqno/equalsverifier/blob/167350bf1975c25798820987f6cb4e0b9f28c068/src/test/java/nl/jqno/equalsverifier/integration/extended_contract/SignificantFieldsTest.java#L33 – smac89 Nov 09 '17 at 22:43
  • What's the purpose of `id` if two objects can have the same `id` but not be equal? – biziclop Nov 09 '17 at 22:49
  • @biziclop instead of ```id``` you can call it ```oneToBillionUniqueId```. This was just an example – MAGx2 Nov 09 '17 at 22:51
  • I see, that makes sense. Sorry, it just stood out for me as odd, so I thought I'd ask. – biziclop Nov 09 '17 at 22:58

1 Answers1

4

Disclaimer: I'm the creator of EqualsVerifier.

The reason why it's the default behaviour, is because it's generally a good practice, and EqualsVerifier wants to encourage its users to follow good practices. You want your hashCode distribution to be as large as possible, to ensure good performance when using hash-based collections.

If you have a good reason to do something else (and judging from your question, it seems like you may have one), you can always disable this behaviour by adding .suppress(Warning.STRICT_HASHCODE).

But I still wonder: if you feel the need to include name in your equals method, apparently your id is not unique. Why then not also include name in hashCode? It's not that much extra work, especially since you can generate one from your IDE, or simply use java.util.Objects.hash(id, name).

jqno
  • 15,133
  • 7
  • 57
  • 84