equals
method that preserves the contract of
equals
even when subclassses of concrete classes add new fields.
Posts: 409 / Nickname: bv / Registered: January 17, 2002 4:28 PM
How to Write an Equality Method in Java
May 31, 2009 9:00 PM
|
This article, adapted from a section in Programming in Scala, describes a technique for overriding the equals method in Java that preserves the contract of equals even when subclassses of concrete classes add new fields:
http://www.artima.com/lejava/articles/equality.html What do you think of the technique presented in the article? |
Posts: 128 / Nickname: watson / Registered: September 7, 2005 3:37 AM
Re: How to Write an Equality Method in Java
June 1, 2009 10:06 AM
|
Kind of continuing the discussion from the other article, I don't really the obsession over equals. My experience is that ultimately there is never one single way everyone will want to compare two objects, even when they are of the same type. Object.equals is only really useful when you know exactly how it will be used.
The solution here works but the concrete example shown basically comes down to this: if (that instanceof this && this instanceof that) { //... } else { return false; } Which is barely different from the class comparison approach. Is there a different type of implementation that allows for correctness i.e. is there another valid way to implement canEquals()? I haven't thought it through but I would think that any deviation from this formula and we are back to violating the contract of equals. What about when I want to compare a collection of Points as Points without regard to their color? I have to write a separate equals routine. What I'd actually do is create a Comparator and use a TreeSet or something that supports Comparators. It would be nice to have an Equalator that supported hashing but it seems that people would rather chase their tails trying to come up with technically correct but useless equals methods. |
Posts: 2 / Nickname: chansen / Registered: June 1, 2009 1:05 PM
Re: How to Write an Equality Method in Java
June 1, 2009 6:16 PM
|
I'm the co-author of a library which is designed to help with this sort of thing. It uses reflection to implement hashCode(), equals(Object) and toString() for you, so that you don't fall into one of the common pitfalls listed in the article. Which fields/methods are included in or excluded from those operations are controlled via annotations. It's called Pojomatic and can be found at http://www.pojomatic.org . An intro can be found here: http://polyglot-window.blogspot.com/2009/01/introducing-pojomatic.html
|
Posts: 2 / Nickname: chansen / Registered: June 1, 2009 1:05 PM
Re: How to Write an Equality Method in Java
June 1, 2009 6:40 PM
|
Sorry for the double-post, but I should mention that the library I mentioned previously will not help with pitfalls 1 or 3, but we also recommend @Override for #1. As for pitfall #3, it should be noted that using mutable data in equals does not violate the contract of equals because the consistency requirement specifically accounts for mutability.
|
Posts: 12 / Nickname: carfield / Registered: September 16, 2002 3:19 PM
Re: How to Write an Equality Method in Java
June 1, 2009 7:44 PM
|
The amend case is the one hit most of the time :-/
|
Posts: 3 / Nickname: apollodude / Registered: December 10, 2008 6:51 AM
Re: How to Write an Equality Method in Java
June 2, 2009 5:35 AM
|
I don't think LSP is a problem in the article. Defining "equals" is. It has subtly different meanings in different contexts, even where meaning is well-established. We confuse equality and sameness all the time in the world of software development.
Equality means different things not only for different types but also in different domains. Sometimes, you need different meanings of "equals" for the same type in different contexts. Thus, it is unwise to write code that relies on an override of object.equals. One should instead allow client code to specify an equality tester. As for LSP, indeed, if overriding methods must behave 100% the same as the methods which they override, then overriding is useless. If they merely must fulfill (and optionally extend) their parents' contracts, then LSP is respected in the original article. |
Posts: 4 / Nickname: alblue / Registered: April 14, 2003 0:07 AM
Re: How to Write an Equality Method in Java
June 1, 2009 4:12 AM
|
It's really a shame when people cite LSP as if it were some kind of golden rule. In fact, it doesn't exist, and never has - it was cited as a 'wouldn't it be nice if' rule and is trivially disproven:
http://alblue.blogspot.com/2004/07/java-liskov-substution-principle-does.html The problem is that you can't, in fact, blindly substitute one thing for another hand have your program work in exactly the same way. Mocking is an explicit example of this, where you don't want your program to work the same way (but want to supply a different implementation that does nothing/is testable/doesn't have the setup costs etc. Unfortunately, far more people use Bloch's implementation (using instanceof) instead of one which preserves transitivity and obeying the contract as is. You can either (a) test for instance field equality (using getClass and friends) or (b) test behavioural equality using the results of property accessors, but mixing the two is self-inconsistent. |
Posts: 128 / Nickname: watson / Registered: September 7, 2005 3:37 AM
Re: How to Write an Equality Method in Java
June 1, 2009 9:49 AM
|
> The problem is that you can't, in fact, blindly substitute
> one thing for another hand have your program work in > exactly the same way. I'm pretty sure this is not what the LSP means. This is how Robert Martin paraphrases it which is pretty much how I understand it: "Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it." http://www.objectmentor.com/resources/articles/lsp.pdf Or in my own words, if I have a type Foo and a type Bar that extends Foo and I write a method doStuff(Foo), doStuff has to treat Bar instances the same way it treats Foo instances. This is clearly not the case if you use class equality in an equals method. If the substitution could not change the behavior there would be little reason for the substitution in the first place. |
Posts: 409 / Nickname: bv / Registered: January 17, 2002 4:28 PM
Re: How to Write an Equality Method in Java
June 1, 2009 10:59 AM
|
Hi Alex,
> It's really a shame when people cite LSP as if it were > some kind of golden rule. In fact, it doesn't exist, and > never has - it was cited as a 'wouldn't it be nice if' > rule and is trivially disproven: > > http://alblue.blogspot.com/2004/07/java-liskov-substution-p > rinciple-does.html > > The problem is that you can't, in fact, blindly substitute > one thing for another hand have your program work in > exactly the same way. Mocking is an explicit example of > this, where you don't want your program to work the same > way (but want to supply a different implementation that > does nothing/is testable/doesn't have the setup costs > etc. > On LSP, I brought it up in the book chapter and article because I'd heard it as an objection to the canEqual approach. So I wanted to put an "LSP defense" in the text to head this objection off at the pass. When I did, I looked up the original text and it also looked to me like Liskov was saying that subtypes must have exactly the same behavior as supertypes. But what the world thinks LSP means is that *at some level of abstraction" the behavior is compatible, or that the subtype behavior must fulfill the semantic contract of its supertype. I felt a bit uneasy saying the latter is what LSP means, because I also wasn't sure that's what Liskov meant, but it is what the general usage of the term LSP seems to mean.> Unfortunately, far more people use Bloch's implementation > (using instanceof) instead of one which preserves > transitivity and obeying the contract as is. > > You can either (a) test for instance field equality (using > getClass and friends) or (b) test behavioural equality > using the results of property accessors, but mixing the > two is self-inconsistent. > I'm not sure I understand your b. Or maybe your whole last paragraph. Can you clarify? What you wrote in 2004 in the blog post you linked to is what I think has been the conventional wisdom in the Java community for a long time, although it wasn't necessarily widespread wisdom. You wrote: Specifically, in order to rely on the LSP, you have to ensure that M> obeys the contract defined in T, which implies that it must be reflexive, symmetric and transitive. If you don't have that, you don't have a valid overridden method as per the LSP definition, so you can't even use that as your argument. Lastly, if you do try and use instanceof , you can guarantee that you break symmetry, because the instanceof is asymmetric. I did point out in my proof on JavaWorld, that it is possible to overcome this behaviour; however, for any implementation of .equals() using instanceof that obeys reflexivity and symmetry will be forced to break transitivity.So the arguments are set in stone. The LSP doesn't hold in Java, and never has. --end of Alex quote It is true that if you use instanceof in the first two ways shown in this article, you either break symmetry or transitivity, and that if you use getClass you can fulfill the equals contract. This is basically what you're saying in the above quote. But what's been missing from the discussion is that there is a technique that lets you use instanceof while at the same time maintaining the equals contract, and that's what this article is trying to show. The benefit of instanceof/canEqual over just using getClass is that subclasses instances can equal superclass instances. The writer of the subclass decides whether this is possible based on whether or not he or she overrides the canEqual method.
|
Posts: 128 / Nickname: watson / Registered: September 7, 2005 3:37 AM
Re: How to Write an Equality Method in Java
June 1, 2009 0:33 PM
|
> The benefit of
> instanceof/canEqual over just using> getClass is that subclasses instances can> equal superclass instances. The writer of the subclass > decides whether this is possible based on whether or not > he or she overrides the canEqual method.Is it not the case, however, that if the subclass overrides equals() and doesn't override canEqual() to return {that instance of this}, we are back in non-compliance territory. The only benefit I see to this is that the subclass controls the behavior instead of the superclass. That's better but it doesn't address the fundamental issue of designing a part of a system using the equals() method as defined in my base class. If I depend on equals to determine if two points have the same x,y coordinates (e.g. use collections naively) canEquals() solves nothing. |
Posts: 409 / Nickname: bv / Registered: January 17, 2002 4:28 PM
Re: How to Write an Equality Method in Java
June 1, 2009 1:15 PM
|
> > The benefit of
> > instanceof/canEqual over just using> > getClass is that subclasses instances can> > equal superclass instances. The writer of the subclass > > decides whether this is possible based on whether or > not > > he or she overrides the canEqual method.> > Is it not the case, however, that if the subclass > overrides equals() and doesn't override canEqual() to > return {that instance of this}, we are back in > non-compliance territory. > Yes, if you make a Point subclass, say GoodPoint , that overrides equals , and inside that does an (other instanceof GoodPoint) , then it would need to override canEqual too. And GoodPoints would never be equal to Point s. If you didn't override canEqual in that case, it would indeed be breaking the equals contract again (the same instanceof breaking symmetry problem we started with).> The only benefit I see to this is that the subclass > controls the behavior instead of the superclass. That's > better but it doesn't address the fundamental issue of > designing a part of a system using the equals() method as > defined in my base class. If I depend on equals to > determine if two points have the same x,y coordinates > (e.g. use collections naively) canEquals() solves nothing. > No, I don't think the superclass can do it by itself, right? It can determine if the other object is an instance of its class. What it can't figure out is whether the other object is an instance of a subclass that overrode equals and added an (other instanceof <subclass type>) in there. That's what screws up the symmetry, and so the superclass has to always ask the other class if it can equal this one, which is what the canEqual method allows it to do.
|
Posts: 1 / Nickname: kwinter / Registered: June 1, 2009 9:31 AM
Re: How to Write an Equality Method in Java
June 1, 2009 2:46 PM
|
#3 should probably be entitled "Defining hashcode in terms of mutable fields," as the issue doesn't truly lie with the equals method, but in the fact that HashSet only calculates the hash once when the object is added. Using a List instead of HashSet would return true for all 3.
|
Posts: 6 / Nickname: gregor / Registered: August 3, 2005 4:18 AM
Re: How to Write an Equality Method in Java
June 3, 2009 0:13 PM
|
> What it can't figure out is whether the
> other object is an instance of a subclass that overrode > equals If could find out if the other class overrode equals - using reflection. This could very well be the task of a library method like pojomatic mentioned above. |
Posts: 6 / Nickname: gregor / Registered: August 3, 2005 4:18 AM
Re: How to Write an Equality Method in Java
June 3, 2009 0:37 PM
|
There's a version that doesn't require canEqual and is even more flexible:
Point.equals(Object o) { if (o instanceof Point) { if (!o.getClass.isAssignableFrom(getClass())) { //let the subclass decide if we are equal return o.equals(this); } Point p = (Point)o; return x == p.x && y == p.y; } return false; } now ColoredPoint could be defined in either way: ColoredPoint.equals(Object o) { if (o instanceof ColoredPoint) { if (!o.getClass.isAssignableFrom(getClass())) { //let the subclass decide if we are equal return o.equals(this); } ColoredPoint p = (ColoredPoint)o; return color == p.color && super.equals(o); } return false; } or ColoredPoint.equals(Object o) { if (o instanceof ColoredPoint) { if (!o.getClass.isAssignableFrom(getClass())) { //let the subclass decide if we are equal return o.equals(this); } ColoredPoint p = (ColoredPoint)o; return color == p.color && super.equals(o); } else if (o instanceof Point) { return color == Color.RED && super.equals(o); } return false; } |
Posts: 409 / Nickname: bv / Registered: January 17, 2002 4:28 PM
Re: How to Write an Equality Method in Java
June 3, 2009 2:23 PM
|
Hi Gregor,
> > What it can't figure out is whether the > > other object is an instance of a subclass that overrode > > equals > If could find out if the other class overrode equals - > using reflection. > This could very well be the task of a library method like > pojomatic mentioned above. > In practice I expect that would be too slow. Premature optimization, perhaps, but that's my guess. Also, I could tell through reflection if it is a subtype that overrode equals, but I wouldn't be able to tell whether the overridden equals is incompatible with symmetry. If the programer followed the recipe, then it would use instanceof and indeed be incompatible with symmetry, but that couldn't really be assumed. |
Posts: 1 / Nickname: bmrh / Registered: June 3, 2009 2:24 PM
Re: How to Write an Equality Method in Java
June 3, 2009 7:31 PM
|
What am I missing here?
To me this part: Set<Point> hashSet1 = new java.util.HashSet<Point>(); hashSet1.add(p); System.out.println(hashSet1.contains(cp)); // prints false Set<Point> hashSet2 = new java.util.HashSet<Point>(); hashSet2.add(cp); System.out.println(hashSet2.contains(p)); // prints true seems correct. In the first, we are asking "is there a red point at 1,2 in the collection?" and the answer is No - there is a point at 1,2 but we don't know if it's red or not (i.e. FALSE) In the second we are asking "is there a point at 1,2 in the collection?" and the answer is yes - there's a point there (and it happens to be red" (i.e. TRUE) So what's the problem with that implementation of hashCode? |
Posts: 40 / Nickname: vincent / Registered: November 13, 2002 7:25 AM
Re: How to Write an Equality Method in Java
June 4, 2009 0:19 AM
|
> What am I missing here?
> > In the first, we are asking "is there a red point at 1,2 > in the collection?" and the answer is No - there is a > point at 1,2 but we don't know if it's red or not (i.e. > FALSE) > > In the second we are asking "is there a point at 1,2 in > the collection?" and the answer is yes - there's a point > there (and it happens to be red" (i.e. TRUE) You're asking two different questions, above. The first question is "Do we have two similarly coloured points with the same co-ordinates?" and the second question is "Do we have two points with the same co-ordinates, regardless of colour?". With two different questions, it's OK to get two different answers. The equals(Object) method asks "Does this object and the other object have the same defining properties?" (i.e. it's similar to the first question, above). The javadoc for the equals(Object) method includes the following condition: It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true. When comparing a Point and a ColouredPoint the answer is always no because, whilst a ColouredPoint is a Point; a Point isn't a ColouredPoint. It doesn't matter which is the "this" object and which is the "other" object, one of them is missing a defining property that the other requires, so they can never be equal. |
Posts: 37 / Nickname: miata71 / Registered: March 29, 2006 6:09 AM
Re: How to Write an Equality Method in Java
June 4, 2009 10:20 AM
|
Vincent wrote: "When comparing a Point and a ColouredPoint the answer is always no"
In that case, don't use instanceof, use
And you are done. But some disagree with your "always". The original article said "The idea is that as soon as a class redefines equals (and hashCode), it should also explicitly state that objects of this class are never equal to objects of some superclass that implement a different equality method" To "solve" this issue, one trick I read somewhere (in earlier research of instanceof) is to declare your equals method final if you use instanceof. So, if the the original class designer decrees that subclasses are only "a wee bit different" and can be equal, use final with instanceof. In general, the test will only involve fields of the superclass. If the original designer is like Vincent and declares that "all subclasses are different", use getClass(). If the true answer is "it depends" or "the original designer was wrong", you'd need something like canEquals() or an Equalator. |
Posts: 40 / Nickname: vincent / Registered: November 13, 2002 7:25 AM
Re: How to Write an Equality Method in Java
June 5, 2009 3:03 AM
|
Mental note: "Always avoid sweeping statements."
You're quite right right to point out that is is possible to make different classes (where one is a subclass of the other) equal without breaking the contract of the equals(Object) method and Bill has done an extremely excellent job of describing how it can be done. Nevertheless, my conclusion from reading the article is that you should do everything possible to avoid needing such a solution on the grounds that it is unexpected that two object that are different things are the same. If only because the amount of code required to make it work, actually obscures what it is that you are trying to achieve. In addition, the mandatory requirement to implement a canEqual() method is, at best, fragile. I'm a great believer in the KISS principle. I know it was just a toy example, but in this case the obvious solution would be for the Point object to implement a boolean isCoincident(Point other) method that just checks the X and Y co-ordinates for equality. Subclasses would then be automatically covered. The original article was a real pleasure to read because of the way it revisted a core Java idiom and shed new light on it, in all the important areas: - What we do do (but shouldn't), - What we should do (but don't), and - What we could do but... |
Posts: 11 / Nickname: djimenez / Registered: December 22, 2004 0:48 AM
Re: How to Write an Equality Method in Java
June 5, 2009 7:08 AM
|
> The original article was a real pleasure to read because
> of the way it revisted a core Java idiom and shed new > light on it, in all the important areas: > - What we do do (but shouldn't), > - What we should do (but don't), and > - What we could do but... > Hear hear. |
Posts: 128 / Nickname: watson / Registered: September 7, 2005 3:37 AM
Re: How to Write an Equality Method in Java
June 5, 2009 9:43 AM
|
> I know it was just a toy example, but in this case the
> obvious solution would be for the Point object to > implement a boolean isCoincident(Point other) method that > just checks the X and Y co-ordinates for equality. > Subclasses would then be automatically covered. Absolutely and at the risk of beating a dead horse if we could use, say, a HashMap and tell it that we want isCoincident() to be the test for equality in one instance and the more strict equals in another, then we probably wouldn't be having this discussion. We'd use equals to be the canonical equivalence method. The canEquals() approach could still be a useful approach but it solves a pretty minor issue IMO. |
Posts: 20 / Nickname: raoulduke / Registered: April 14, 2006 11:48 AM
Re: How to Write an Equality Method in Java
June 9, 2009 4:40 PM
|
Posts: 1 / Nickname: ajainy / Registered: June 9, 2009 1:18 PM
Re: How to Write an Equality Method in Java
June 9, 2009 6:24 PM
|
Today only I was thinking, how to optimize writing hashCode and equals method.
In typical enterprise application, you lots of data beans which needs hashCode and equals. Now right implementation of hashCode will always give unique number for each new object. So why not we check for equality of hashCode() == hashCode() in equals method. So now, i have worried about only writing good hashCode method, instead of writing both. ------------- public boolean equals(Object obj) { if (obj == this) return true; if (null == obj) return false; return this.hashCode() == obj.hashCode(); } -------------- |
Posts: 128 / Nickname: watson / Registered: September 7, 2005 3:37 AM
Re: How to Write an Equality Method in Java
June 9, 2009 7:01 PM
|
> Now right implementation of hashCode will always give
> unique number for each new object. So why not we check for > equality of hashCode() == hashCode() in equals method. Strictly speaking, it's not possible to write a hashcode method that always produces a unique value. In a nutshell, the number of possible objects in a JVM instance could exceed the number of possible integer values. More practically speaking, it's not really feasible to do this, especially considering that hashcode should be fast. What methodology are you using to implement hashCode that you think will ensure uniqueness? |
Posts: 11 / Nickname: djimenez / Registered: December 22, 2004 0:48 AM
Re: How to Write an Equality Method in Java
June 2, 2009 7:03 AM
|
What exactly are we attempting to solve with this? Using the most implementation-y form of inheritance to very slightly reduce some minor code duplication? All so we can have Point3D extend Point2D or Circle extend Ellipse?
Is that very slight gain really worth this very real cost? (That said, it's a clever technique.) |
Posts: 409 / Nickname: bv / Registered: January 17, 2002 4:28 PM
Re: How to Write an Equality Method in Java
June 3, 2009 2:28 PM
|
Hi Daniel,
> What exactly are we attempting to solve with this? Using > the most implementation-y form of inheritance to very > slightly reduce some minor code duplication? All so we can > have Point3D extend Point2D or Circle extend Ellipse? > > Is that very slight gain really worth this very real > cost? > > (That said, it's a clever technique.) > The main point of the article is to try and spread the word more on the pitfalls of writing equals, and how to avoid them. Everything but the canEqual technique was already conventional wisdom, I think, but not necessarily widespread wisdom. So it doesn't hurt to remind folks. The point of canEqual is simply to show how to do something that I think was not conventional wisdom. The conventional wisdom, such as in Effective Java, is that this was not possible. I don't have a good use case off the top of my head, though. |
Posts: 409 / Nickname: bv / Registered: January 17, 2002 4:28 PM
Re: How to Write an Equality Method in Java
June 3, 2009 2:30 PM
|
Hi Daniel,
> What exactly are we attempting to solve with this? Using > the most implementation-y form of inheritance to very > slightly reduce some minor code duplication? All so we can > have Point3D extend Point2D or Circle extend Ellipse? > Somehow I didn't notice your comment about reducing code duplication. The problem wasn't code duplication, and I'm not sure which code you were thinking was being duplicated. The problem was that equals methods using instanceof without something like canEqual violate the superclass contract. This is apparently widespread in code. Regardless what LSP means, i do believe that in an OO program, subclasses should fulfill the superclass contract. |
Posts: 11 / Nickname: djimenez / Registered: December 22, 2004 0:48 AM
Re: How to Write an Equality Method in Java
June 4, 2009 7:12 AM
|
> > What exactly are we attempting to solve with this?
> Using > > the most implementation-y form of inheritance to very > > slightly reduce some minor code duplication? All so we > can > > have Point3D extend Point2D or Circle extend Ellipse? > > > The main point of the article is to try and spread the > word more on the pitfalls of writing equals, and how to > avoid them. Everything but the canEqual technique was > already conventional wisdom, I think, but not necessarily > widespread wisdom. So it doesn't hurt to remind folks. > > The point of canEqual is simply to show how to do > something that I think was not conventional wisdom. The > conventional wisdom, such as in Effective Java, is that > this was not possible. I don't have a good use case off > the top of my head, though. I didn't mean to criticize the presentation itself, and I concur that further discussing this issue is a good thing in this instance. However, I thought that the issue was that adding attributes to a concrete class is fundamentally at odds with the equivalence relation contracted by equals(). I'm surprised that there even is a workaround - I thought they were fundamentally incompatible! In that sense, I definitely agree that the technique is interesting, and I didn't know about it, so I appreciate hearing about it. > Somehow I didn't notice your comment about reducing code > duplication. The problem wasn't code duplication, and I'm > not sure which code you were thinking was being > duplicated. The problem was that equals methods using > instanceof without something like canEqual violate the > superclass contract. This is apparently widespread in > code. Regardless what LSP means, i do believe that in an > OO program, subclasses should fulfill the superclass > contract. It seems to me that the only reason to subclass Point2D into Point3D would be to reduce duplication (private final <numeric-type> x, y; getX(), getY(), constructor; equals and hashCode implementation, maybe part of the toString implementation). I've long doubted the maxim that OO-maps-the-real-world, and I don't think Point3D is-a Point2D anyway (a 2D point in 3D is a line!), so I wouldn't model it that way. Specifically, I was referring to this.x==that.x&&this.y==that.y : I don't think using a heavyweight technique like concrete implementation inheritance is called for simply to remove the duplication of those 15 tokens between Point2D and Point3D.
|
Posts: 128 / Nickname: watson / Registered: September 7, 2005 3:37 AM
Re: How to Write an Equality Method in Java
June 4, 2009 9:18 AM
|
> In that sense, I definitely agree that the technique is
> interesting, and I didn't know about it, so I appreciate > hearing about it. I agree but I think it's a bit like learning that it's possible to have sustained powered flight for more than 3 seconds because someone did it for 4 seconds. The real problem that developers face that leads to all this navel staring is not resolved by this technique. > It seems to me that the only reason to subclass Point2D > into Point3D would be to reduce duplication (private final > <numeric-type> x, y; getX(), getY(), constructor; equals > and hashCode implementation, maybe part of the toString > implementation). I've long doubted the maxim that > OO-maps-the-real-world, and I don't think Point3D is-a > Point2D anyway (a 2D point in 3D is a line!), so I > wouldn't model it that way. You have a point about 3D points not being 2D points but there are other examples that don't have this problem. The colored point is a better. Perhaps Person and Employee is a better example. Clearly an Employee is a Person. Is a Person object representing me equal to an Employee object representing me? It depends. If the Person object is a Customer, maybe not. The real issue is that most Collections and Maps in the JDK depend on the equals (and sometimes hashcode) methods. Therefore the only way to use these standard collections in these situations is make equals work for all the different ways you want to compare things and that's often impossible (or infeasible) without violating the contract of equals. The best solution is to make collections take a Equalator interface (similar to a Comparator). All of this nonsense goes away if you do that. Unfortunately the repeated cries for this feature from developers has been ignored by Sun for years. |
Posts: 11 / Nickname: djimenez / Registered: December 22, 2004 0:48 AM
Re: How to Write an Equality Method in Java
June 5, 2009 7:08 AM
|
> > It seems to me that the only reason to subclass Point2D
> > into Point3D would be to reduce duplication (private final > > <numeric-type> x, y; getX(), getY(), constructor; equals > > and hashCode implementation, maybe part of the toString > > implementation). I've long doubted the maxim that > > OO-maps-the-real-world, and I don't think Point3D is-a > > Point2D anyway (a 2D point in 3D is a line!), so I > > wouldn't model it that way. > > You have a point about 3D points not being 2D points but > there are other examples that don't have this problem. > The colored point is a better. Perhaps Person and > Employee is a better example. Clearly an Employee is a > Person. Is a Person object representing me equal to an > Employee object representing me? It depends. If the > Person object is a Customer, maybe not. > Employee/Person/Customer is a much better example, thank you. In a domain model I design, there would be Employee and Customer classes because they represent concrete entities. But although I know in real life that employees and customers are people, I wouldn't model a Person superclass/type pre-implementation. The only way a Person class would exist would be if I had duplication to remove between Employee and Customer, and it wouldn't be at design time, but at implementation time. > The real issue is that most Collections and Maps in the > JDK depend on the equals (and sometimes hashcode) methods. > Therefore the only way to use these standard collections > in these situations is make equals work for all the > different ways you want to compare things and that's often > impossible (or infeasible) without violating the contract > of equals. > I've solved this in the narrow way described above by Morgan Conrad, in a library for primary keys where the abstract class for numeric keys locked equals() to only match instances of the same subtype (eg, IntPK-equals-IntPK, not IntPK-equals-CharPK). But that's the narrowest way, not the general way of this article. I've just never come across a time when the nifty trick of using a canEquals() method would actually help me. Maybe I do too much business programming. Have you (has anyone?) had success modeling a Person superclass/type (or moral equivalent) before implementation? My attempts failed early and often, due to issues like this one. > The best solution is to make collections take a Equalator > interface (similar to a Comparator). All of this nonsense > goes away if you do that. Unfortunately the repeated > cries for this feature from developers has been ignored by > Sun for years. Yes indeed. At least a single benevolent dictator can be compromised by taking him to a bar :-), it's much harder with corporate "benevolent dictators." |
Posts: 6 / Nickname: gregor / Registered: August 3, 2005 4:18 AM
Re: How to Write an Equality Method in Java
June 10, 2009 0:04 AM
|
> The best solution is to make collections take a Equalator
> interface (similar to a Comparator). All of this nonsense > goes away if you do that. Unfortunately the repeated > cries for this feature from developers has been ignored by > Sun for years. Yes, very true. Apache collections allows you to do that and I've added a feature request for google collections (which uses generics). http://code.google.com/p/google-collections/issues/detail?id=188 http://commons.apache.org/collections/api-release/org/apache/commons/collections/map/AbstractHashedMap.html#isEqualKey(java.lang.Object,%20java.lang.Object) |
Posts: 1 / Nickname: thangalin / Registered: June 10, 2009 4:31 AM
Re: How to Write an Equality Method in Java
June 10, 2009 11:32 AM
|
In Pitfall #1, #2, and #3:
result = (this.getX() == that.getX() && this.getY() == that.getY()); I would prefer: result = that.xEquals( getX() ) && that.yEquals( getY() ); Or even: result = that.equals( getX(), getY() ); This: * avoids duplicating code (writing this.getX() == that.getX() will likely occur more than once); * allows changing of the underlying data type (as Java cannot override a method signature based on return type alone); * allows subclasses to inject behavioural changes, such as logging (ideally without breaking LSP); * promotes encapsulation: the object containing the data performs tasks upon it; and * is shorter. The equals method then reduces to: @Override public boolean equals(Object other) { return other instanceof Point ? ((Point)other).equals( getX(), getY() ) : false; } For Pitfall #3, you could also implement an Observer - Observable pattern so that the collection can rehash its objects if any of them are changed. Using a separate method, such as equalsContents, however, is simpler. For the section on the canEqual method, I would evoke the same encapsulation metaphor: result = that.canEqual( this ) && that.colorEquals( getColor() ) && super.equals( that ); Note that the color instance variable, even though it was declared final, is best not touched directly (at least until a new revision of Java is released that is like Smalltalk in how it handles instance variables). It seems to me that direct manipulation of class-scope instance variables and null data have needlessly introduced more bugs in the various Java-based systems I have worked on than all other problems combined! (I am, of course, exaggerating.) I still feel like there is some redundancy in the canEquals and equals methods. Specifically: @Override public boolean equals(Object other) { boolean result = false; if (other instanceof ColoredPoint) { And: @Override public boolean canEqual(Object other) { return (other instanceof ColoredPoint); I have not worked out the logic (reversing this and that might be incorrect), but perhaps the equals method can be reduced to: @Override public boolean equals(Object other) { boolean result = false; if( this.canEqual( other ) ) { ColoredPoint that = (ColoredPoint) other; result = that.colorEquals( getColor() ) && super.equals( that ); } return result; } Which would further reduce to: return this.canEqual( other ) ? ((ColoredPoint)other).colorEquals( getColor() ) && super.equals( that ) : false; An exceptional article; thank you for writing it! |
Posts: 1 / Nickname: tonyxzt / Registered: January 9, 2007 0:19 AM
Re: How to Write an Equality Method in Java
June 23, 2009 3:40 PM
|
Angelika Langer and Klaus Kreft already did a good work about: http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html
Tonyx |
Posts: 1 / Nickname: juanco / Registered: August 29, 2003 3:16 PM
Re: How to Write an Equality Method in Java
June 30, 2009 11:11 PM
|
One problem is using a HashSet for containing objects of different but related types.
The other problem is that a derived class must not change the contract of the base class. Thus, it should not redefine "equals". MHO is that ColoredPoint sucks as an example. In real, responsible programming, the new features should be added to Point until it becomes a fat class, or Design Patterns should be used to achieve the same through delegation, decoration, or proxies. A collection of Point must respond to the Point contract, and nothing else. Period (point). |
Posts: 2 / Nickname: darko / Registered: July 9, 2009 10:14 AM
Re: How to Write an Equality Method in Java
July 11, 2009 3:42 PM
|
Apparently, a careful design is needed to avoid issues with equals() usage. However, I think in some cases the problem is just a lack of the flexibility which could have been provided by some kind of an Equalator class - as mentioned already in some previous posts.
Let's revisit the example used by Joshua Bloch to illustrate one of his concerns regarding the equals() usage. The issue was the following method of the Point class (which could have been provided within some unrelated utility class as well):
Here unitCircle is a HashSet<Point> collection of the appropriate Point objects. Now if we have a ColorPoint class which is derived from Point but redefines its equals() method, it turns out onUnitCircle() can't be reused properly to process ColorPoint objects as well. There is no consistent implementation of ColorPoint.equals() which could accommodate such usage. Consequently, none of the implementations of equals() suggested earlier in this discussion thread can resolve this issue either. In order to support a safe reuse of methods like onUnitCircle(), Joshua Bloch recommends avoiding the inheritance and having a Point object as a member of the ColorPoint class. Then asPoint() method could be used to pass such Point object to methods which require Point-based equals() behavior. But I think even this approach is not quite good enough, and abandoning the inheritance relationship can significantly impact the ability to reuse the Point-based utilities. For example, in the context of usage there might be some Point method calls which are expected to be overridden by its derived classes, like:
Here p.show() is expected to behave as appropriate for each kind of Point object. But if we pass ColorPoint.asPoint() object, it won't represent properly that aspect of the ColorPoint behavior (the point might get displayed in some default color). Further on, it would be quite inconvenient to reuse the utilities like the following ones, since new Point collections would have to be created and passed as input arguments:
However, if it's feasible to modify the Point-related code, Point class could be redesigned as having a Position object exposed via get/setPosition() methods. Then reusable utility methods could be achieved by applying the Position-based equality as needed: using HashSet<Position> objects and passing Point.getPosition() to contains() method. Still, such Position class wouldn't be necessary if there was the ability to pass the appropriate Equalator instance to the HashSet<Point> constructor and thus support the required context-specific equality. |
Posts: 2 / Nickname: darko / Registered: July 9, 2009 10:14 AM
Re: How to Write an Equality Method in Java
July 21, 2009 4:56 PM
|
Aside from the issue I discussed in my previous post, I think that for "non-value-added" class hierarchies an implementation of equals() similar as suggested by Gregor Zeitlinger could be appropriate. However, that implementation would cause an infinite recursion when the objects belong to different branches of the same hierarchy. So here is a revised and slightly repackaged implementation:
Any further derived classes which don't add new values can simply rely on equals() implementation in the base Point class. Otherwise, a derived class could override equalsImpl() in a similar manner. However, in such cases issues like one raised by Joshua Bloch might occur - as I pointed out in my previous post. Also, I've noticed there's somewhat similar implementation of equals() provided by James Ahlborn in response to the following article by Angelika Langer and Klaus Kreft: http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals.html Note that in some cases that implementation might evaluate the same parameters twice. Further on, as mentioned earlier, there is a follow-up article: http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html In that article more complex implementation of equals() is proposed, which covers value-added classes as well. The equality is based on the comparison of common parts and the notion of default values for attributes which are not common. It's defined in such way that typical non-transitivity issues are avoided. For example, a Point object is equal to only one ColorPoint object - the one which has the same position and the "default" color. While such approach provides a formally correct implementation of equals() which might be useful in some cases, it still can't address context-specific needs - like in the example I discussed in my previous post. In such cases the only consistent solution is to expose the relevant object parts and use their own equals() method. Note that the article is pretty old, from 2002. The following year there was a little Artima article by the same authors: http://www.artima.com/weblogs/viewpost.jsp?thread=6543 where they say: "...Or take the inconsistency between comparison and quality: we can support several sorting orders via comparators, but we can only have one notion of equality via equals(). Why? ... |
Posts: 2 / Nickname: cirric / Registered: November 30, 2009 5:18 AM
Re: How to Write an Equality Method in Java
November 30, 2009 11:35 AM
|
Hi Bill
Thanks for a very interesting article that I got a link to a few weeks back and has been bothering me ever since. A problem that is hard to pin down but has a simple solution when you get there, maybe. Perhaps too simple, what you think? Start with some isA or similar comparisons, remove those that are not obligatory and you get left with almost nothing: public class Point { private final int x; private final int y; // ................ @Override public boolean equals(Object obj) { return (obj instanceof Point && ((Point)obj).fEquals(this)); } protected boolean fEquals(Point obj) { return (this.x == obj.x && this.y == obj.y); } @Override public int hashCode() { // ................ } public class ColoredPoint extends Point { private final Color color; // ................ @Override public boolean equals(Object obj) { return (obj instanceof ColoredPoint && ((ColoredPoint)obj).fEquals(this)); } protected boolean fEquals(ColoredPoint obj) { return (this.color.equals(obj.color) && super.fEquals(obj)); } @Override protected boolean fEquals(Point obj) { return false; } @Override public int hashCode() { // ................ } And so on down the inheritance chain to any depth you like. Surely someone else must have though of doing this before now and then rejected it for some reason? None of the how to override equals() postings I have seen suggest it but if you need an equals() that can be overridden or not as required with minimum additional coding and overhead it looks like an answer. Took a while to get to this embarrassingly naive solution. There is a blow by blow account including an equals() for Equality of the Third Kind at http://martinsjava.blogspot.com/2009/11/overriding-equalsobject-optimally.html |
Posts: 40 / Nickname: vincent / Registered: November 13, 2002 7:25 AM
Re: How to Write an Equality Method in Java
December 1, 2009 1:38 AM
|
Running the your code (with the appropriate constructors, etc., added in)
Point p1 = new Point(1, 1); Point p2 = new ColoredPoint(1, 1, Color.PINK); ColoredPoint cp1 = new ColoredPoint(1, 1, Color.PINK); System.out.println("p1.equals(p2) is " + (p1.equals(p2))); System.out.println("p2.equals(p1) is " + (p2.equals(p1))); System.out.println("p1.fEquals(p2) is " + (p1.fEquals(p2))); System.out.println("p2.fEquals(p1) is " + (p2.fEquals(p1)) + "\n"); System.out.println("p1.equals(cp1) is " + (p1.equals(cp1))); System.out.println("cp1.equals(p1) is " + (cp1.equals(p1))); System.out.println("p1.fEquals(cp1) is " + (p1.fEquals(cp1))); System.out.println("cp1.fEquals(p1) is " + (cp1.fEquals(p1)) + "\n"); System.out.println("p2.equals(cp1) is " + (p2.equals(cp1))); System.out.println("cp1.equals(p2) is " + (cp1.equals(p2))); System.out.println("p2.fEquals(cp1) is " + (p2.fEquals(cp1))); System.out.println("cp1.fEquals(p2) is " + (cp1.fEquals(p2)));yields: p1.equals(p2) is falseOne immediate point (sic) to note is the asymmetric fEquals test which can return true or false when testing the same objects, depending on which object you ask.
|
Posts: 2 / Nickname: cirric / Registered: November 30, 2009 5:18 AM
Re: How to Write an Equality Method in Java
December 1, 2009 7:44 AM
|
Hi Vincent
The reason you are getting asymmetrical and strange results is because you are calling the protected fEquals() methods from main() - the fact that this can be done is down to Java weak same-package encapsulation, not my fault. These methods should not be called from outside of the class but cannot be private because they need to be overridden in subclasses - protected is the best that can be done. In equals() it is only the combination of the instanceof check and calling the received object's version of fEquals() with the current object that makes the technique work. Please have a look at my blog link if you would like a detailed breakdown - you may like to skip the analysis of equals() requirements and go straight to 'Devising a solution'. Your result p2.fEquals(cp1) is false is interesting. I had not considered what would happen if these methods were called from outside of the class against a reference to a different class of object. Fortunately inside of equals() there is no doubt about type and everything works ok as shown by the equals() results in your output. |
Posts: 1 / Nickname: jecaplan / Registered: March 1, 2010 5:45 PM
Re: How to Write an Equality Method in Java
March 2, 2010 0:36 AM
|
A few comments:
1. Bloch's recommendations to include a reference equality short circuit seem to have been ignored here. The cleanest version would be @Override public boolean equals( Object other ) { Point that; return this == other || ( canEqual( other ) && ( that = ( Point )other ).canEqual( this ) && getX() == that.getX() && getY() == that.getY() ); } 2. The value add here is not so much that code duplication is avoided; it is that writing an equals() method that is more flexible than the one in Effective Java and still compliant is now 100% boilerplate and could conceivably be generated by an IDE. Follow the formula and you not only don't have to think about how to implement equals()/canEqual(), but you also get to extend concrete classes in non-state-related ways and preserve the contract. It is also more efficient than the blindlyEquals()/fEquals() solutions, although those formulations might have other benefits. 3. I have approached the boilerplate equality method using reflection, keeping a static cache mapping every class to the class which defines its equals (determined via reflection for cache misses). Then equals() always looks like @Override public boolean equals( Object other ) { C cOther; return this == other || ( eqPrereq( this, other ) && // for direct subclasses of Object // super.equals( other ) && // otherwise nullSafeEquals( this.getProp1(), ( cOther = ( ( C )other ) ).getProp1() ) && nullSafeEquals( this.getProp2(), cOther.getProp2() ) && ... ); } where eqPrereq is return oOther != null && getEqualsClass( oThis ).equals( getEqualsClass( oOther ) ); This way you don't need to implement anything that is not already in Object, and eqPrereq is implemented once (in a commons package). |
Posts: 1 / Nickname: nicerobot / Registered: March 21, 2010 9:00 PM
Re: How to Write an Equality Method in Java
March 22, 2010 3:08 AM
|
Am i missing something? Wasn't .equals solved:
Point: public boolean equals (final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (this.getClass() != obj.getClass()) { return false; } final Point other = (Point) obj; if (this.x != other.x) { return false; } if (this.y != other.y) { return false; } return true; } ColorPoint: public boolean equals (final Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (this.getClass() != obj.getClass()) { return false; } final ColoredPoint other = (ColoredPoint) obj; if (this.color == null) { if (other.color != null) { return false; } } else if (!this.color.equals(other.color)) { return false; } return true; } And for the cases were you need symmetry (which breaks the .equals contract due to the impossibility of implementing symmetry and transitivity through a hierarchy), use the following to delegate to super classes: ... if (this.getClass() != obj.getClass()) { if (obj.getClass().isAssignableFrom(this.getClass())) { return obj.equals(this); } if (!this.getClass().isAssignableFrom(obj.getClass())) { return false; } } ... Doesn't this accomplish the same as canEqual but without the need for an additional method? |
Posts: 1 / Nickname: davoud / Registered: February 15, 2012 6:47 PM
Re: How to Write an Equality Method in Java
February 16, 2012 0:50 AM
|
@Override public boolean equals(Object other) {
boolean result = false; -->> if (other instanceof ColoredPoint) { ColoredPoint that = (ColoredPoint) other; result = (that.canEqual(this) && this.color.equals(that.color) && super.equals(that)); } return result; } -->> @Override public boolean canEqual(Object other) { return (other instanceof ColoredPoint); } you checked twice ,why ? |
Posts: 1 / Nickname: kzvikzvi1 / Registered: May 8, 2012 6:13 AM
Re: How to Write an Equality Method in Java
May 8, 2012 11:25 AM
|
Nice post, i liked the canEqual method name :). Here are my thoughts on it.
Keeping the canEquals method in hands of a sub class makes it design by discipline, not by mandate. How about writing the canEquals method like this in the super class. I guess it may eliminate the need to subclasses overriding it. @Override public boolean canEqual(Object other) { return (other instanceof this.getClass()); } However, the above implementation can eliminate any possibilities of subclasses making mistakes. |
Posts: 1 / Nickname: sixcorners / Registered: April 20, 2013 11:17 AM
Re: How to Write an Equality Method in Java
April 20, 2013 4:25 PM
|
Sorry for posting to such an old thread..
From the article: @Override public boolean equals(Object other) { boolean result = false; if (other instanceof ColoredPoint) { ColoredPoint that = (ColoredPoint) other; result = (this.color.equals(that.color) && super.equals(that)); } else if (other instanceof Point) { Point that = (Point) other; result = that.equals(this); } return result; }Won't that recurse forever, or until there is a stack overflow error, if you use it with a different subclass of Point that does something similar? |
Posts: 1 / Nickname: winstonfg / Registered: June 4, 2013 11:43 PM
Re: How to Write an Equality Method in Java
June 5, 2013 6:25 AM
|
Thanks very much for the article, I read it a while ago and have recently come back to it. Very informative.
However, I _think_ I may have come up with a slightly neater version of the canEqual() solution. Assuming that: (a) You don't override equals() unless you intend to restrict comparisons to a sub-branch of the hierarchy. (b) When you do override equals(), you ALSO override canEqual() to check for the appropriate subclass. it seems to me that you could write your equals() methods as follows: For Point (or the top class in the hierarchy): public boolean canEqual(Object other) { return other instanceof Point; } @Override public boolean equals(Object other) { if (other == this) return true; if (!canEqual(other)) return false; // Assuming canEqual() is overridden to check for a // subclass of this one, the following assignment // must work: Point that = (Point) other; // Check that that.canEqual(this) AND invariants match. return that.canEqual(this) && x == that.x & y == that.y; } For ColoredPoint (or a subclass in the hierarchy): @Override public boolean canEqual(Object other) { return other instanceof ColoredPoint; } @Override public boolean equals(Object other) { if (other == this) return true; if (!super.equals(other)) return false; // We now know that 'other' must be a ColoredPoint // because the 2nd check in the top-level equals() // method will have called OUR canEqual() method. // So, the following assignment must work: ColoredPoint that = (ColoredPoint) other; // We now only need to check invariants because // super.equals(other) can't return true unless // other.canEqual(this). // So... return color.equals(that.color); I haven't benchmarked it, but I suspect that performance will be slightly worse than the style posted in the article, but it it just seems more logical to me from a hierarchical standpoint: 1. It eliminates reflexivity quickly. 2. It makes sure that superclass values (and type) are checked FIRST. 3. It makes use of the polymorphic nature of canEqual(). I also suspect that it would take less massaging to work properly for more sophisticated hierarchical comparisons (but it's just a feeling; don't ask me for proof :-) ). Comments (even "ít's crap") gratefully received - especially if someone's already sussed out the same thing. Winston |
Posts: 1 / Nickname: markyy86 / Registered: June 5, 2019 10:17 AM
Re: How to Write an Equality Method in Java
June 5, 2019 10:38 AM
|
How Joshua Bloch writes there is no general solution when you try to create subclass adding fields that changes the state of entity.
The solution with canEqual looks like a workaround. In OOP the problem could be solved by separating interface and implementation. Very strange that no one wrote this before, or did I miss something? interface Point { int getX(); int getY(); } interface ColoredPoint extends Point { Color getColor(); } // lombok annotations @Getter @AllArgsConstructor // compare by own fields x, y @EqualsAndHashCode(of = {"x", "y"}) class PointImpl implements Point { private final int x; privaet final int y; } @Getter @AllArgsConstructor // compare by own fields point, color @EqualsAndHashCode(of = {"point", "color"}) class ColoredPointImpl implements ColoredPoint { private final Point point; // just delegate getX, getY to point private final Color color; } in that way the equality and Liskov principle could be satisfied. |
Posts: 1 / Nickname: alexv83 / Registered: February 7, 2016 3:32 PM
Re: How to Write an Equality Method in Java
February 7, 2016 9:41 PM
|
about point number 3.
I think it is a very bad idea, not overriding the equals() Method. I think the best thing to do is to understand ones you have changed the object, you have! to remove! the old one from the hash, and enter the new one. yes it might be little less efficient, but it will save you from troubles, and it is logically correct. that is my opinion. |
Posts: 1 / Nickname: iculantio / Registered: July 23, 2019 9:24 AM
Re: How to Write an Equality Method in Java
August 6, 2019 11:01 AM
|
> The benefit of
> instanceof/canEqual over just using > getClass is that subclasses instances can > equal superclass instances. The writer of the subclass > decides whether this is possible based on whether or not > he or she overrides the canEqual method. Is it not the case, however, that if the subclass overrides equals() and doesn't override canEqual() to return {that instance of this}, we are back in non-compliance territory. The only benefit I see to this is that the subclass controls the behavior instead of the superclass. That's better but it doesn't address the fundamental issue of designing a part of a system using the equals() method as defined in my base class. If I depend on equals to determine if two points have the same x,y coordinates (e.g. use collections naively) canEquals() solves nothing. |