• Post Reply Bookmark Topic Watch Topic
  • New Topic

equals() in subclasses  RSS feed

 
Graeme Byers
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
�Having two objects in different class types be considered equal is not a good idea, but that is a design issue we will not consider here�S&B p527.

Well if Manager extends base class Emp , both constructed from the same one field (String name ) with appropriate equals() and compareTo() is :

Is it good programming practice for a class's equals() to invoke a its own compareTo() ?

I accept that there may not be a 'hard and fast' answer.
Anything interesting to read ?
Thank you.
[ October 01, 2008: Message edited by: Graeme Byers ]
 
Campbell Ritchie
Marshal
Posts: 56578
172
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The book to read is Joshua Bloch's Effective Java, which I reviewed for the JavaRanch books page.
No, it is not a good idea to invoke compareTo in equals.

The problem with equals and inheritance is that you can end up with
System.out.println (tom.equals(thomas));
and
System.out.println (thomas.equals(tom));
printing out different results. If your classes both have exactly the same fields then you might get away with using instanceof in the superclass equals and seeing whether all the fields are equal with their respective equals method.
Then you don't override equals again.

But Bloch is full of warnings about when equals() doesn't work properly. Look in the Object class and see how it is supposed to work.
 
Henry Wong
author
Sheriff
Posts: 23295
125
C++ Chrome Eclipse IDE Firefox Browser Java jQuery Linux VI Editor Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I agree with Campbell, the contract for equals() require that it is both Symetric and Transitive. This means that you can't have dependencies on class type, as it will mean that the superclass needs to know the existance of the subclasses.

On the other hand, the list collections do allow this. For example, an ArrayList can be compared (using equals) with a LinkList, and it will work. In that case, equality is based on the items in the list -- which is obtained via the List interface.

Henry
 
Mike Simmons
Ranch Hand
Posts: 3090
14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
[Campbell]: No, it is not a good idea to invoke compareTo in equals.

Hm, why do you say that? There are some issues to be aware of, such as the fact that compareTo() is allowed to throw ClassCastException or NullPointerException, while equals() should never throw either. But for a class MyClass that implements Comparable, this should be a perfectly legal implementation of equals():

This may not be the most efficient possible implementation, but it works.

It's also possible to use a variant with instanceof, assuming Comparable is also implemented in a compatible manner, but it's probably easier to leave that to Effective Java if Graeme wants to learn more.
 
Campbell Ritchie
Marshal
Posts: 56578
172
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Because compareTo might return 0 when the two Objects are not equal to each other (see BigDecimal), because it's not a good idea to use a method designed to do "a" in a method doing "b" unless you can describe "b" in terms of "a+c."

Of course in a final or immutable class all sorts of things you aren't supposed to do eg using instanceof and invoking compareTo in equals will still work nicely! Best to look in Effective Java and see what all the complications are; Bloch doesn't actually dictate a "right" way to write an equals method, more lots of "wrong" ways.
 
Ove Lindström
Ranch Hand
Posts: 326
Android Firefox Browser Mac OS X
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I agree with Campbell. compareTo is used to sort things according to their "natural" order. equals is used to check if two objects looks the same.

It is possible (and even normal) to have a compareTo that only looks at the fields that are significant to do the ordering.
 
Mike Simmons
Ranch Hand
Posts: 3090
14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
[]Campbell]: Because compareTo might return 0 when the two Objects are not equal to each other

Well, in the implementation I gave, they are equal to each other in that case. By definition of the equals() method. Because I assumed that the compareTo() method was already implemented, and then implemented equals() in a compatible manner. If you do it in the opposite order, which is more common, then it's not quite as easy to make the two methods compatible. That's why I later said "assuming Comparable is also implemented in a compatible manner" when referring to such a situation.

[Campbell]: (see BigDecimal)

Yes, Josh knowingly wrote BigDecimal with a compareTo() that is incompatible with equals(), and documented it as such. Maybe he felt that it was really useful to distinguish between 2.0 and 2.00. Or maybe he just didn't have the clout, back then, to force a redefinition of the Number class and its subclasses, to make both compareTo() and equals() both consistent and useful between different Number implementations (like he did for Collection, List, Set and Map a few years later). Or maybe he hadn't thought through all the implications, or discovered better solutions. I dunno. Some corners were cut back in the early days of JDK 1.0 and before, and many things have been learned since then.

Any way, it's true that some classes (like BigDecimal) have compareTo() methods that are incompatible with their equals() methods. That doesn't mean that such compatibility is impossible, however - at least in systems where you have control over all relevant class definitions. In many cases, it's not even difficult. But it depends on what contracts you've inherited that are beyond your control.

[Campbell]: because it's not a good idea to use a method designed to do "a" in a method doing "b" unless you can describe "b" in terms of "a+c."

You mean, the way I described "b", the equals() method, in terms of "a", the compareTo() method, in my last post? Where "c" would be the null check, class check, and == 0 check?

I'm actually not sure where this last rule of yours came from, but anyway, I don't see how it would apply here, except as something that's already been taken care of.

It sounds like your objections reduce to "it's not a good idea to do this if you don't know what you're doing." Which is somewhat different from your earlier statement that it's just a bad idea to do this, period.

[Ove]: I agree with Campbell. compareTo is used to sort things according to their "natural" order. equals is used to check if two objects looks the same.

Sure. But that doesn't mean the two are necessarily incompatible, either. And if someone wants to define equals() in terms of compareTo(), such that they are compatible, what's wrong with that?

