23

I'm trying to write some generic code to define class equality and hashcodes based on a list of fields. When writing my equals method, I was wondering if, based on Java convention, it should ever be possible for two object of different to be equal. Let me give some examples;

class A {
  int foo;
}
class B {
  int foo;
}
class C extends A {
  int bar;
}
class D extends A {
  void doStuff() { }
}
...
A a = new A(); a.foo = 1;
B b = new B(); b.foo = 1;
C c = new C(); c.foo = 1; c.bar = 2;
D d = new D(); d.foo = 1;

a.equals(b); //Should return false, obviously
a.equals(c);
c.equals(a); //These two must be the same result, so I'd assume it must be false, since c cant possible equal a
a.equals(d); //Now this one is where I'm stuck. 

I see no reason that in the last example the two shouldn't be equal, but they do have different classes. Anyone know what convention dictates? And if they would be equal, how should an equals method handle that?

Edit: if anyone's interested in the code behind this question, see: https://gist.github.com/thomaswp/5816085 It's a little dirty but I'd welcome comments on the gist.

thomas88wp
  • 2,381
  • 19
  • 31
  • 9
    Depends on the class's `equals` implementation. – Evan Mulawski Jun 19 '13 at 17:03
  • Yes, but assuming I'm creating that equals implementation, what would the desired result be? – thomas88wp Jun 19 '13 at 17:04
  • 2
    Whatever **you** want it to be :) There's no "convention" about this, this totally is specific to **your** software logic. – m0skit0 Jun 19 '13 at 17:05
  • 4
    NOTE: if you override `.equals()`, you MUST override `.hashCode()` as well – fge Jun 19 '13 at 17:05
  • 1
    Thanks fge - I do implement it in the full code, but this is a conceptual question. – thomas88wp Jun 19 '13 at 17:06
  • 1
    @fge While it is good practice to do both, you might only use equals e.g. Assert.assertEquals(), List.indexOf() etc. – Peter Lawrey Jun 19 '13 at 17:06
  • 6
    @fge yes it is good practice to always override the `.hashCode()` however that is actually only NEEDED if you are going to be hashing the class (putting it in a Hash map, etc). It is not needed every time. – chancea Jun 19 '13 at 17:08
  • 3
    @PeterLawrey as far as I'm concerned, I see no reason not to do both; should you choose not to, you knowingly break the `Object` contract... Something I would not venture to do ;) – fge Jun 19 '13 at 17:13
  • @chancea the effort required to implement it is so small that I see no reason whatsoever not to do so. Knowingly ignoring this is spelling trouble for users less advanced than you who will reuse your code. – fge Jun 19 '13 at 17:18
  • [When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.](http://en.wikipedia.org/wiki/Duck_typing#In_Java) – cesarse Jun 19 '13 at 17:21
  • 1
    This answer to another question also applies to yours. It quotes Joshua Bloch as favoring the ability of a subclass to be equal to a superclass in cases like your a.equals(d) - http://stackoverflow.com/a/596507/202009 . – Andy Thomas Jun 19 '13 at 17:42
  • 3
    If I have classes for `NaturalNumber`, `Integer`, `Rational` and `ComplexRational`, I'd expect `equals` to work between them (5 = 5/1+0i). If I have `CentimetersLength` and `InchLength`, I'd expect `equals` to behave well: 0cm = 0″, 1cm ≈ 0.3937″, 2cm ≠ 2″. Good luck. – Kobi Jun 20 '13 at 07:52

11 Answers11

23

They could be, but it's typically very difficult to maintain the symmetric and transitive properties of equality in that case. At least while having a useful/intuitive definition of equality.

If you allow a subclass to consider itself equal to an instance of the superclass, then the superclass needs to consider itself equal to an instance of the subclass. Which means that you'll be encoding specific knowledge about the subclass (all possible subclasses?) in the superclass, and downcasting as needed, which isn't very clean.

Or, you do the comparison purely with fields contained in A, and don't override equals() at all. This fixes the above, but has the problem that two instances of C with different values of bar would be considered equal, which is probably not what you want.

Or, you override in C, and compare bar if the other object is an instance of C, but otherwise don't for an instance of A, you have another problem. c1.equals(c2) would be false, but c1.equals(a) would be true, as would c2.equals(a) and so a.equals(c2). This breaks transitivity (since c1 == a and a == c2 implies c1 == c2).


In summary, it's theoretically possible but you would have to cripple your equals implementation to do so. And besides, the runtime class is a property of an object just as much as bar is, so I'd expect objects with different concrete classes to be not equal to each other anyway.

Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • That was my thought... the symmetry is the tough part. But I guess my concern is with anonymous classes, it'd be nice if they didn't break equality when they don't add new fields. – thomas88wp Jun 19 '13 at 17:09
  • @thomas88wp I know what you mean, I've come up against the same issue before with anonymous classes. I think the root problem is that it's not obvious what "equals" actually means, and different contexts will have varying levels of strictness. In your case you just want to know that the objects represent the same values(/fields), but in other cases (e.g. `Set` insertion) a stricter form of equality may be more desirable. – Andrzej Doyle Jun 19 '13 at 17:16
  • 1
    Yup. I guess that's the ultimate problem with generic implementations of anything. The whole reason you override equals() in the first place is so you can determine what it means. Thanks for the advice. – thomas88wp Jun 19 '13 at 17:17
  • 3
    `LinkedList`s and `ArrayList`s can quite definitely (and reasonably) be `equal`. – Louis Wasserman Jun 19 '13 at 17:38
  • make equals() final in the superclass and the symmetry is solved. – Honza Brabec Jun 19 '13 at 17:48
  • I had a situation where it was actually easy. A strategy of adapters, 3 implementations adapting different Address types. all 3 of them implemented an interface, say `PostalCodeAndCountryCodeAddress` so I implemented isEquals() as default method in the interface and equals() on each of the three proxied isEquals(). transient and symmetry holds, same with hashCode(). – juanmf Aug 15 '18 at 05:32
4

First note: when you override .equals(), you absolutely MUST override .hashCode() as well, and obey the defined contract. This is no joke. If you do not obey THAT, you are doomed to encounter problems.

As to handling equality between different classes inheriting one another, if all these classes have a common member, this can be done as such:

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

@Override
public boolean equals(final Object o)
{
    if (o == null)
        return false;
    if (this == o)
        return true;
    if (!(o instanceof BaseClass))
        return false;
    final BaseClass other = (BaseClass) o;
    return commonMember.equals(other.commonMember); // etc -- to be completed
}
Bruno Reis
  • 37,201
  • 11
  • 119
  • 156
fge
  • 119,121
  • 33
  • 254
  • 329
1

Object.equals() is required to be reflexive, symmetric, transitive, consistent across multiple invocations, and x.equals(null) must be false. There are no further requirements beyond that.

If equals() for a class you define does all of those things, then it's an acceptable equals() method. There is no answer to the question of how fine-grained it should be other than the one you provide yourself. You need to ask yourself: Which objects to I want to be equal?

Note, however, that you should have a good reason for making a.equals(b) true when a and b are instances of different classes, as that can make it tricky to implement a correct equals() in both classes.

uckelman
  • 25,298
  • 8
  • 64
  • 82
1

Also remember that you need to follow these rules in order to correctly implement the equals method.

  1. Reflexive : Object must be equal to itself.
  2. Symmetric : if a.equals(b) is true then b.equals(a) must be true.
  3. Transitive : if a.equals(b) is true and b.equals(c) is true then c.equals(a) must be true.
  4. Consistent : multiple invocation of equals() method must result same value until any of properties are modified. So if two objects are equals in Java they will remain equals until any of there property is modified.
  5. Null comparison : comparing any object to null must be false and should not result in NullPointerException. For example a.equals(null) must be false, passing unknown object, which could be null, to equals in Java is is actually a Java coding best practice to avoid NullPointerException in Java.

As Andrzej Doyle rightly said, it becomes difficult to implement the Symetric and Transitive property when it's spread across multiple classes.

Hiro2k
  • 5,254
  • 4
  • 23
  • 28
1

This question seems to me to indicate a muddy architecture. In theory, if you want to implement .equals such that you compare only specific members of the two instances you can do this, but whether this is a good idea really depends on just what purpose these classes are intended to serve (and even then I think there are better approaches).

Are these objects, more or less, just intended to be nothing more than bags of data? If so, perhaps you should create a separate comparison class that determines whether the two objects are "equivalent enough" for the purposes you need, rather than force the objects themselves to care about some alien, unrelated class. I'd be concerned if my code were concerning itself with potentially unrelated objects just because I thought it might be a good idea for them to know about each other due to temporary convenience. Also, as Andrzej mentioned, it's very problematic for a parent class to know or care about specific implementation details of derived classes. I've seen first-hand how this causes problems both subtle and egregious.

Are the objects "doers" rather than data storage? Since your subclass D implements a method then this indicates that it's more than just a bag of data... and in this case, philosophically, I can't see how it would be a good idea to consider A and D equal based merely on a set of value fields. Compare the fields, yes. Consider them equal or equivalent? No. This sounds like a maintainability nightmare in the long haul.

Here's an example of what I think would be a better idea:

class A implements IFoo{

    private int foo;
    public int getFoo(){ return foo; }

}

class B implements IFoo{

    private int foo;
    public int getFoo(){ return foo; }

}

class CompareFoos{
    public static boolean isEquivalent(IFoo a, IFoo b){
        // compare here as needed and return result.
    }
}

IFoo a = new A();
IFoo b = new B();
boolean result = CompareFoos.isEquivalent(a, b);
scriptocalypse
  • 4,942
  • 2
  • 29
  • 41
0

To some extent it depends on what you want the code to do. As long as you are clear what the equals method will compare then I don't see a problem necessarily. I think the problem really comes when you start making lots of sub-classes (e.g. Class E). There's a danger than that one of the sub-classes won't follow the contract so you could end up with

a.equals(e) --> true
e.equals(a) --> false

which would result in strange behaviour.

Personally I try to avoid equals comparing two different classes and returning true but I have done it a couple of times where the whole class hierarchy was under my control and final.

wobblycogs
  • 4,083
  • 7
  • 37
  • 48
0

Consider this example -

abstract class Quadrilateral{
    Quadrilateral(int l, int w){
        length=l;
        width=w;
    }

    private int length, width;
}

class Rectangle extends Quadrilateral{
    Rectangle(int l, int w){super(l,w);}
}

class Square extends Quadrilateral{
    Square(int l){super(l, l);}
}

Square s = new Square(3);
Rectangle r = new Rectangle(3,3);

r.equals(s);//This should be true because the rectangle and square are logically the same.

So yes, there are cases where two different classes can be equal. Though clearly this is not common.

David says Reinstate Monica
  • 19,209
  • 22
  • 79
  • 122
  • ...why should that be true? You have a set of Quadrilaterals and a 3x3 square in it. Now you add a 3x3 rectangle. You expect the set to contain a rectangle. It doesn't. – djechlin Jun 19 '13 at 17:14
  • Well I'm guessing your square should only have one parameter :) but yeah, I'm torn about whether that should be true. – thomas88wp Jun 19 '13 at 17:15
  • Because the square and the rectangle are the same exact shape in this case. @thomas88wp And yes, hah, I suppose this is true. – David says Reinstate Monica Jun 19 '13 at 17:15
  • That depends on `Quadrilaterals` implementation of `.equals` and depends on what constructors are defined in the `Rectangle` and `Square` classes. – chancea Jun 19 '13 at 17:16
  • @chancea It is logicially true and thus should be true in the implementation. Obviously everything is dependent on implementation, thats why we are talking about how it should be done. – David says Reinstate Monica Jun 19 '13 at 17:17
  • @chancea Since it wasnt obvious enough for you, I explicitly defined the constructors. – David says Reinstate Monica Jun 19 '13 at 17:21
  • @Dgrin91 I was just making a statement, I am not the one who down voted – chancea Jun 19 '13 at 17:22
  • @chancea Not say you are, Im saying your comment about the implementation of `.equals()` is silly. – David says Reinstate Monica Jun 19 '13 at 17:23
  • 1
    I downvoted. The square and the rectangle are not "logically" the same. If you call `setWidth(4)` on the square and `setWidth` on the rectangle the resulting objects will have different areas, respectively, 4x4 = 16 and 4x3 = 12. – djechlin Jun 19 '13 at 17:27
  • @djechlin Yes.... and then they will not be equal. If you change the state of an object you clearly are changing it, so previous equalities will also change. IE twins can look alike when they are young and then *not* look alike when they are older. This is because they **changed**. – David says Reinstate Monica Jun 19 '13 at 17:30
  • 1
    Actually more generally `equals` should only be overridden for immutable objects, or more loosely, should only depend on immutable properties. I still disagree that this is good design. Classes should not turn out to be surprise-equals to unrelated objects. – djechlin Jun 19 '13 at 17:35
  • The square/rectangle problem is a problem largely because of the types' mutability. It could be largely solved by having the methods that would mutate the object, instead return a new (Rectangle|Square) representing the object after the changes. But i kinda see the point in a `Rectangle(3, 3)` and a `Square(3)` being equal, iff the two appear and work identically in every case. (Course, `Rectangle.isSquare()` and `Rectangle(int size)` would make just as much sense...) – cHao Jun 19 '13 at 17:35
  • @djechlin How is it a surprise equals if it is clearly suppose to be equal? – David says Reinstate Monica Jun 19 '13 at 17:40
  • 1
    It's poor encapsulation and symmetry will be lost. `Square` knows nothing of `Rectangle` yet `Square`'s `equals` method sometimes returns `true` when input a `Rectangle`. But `Square` has no way of knowing what `Rectangle.equals` will return when a `Square` is input. `Rectangle` may later be augmented with information like color, created timestamp, coordinates, etc. and `Square` will happily return `equals` to a `Rectangle` while `Rectangle` will not return equals to `Square`. – djechlin Jun 19 '13 at 17:54
  • @djechlin if Square should be equal to Rectangle then the equals() method shall be defined in the Quadrilateral and shall be final. That way none of the problems you describe will exist. – Honza Brabec Jun 19 '13 at 18:03
  • @HonzaBrabec No, the entire class should be final then. Subclasses are, obviously, free to add information, and information that presumably would differentiate them, i.e. make them not `equals`, to each other or instances of the parent class. – djechlin Jun 19 '13 at 18:05
  • @djechlin And that is the point where we disagree. Look at it from the other side. If you were comparing two Quadrilaterals and both of them had the same width and length, wouldn't you be surprised to see them non equal? (Assuming that you defined equals for them) – Honza Brabec Jun 19 '13 at 18:12
  • @HonzaBrabec if that *were* to surprise me, I would be *very* surprised to learn one of them had data or functionality that the other one didn't, and therefore that the Quadrilateral class were final in the first place. Also, sidenote, I'm playing along and pretending the quadrilaterals are only defined by length and width. In reality they have 4 sides of possibly all different lengths. – djechlin Jun 19 '13 at 18:15
  • @HonzaBrabec contrapositively if I saw a *non* final Quadrilateral class, I would assume its children might have other data like thickness, color, etc. that might make them not equal even though length and width are the same. – djechlin Jun 19 '13 at 18:16
  • @djechlin Agreed that the Quadrilateral example is not good, I was too just "playing along". I don't want to chat here because it doesn't belong into the comments. Just look at this link http://www.javapractices.com/topic/TopicAction.do?Id=17 – Honza Brabec Jun 19 '13 at 18:21
  • 1
    @HonzaBrabec "If you extend a concrete class, and add a new field which contributes to equals, then it is not possible to write a perfectly correct equals method for the new class. Instead, you should use composition instead of inheritance. (See Effective Java by Joshua Bloch for more information.)" from your link. – djechlin Jun 19 '13 at 18:23
  • @djechlin Yeah basically thats the paragraph I was referring to when you mentioned adding "thickness, color, etc. that might make them not equal even though length and width are the same." – Honza Brabec Jun 19 '13 at 18:28
  • @djechlin: No, the OP has a point. A square is just a special case of a rectangle that has sides of equal lengths, geometrically they are equal, and lastly it's just a different name for the same thing. Finally, the OP's code does not introduce *any* new data in the `Square` class so your argument regarding additional data in a subclass is invalid, and given the code *just as it is* `equals` should indeed return `true`. The post just illustrates that `equals` is intuitively contextual and semantical, and that it cannot be determined by adhering to just type information. – Boris B. Jun 19 '13 at 21:30
0

No.

Set<Parent> p = new HashSet<>();
p.insert(new Parent(1));
p.insert(new Child(1));

Supposing those two instances are equals, does p contain a Child? Very unclear. More fun:

class Parent {
    public int foo = 0;
    void foo() {
        foo = 1;
    }
}

class Child extends Parent {
    @Override
    void foo() {
        foo = 2;
    }
}

Set<Parent> set = new HashSet<>();

for(Parent parent : set) {
    parent.foo();
    System.out.println(parent.foo); // 1 or 2?
}

I challenge you to know what p's element contains without spending more than 1 minute on the Javadoc pages for Set, HashSet, or equals.

djechlin
  • 59,258
  • 35
  • 162
  • 290
0

The short answer is that in very limited cases (e.g. Integer and Long) it is OK for two objects of different classes to be equal, but it is so hard to pull off correctly that it is generally discouraged. It's hard enough to ensure that you create an equals() method that is Symmetric, Reflexive, and Transitive, but on top of that you should also:

  • create a hashCode() method that is consistent with equals()
  • create a compareTo() method that is consistent with equals()
  • further ensure that the compareTo() method is well behaved

Failure to do that can cause problems with using the objects in Sets, Trees, Hashes, and Sorted Lists.

There's a good article on the the topic of equals() based on part of Josh Bloch's book Effective Java but even that only covers equals(). The book goes into much greater detail, especially about how the problem becomes serious quickly once you start using the objects in collections.

Old Pro
  • 24,624
  • 7
  • 58
  • 106
-1

It depends what you need, as @andrzeg said.

One additional thing. Do you want a.equals(d) to be true, but d.equals(a) to be false? when coding you will probably be using instanceof which may or may not be what you want.

Ayman
  • 11,265
  • 16
  • 66
  • 92
-2

I think that this question can be reduced to whether to use instanceof or getClass() in equals() implementation.

If D derives A and your inheritance hierarchy is right then indeed D IS A. It is logical to treat D as any other A and therefore you should be able to check D for equality as you would any other A.

Here is a link where Josh Bloch explains why he favors the instanceof approach.

http://www.artima.com/intv/bloch17.html

Honza Brabec
  • 37,388
  • 4
  • 22
  • 30