1

I am looking for guidance with regard to overriding both hashcode and equals in a subclass.

I have found a similar question here: Overriding equals() & hashCode() in sub classes ... considering super fields

But what I want is something slightly different.

Imagine this (somewhat stupid) example:

class Food {

  String name;

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Food) {
      Food other = (Food)obj;
      return name.equals(other.name);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return name.hashCode();
  }
}

class Vegetable extends Food {
  // No additional fields here
  // Some methods here
}

class Fruit extends Food {
  // No additional fields here
  // Some methods here
}

Given that:

  1. The subclasses do not add any extra fields
    • in this example at least, they are basically just marker classes
  2. A Fruit and Vegetable with the same name, should not be equal

Questions:

  1. Would you expect the subclass equals to simply contain an instanceof check for the subclass and a call to super.equals?
  2. How should the hashcode be structured in an attempt to have Fruit and Vegetable instances with the same name have different hashcodes?
Community
  • 1
  • 1
Dylan Watson
  • 2,283
  • 2
  • 20
  • 38
  • 1
    you could use the class name as a component of the hashcode (for example: `17*classname.hashcode() + super.hashcode()`) –  Mar 17 '14 at 06:28
  • 1
    Note that a lot of data structures in the Java API are showing the behavior you are showing here. Take a look at all sort of Maps. A HashMap can equal a LinkedHashMap for instance. Maps do check the content of the Map. The implementing class doesn't really matter. – Martijn Courteaux Mar 17 '14 at 06:58

2 Answers2

2

You can just use .getClass() in Foo's .equals():

@Override
public final boolean equals(final Object obj)
{
    if (obj == null)
        return false;
    if (this == obj)
        return true;
    // Class<?> are signletons, therefore we can use ==/!=
    if (getClass() != obj.getClass())
        return false;
    return name.equals(((Foo) obj).name);
}

.getClass() will return the class of the object, therefore Fruit or Vegetable, or even Food.

For .hashCode(), name.hashCode() is fine. Don't forget that there is no requirement that the hashcode of two objects which are not .equals() be different. But if you want to, then .getClass() can be used here as well:

@Override
public final int hashCode()
{
    return 31 * getClass().hashCode() + name.hashCode();
}
fge
  • 119,121
  • 33
  • 254
  • 329
  • Yeah seems fair, except that I was trying be good and attempting to stick to Josh Bloch's idea of 'Always override hashCode when you override equals' – Dylan Watson Mar 17 '14 at 06:34
  • 1
    Well you do override hashCode. Again, there is no requirement that if `!o1.equals(o2)` then `o1.hashCode() != o2.hashCode()`. Or maybe you misinterpreted the "you don't care" part. I meant "this is OK" :=) – fge Mar 17 '14 at 06:42
  • Note that this is a legal hashCode() implementation although pretty useless: `return 42;` – fge Mar 17 '14 at 06:43
1
  1. Would you expect the equals to simply contain an instanceof check and a call to super.equals?

instanceof is dangerous here because Food is not abstract. This would mean equals was not symmetric.

someFruit.equals(someFood) // will be false
someFood.equals(someFruit) // will be true

This may not turn in to a problem but it's a thing you should consider.

If you don't want to break the contract, this is a case where Food should check whether this.getClass() == obj.getClass(). If you do that then you also don't necessarily need to override it in the subclasses.

Otherwise it doesn't really matter. The method is defined contractually and you can implement it however you want.

  1. How should the hashcode be structured in an attempt to have Fruit and Vegetable instances with the same name have different hashcodes?

They don't need to be different.

Radiodef
  • 37,180
  • 14
  • 90
  • 125