[Ove]: It is possible (and even normal) to have a compareTo that only looks at the fields that are significant to do the ordering.

Possible, absolutely. Normal - well that's arguable; it strikes me as being a bit lazy in many cases. It's also possible to define equals() in such a way as to ignore some fields as unimportant, just as you suggest for compareTo(). You can make both methods equally lazy if you want. And remember, the API for compareTo() does "strongly recommend" that we implement compareTo() and equals(0 to be consistent with each other. Sure, there are exceptions in the standard APIs, including Josh's own BigDecimal class. But such exceptions don't need to be the norm.
 
Campbell Ritchie
Marshal
Posts: 56578
172
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I think we are just going to go round in circles.

I personally disagree with Bloch about BigDecimal; I think there is in fact a difference between 2, 2.0 and 2.00.
Remember that most classes don't in fact implement Comparable<T> at all since they don't have a "natural order," so the temptation to use compareTo() will only arise rarely.
I think you are right that a compareTo<Number> method would have been useful. But, like missing out generics in the earlier stages, that is just one of those mistakes we shall have to live with . . .
 
Rob Spoor
Sheriff
Posts: 21135
87
Chrome Eclipse IDE Java Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Mike Simmons:
Maybe he felt that it was really useful to distinguish between 2.0 and 2.00.

From a scientific point of view, 2.0 is different from 2.00, and the difference is in significance (number of decimals).

2.0 could be any number between 1.95 (inclusive) and 2.05 (exclusive), whereas 2.00 could be any number between 1.995 (inclusive) and 2.005 (exclusive). Science rules define the siginificance of an arithmetic outcome based on the significance of its operands. For instance, 2.0 * 3.0 will be 6.00, not 6

Now, sorting 2.0 and 2.00 is not as easy as the equality check. 2.00 can be both before (e.g. 2.04 and 2.000) and after (e.g. 2.00 and 2.003) 2.0
Since this cannot be determined, 2.0 and 2.00 are considered to have the same order.


Wow, all of this I still remembered from high school 12 years ago.
 
Ove Lindström
Ranch Hand
Posts: 326
Android Firefox Browser Mac OS X
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Mike Simmons:

[Ove]: It is possible (and even normal) to have a compareTo that only looks at the fields that are significant to do the ordering.

Possible, absolutely. Normal - well that's arguable; it strikes me as being a bit lazy in many cases. It's also possible to define equals() in such a way as to ignore some fields as unimportant, just as you suggest for compareTo(). You can make both methods equally lazy if you want. And remember, the API for compareTo() does "strongly recommend" that we implement compareTo() and equals(0 to be consistent with each other. Sure, there are exceptions in the standard APIs, including Josh's own BigDecimal class. But such exceptions don't need to be the norm.


Yes, I can agree with that. If I find that I have to manipulate the compareTo in such a way that it does not match with equals all the time, I mark that class as "Smelly laundry basket". Meaning, you are trying to put to much into one class and need to split it in smaller chunks so that it is easier to sort.

Acctually, I find myself using the compareTo-method less and less and instead using Comparators since most of our projects need to sort objects in many different ways.
 
Campbell Ritchie
Marshal
Posts: 56578
172
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
And I can agree with Ove Lindstr�m.
 
Mike Simmons
Ranch Hand
Posts: 3090
14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
[Rob]: 2.0 could be any number between 1.95 (inclusive) and 2.05 (exclusive), whereas 2.00 could be any number between 1.995 (inclusive) and 2.005 (exclusive). Science rules define the siginificance of an arithmetic outcome based on the significance of its operands. For instance, 2.0 * 3.0 will be 6.00, not 6

If you're going to go down this path, let's note that BigDecimal isn't really following the rules for significant digits used in hard sciences and engineering anyway, since 2.0 * 3.0 should be 6.0, not 6.00. But that's not what BigDecimal does - it's off in its own world here.

[Rob]: Now, sorting 2.0 and 2.00 is not as easy as the equality check. 2.00 can be both before (e.g. 2.04 and 2.000) and after (e.g. 2.00 and 2.003) 2.0
Since this cannot be determined, 2.0 and 2.00 are considered to have the same order.


But by that logic, it also can't be determined whether 2.0 is before or after 2.01 - since the first could range from 1.95 to 2.05, and the latter is between 2.005 and 2.015.

(Actually I'd prefer not to overstate the exactness of those limits, since in reality they're usually somewhat fuzzier than that - but the basic point remains.)

One could also argue that these methods should simply throw some sort of exception whenever it can't be determined which is truly before another, or whether two quantities are really "equal" (depending on what definition you want to use). Much like == should be used with extreme caution when dealing with foating-point arithmetic.

In practice though, I don't think that would be a good idea most of the time.

[Ove]: Acctually, I find myself using the compareTo-method less and less and instead using Comparators since most of our projects need to sort objects in many different ways.

Agreed. It's too bad Java didn't provide similar support to plug in different implementations of equals() (with matching hashCode()) as well, since as we've seen in this thread, there can be more than one sensible way to define what equality means for a given class.
 
Graeme Byers
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
As well as Joshua Bloch "Effective Java" , try :
//www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals.html

Thank you all for your replies.
 
Campbell Ritchie
Marshal
Posts: 56578
172
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Useful link. Thank you.
 
It is sorta covered in the JavaRanch Style Guide.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!