4

So the scenario is as follows:

class Feline  
{  
   String name;  
   int age;  

   equals(Object obj) {...}  
   hashCode(){...}  
}  


class Cat extends Feline  
{  
   int teeth;  

   hashCode(){...}  
   equals(Object obj)  
   {
     if (!super.equals(obj))
    {
        return false;   //If I don't want this should I use 
    }    
      ...
   }  
}

The issue is in reality this inheritance is correct, but to the program it is not necessarily true. My thought on this is Cat should actually be composed of a Feline object. The question is, which of these approaches should I take?

EDIT
This is the implementation from Eclipse, default equals/hashcode. It may be that the implementation of equals is not the most accurate way to do this.

Woot4Moo
  • 23,987
  • 16
  • 94
  • 151
  • Not the answer you're looking for but it's definitely related: there is **no way** to add an aspect ("teeth" in your case, altough all [?] feline have teeth... But the point stands ;) and preserve the *equals(...)* contract. From the book *Effective Java* itself, where the following quote appears in bold: *"There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract"*. Appeal to authority: go argue with Joshua Bloch, not me, I'm just quoting the book *Effective Java* ;) – TacticalCoder Oct 17 '11 at 19:41
  • Which chapter is that in Effective Java, I have the book in front of me at the moment. – Woot4Moo Oct 17 '11 at 19:42
  • on my edition (1st edition) I've got that quote appearing in *"Chapter 3: Methods Common to All Objects"* under *"Item 7: Obey the general contract when overriding equals"*. Hope it helps :) – TacticalCoder Oct 17 '11 at 19:45
  • how is this opinion based? It surely has a mathematical answer. – Woot4Moo Mar 10 '17 at 17:44

4 Answers4

9

Ohoh equality checking in the face of inheritance. That's EXTREMELY hard to get right and rather long to describe.

The correct solution isn't as straight forward as one would think, so please go read this - that should clear up all your questions. If not feel free to ask again :)

Edit: So as a short summary of the above link: An equality method should fulfill the following properties:

  • It is reflexive: for any non-null value x, the expression x.equals(x) should return true.
  • It is symmetric: for any non-null values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null values x and y, multiple invocations of x.equals(y) should consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null value x, x.equals(null) should return false.

To guarantee that this works, we need to specify another method: public boolean canEqual(Object other). This method should return true if the other object is an instance of the class in which canEqual is (re)defined, false otherwise.

In other words the method must always be overwritten if we overwrite equal() itself. The implementation itself is trivial, a short example:

class Foo {
    public boolean canEqual(Object other) {
        return other instanceof Foo;
    }
    // equals implementation, etc.
}

The equals method itself must always first check if the given object canEqual itself, e.g. something along the lines of other.canEqual(this) && restOfComparison.

Short example of a class extending the above Foo:

class Bar extends Foo {
    public boolean equals(Object other) {
        if (other instanceof Bar) {
             Bar that = (Bar)other;
             return that.canEqual(this) && otherStuff;
        }            
        return false;
    }

    public boolean canEqual(Object other) {
          return other instanceof Bar;
    }
}
Voo
  • 29,040
  • 11
  • 82
  • 156
  • that is quite helpful. It appears as though the solution is composition. – Woot4Moo Oct 17 '11 at 19:45
  • if you add some information from that link I will be more than happy to mark this as the correct answer. – Woot4Moo Oct 17 '11 at 19:46
  • @Voo: +1, a classic link :) I hesitated mentioning it in my comment about *Effective Java*. – TacticalCoder Oct 17 '11 at 19:46
  • That does the problem because you get rid of inheritance, but you can solve this "correctly" while still using inheritance. It's rather intricate as the article shows, but doable. What's best in your example depends on what you're planning to do with it - inheritance is often overused, but does have its uses. And from a logical point of view a cat should be feline. – Voo Oct 17 '11 at 19:48
  • And yes I assume I should add some short summary - it's just such an intricate topic that I always prefer to link to some real experts, but someone will surely correct me if I get something wrong ;) – Voo Oct 17 '11 at 19:49
  • The commons-lang library helps with doing this. More info here: http://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-java – Sarel Botha Oct 17 '11 at 19:52
0

I don't use eclipse, so I've never seen that implementation. I guess it makes sense in a lot of cases.

There's no problem in just removing that and writing your own from scratch. I would still feel comfortable using inheritance in this case.

I wouldn't worry quite so much about these guidelines you're trying to follow and just do what makes sense now. You can't predict all future changes in requirements. Do make a list of test cases and verify that your design fits the test cases that you know of today.

Sarel Botha
  • 12,419
  • 7
  • 54
  • 59
0

If you inherit from a class your Cat class can not be equal if Feline already fails that check. Thats what

if (!super.equals(obj))
{
    return false;   //If I don't want this should I use 
} 

does. It doesnt say anything what happens if Feline returns true. If its implemented properly it would check class equality and then you just need to check teeth.

The default implementation just provides a shortcut to determine 'unequalness'. To stay with your example a Cat is always a Feline so if Feline says what i have here is not Feline (i.e. a Canine) then you can rest assured it cant be a cat. But if it says yes its a Feline then its up to you to decide if that Feline is a (House)cat or a Tiger.

Stefan
  • 838
  • 3
  • 13
  • 28
-1

I don't usually recommend inheritance unless every child is a parent. You have to ask yourself, is every instance of Cat truly a feline?

DanielCW
  • 149
  • 1
  • 14
  • In the real world yes, the question is more around the equals implementation that Eclipse provides. – Woot4Moo Oct 17 '11 at 19:36