The Artima Developer Community
Sponsored Link

Weblogs Forum
Some Objects are More Equal Than Others

28 replies on 2 pages. Most recent reply: Jan 25, 2007 12:03 PM by antonio lucca

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 28 replies on 2 pages [ 1 2 | » ]
Cay Horstmann

Posts: 13
Nickname: cay
Registered: Apr, 2003

Some Objects are More Equal Than Others (View in Weblogs)
Posted: May 8, 2003 11:23 PM
Reply to this message Reply
Summary
Many Java programmers believe that they should include an instanceof test in equals methods. Many authors egg them on. This article explains why this is very wrong and muddleheaded in almost all cases, and urges programmers to test for equality of classes instead.
Advertisement
The Java Language Specification states that the equals method of any class should be reflexive, symmetric (well, almost...null.equals(x) is different from x.equals(null)), and transitive.

That's easy enough to achieve:

public Employee
{
public boolean equals(Object other)
{
if (getClass() != other.getClass()) return false;
// cast other to Employee and compare fields
. . .
}
}

What bugs me is how many programmers do something different and dangerous, and how many authors egg them on. If you look at the implementations of equals in the source of the standard library (about 150), you will find that the majority use an instanceof test instead.

public Employee
{
public boolean equals(Object other)
{
if (!(other instanceof Employee)) return false;
// cast other to Employee and compare fields
. . .
}
}

This is a crummy idea. In many (most?) cases, a subclass will want to override equals by comparing the superclass fields and the subclass fields, like this:

public Manager extends Employee
{
public boolean equals(Object other)
{
. . .
if (!super.equals(other)) return false;
// cast other to Manager and compare fields
return bonus == ((Manager)other).bonus;
}
}

But now equals has become seriously asymmetric:

  • anEmployee.equals(aManager) compares true if the employee and manager have the same superclass data
  • aManager.equals(anEmployee) throws an exception
That problem can be avoided easily. Does it ever make sense for an Employee to equal a Manager? No. Just forget about instanceof and test for equality of classes instead.

Why don't more authors mention this?

For example, Josh Bloch has a chapter on equals in his Effective Java book, and he never once mentions the getClass() != other.getClass() test.

Instead, he pontificates about a rather pointless example with Point and ColoredPoint classes where equals does not take the point's colors into consideration.

Why is there a stubborn refusal in the Java literature to recognize the obvious?

  • You should almost always test for getClass() != other.getClass() in an equals method
  • There is a small number of cases where instanceof makes sense, namely if
    • a superclass has fixed the semantics for equals
    • the state of various subclasses differ in inessential ways from the state of the superclass
    • and the subclasses can add value by redefining equals to be more efficient
    This is the case in the java.util.Collection hierarchy.
If a class is final it doesn't matter. But there are lots of non-final classes with bad definitions of equals, for no better reason than that the folks who should know better set a bad example.

That's my pet peeve of the day.


Guizhong Chen

Posts: 1
Nickname: agui
Registered: May, 2003

Re: Some Objects are More Equal Than Others Posted: May 9, 2003 1:01 PM
Reply to this message Reply
The statement:
if (getClass() != other.getClass()) return false;
seems really dangerous. It is mixing the concept of identity with the concept of type. Why 2 objects of different types could not be equal?

Vincent O'Sullivan

Posts: 724
Nickname: vincent
Registered: Nov, 2002

Re: Some Objects are More Equal Than Others Posted: May 11, 2003 10:25 AM
Reply to this message Reply
>
> Why 2 objects of different types could not be equal?

Two objects of different types cannot - safely - state that they are equal unless each of the classes is aware of how the other class determines equality. This implies that each class has knowledge of how the internals of the other class works. This has two bad implications. The first is that both classes are 'closely coupled' (i.e. changing the implementation of one class affects the way in which the other class is also implemented). The second is that the workings of one or other class is not properly encapsulated and may be being used by the other class in a way not intended by the developer of the class.

If two different objects have the same way of determining equality, that also implies that both those objects contain duplicate code or a common 'virtual object' within them. This duplicate code may need to be extracted into a third object, an instance of which would then be contained in the original objects.

Vince.

Johan H

Posts: 1
Nickname: daemon23
Registered: May, 2003

Re: Some Objects are More Equal Than Others Posted: May 11, 2003 6:18 PM
Reply to this message Reply
Attempting to add an aspect to the equality comparison by using subclassing violates the Liskov substitution principle.

Actually Joshua Bloch has commented this issue in
an interview by Bill Venners.

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

Best regards
Johan H

Jonny De Hertogh

Posts: 1
Nickname: jdhertog
Registered: May, 2003

Re: Some Objects are More Equal Than Others Posted: May 12, 2003 4:24 AM
Reply to this message Reply
2 improvements on the topic of Cay:
- you will get a NullPointerException on other.getClass() if other is null
- if 'this' and 'other' is a reference to the same object-instance, you can immediately return true

Thus...
public Employee{
   public boolean equals(Object other)
   {
      if (this == other) return true;
      if (other == null) return false;
      if (getClass() != other.getClass()) return false;
      // cast other to Employee and compare fields
      . . .   }
}

Dave Astels

Posts: 32
Nickname: dastels
Registered: Mar, 2003

Re: Some Objects are More Equal Than Others Posted: May 13, 2003 10:21 AM
Reply to this message Reply
> public Employee{
>   public boolean equals(Object other) {
>     if (this == other) return true;
>     if (other == null) return false;
>     if (getClass() != other.getClass()) return false;
>     // cast other to Employee and compare fields
>   }
> }


This is a beautiful example of the use of guard clauses to simplify the code and generally make it clearer. Many programmers would have written something like:

public boolean equals(Object other)
{
  if (this == other) {
    return true;
  } else if (other == null) {
    return false;
  } else if (getClass() != other.getClass()) {
    return false;
  } else {
    // cast other to Employee and compare fields
  }
}


or even worse (in the misguided persuit of A Single Return):

public boolean equals(Object other)
{
  boolean result = false;
 
  if (this == other) {
    result = true;
  } else if (other == null) {
    result = false;
  } else if (getClass() != other.getClass()) {
    result = false;
  } else {
    // cast other to Employee and compare fields
  }
  return result;
}


Dave

Dave Astels

Posts: 32
Nickname: dastels
Registered: Mar, 2003

Re: Some Objects are More Equal Than Others Posted: May 13, 2003 10:28 AM
Reply to this message Reply
If you happen to be using JUnit to write tests for you code, your can use EqualsTester that is part of Mike Bowler's Gargoyle JUnit Extensions. It tests for an equals method as descibed in Cay's post. It also tests for a correct implementation of hashCode().

http://gsbase.sourceforge.net

Dave

Angelika Langer

Posts: 2
Nickname: langer
Registered: May, 2003

Re: Some Objects are More Equal Than Others Posted: May 13, 2003 2:13 PM
Reply to this message Reply
> Why is there a stubborn refusal in the Java literature
> to recognize the obvious?

Not all Java literature advocates the instanceof test. For instance, "Practical Java" by Peter Haggar gets it right and there have been some articles discussing the issue (see some reference below).

http://www.macchiato.com/columns/Durable5.html
http://www.cuj.com/java/articles/a19.htm?topic=jav
http://www.cuj.com/java/articles/a22/a22.htm?topic=jav

This one is in German for those who enjoy some foreign language reading at times:
http://www.langer.camelot.de/Articles/JavaSpektrum/02.Equals-Part2/02.Equals2.html

Dave Astels

Posts: 32
Nickname: dastels
Registered: Mar, 2003

Re: Some Objects are More Equal Than Others Posted: May 13, 2003 2:16 PM
Reply to this message Reply
> Not all Java literature advocates the instanceof test.
> For instance, "Practical Java" by Peter Haggar gets it
> right

EqualsTester that I mentioned previously is based on equality as described in "Practical Java".

Dave

Ken Arnold

Posts: 27
Nickname: arnold
Registered: Apr, 2003

Re: Some Objects are More Equal Than Others Posted: May 14, 2003 10:07 AM
Reply to this message Reply
So tell me: Suppose an inheritance chain where Z extends Y extends X. The logic of X.equals() uses getClass(). In what way, exactly, is Y.equals() supposed to test if its X part is equal to the other object's X part?

It could reproduce the comparison subpart of X.equals, using accessible members of X, but that might lead to a different meaning of equality. This is a problem if, over time, the notion of X's equality changes (there are bugs, after all, even in designs, and these are some of them). Or worse, some parts of X's state may be purposefully not accessible.

If X's writer is thinking about this problem, X might have a protected X.contentEquals() method that does the comparison part of X.equals(), making it easy for Y.equals() to inherit that logic. But this will necessarily be a non-standard mechanism that each inventor of a class must (a) think about, (b) invent and name as makes sense to them, and (c) make available to subclass writers in a way that they will notice, as it is non-standard. I don't hear much cheering for this.

And then consider this with Z, who must replicate X and Y comparisons, and/or use X and Y comparison methods, as the writers of X and Y decided to make them.

Oy vey.

I personally think that a.equals(b) == b.equals(a) is one of those nice theories that really can't hold up as things are constituted. A real solution would be to have an "equivalent" operator distinct from an "identical" operator (which we have in ==). So one could say "a <-> b" or whatever, and it would invoke a.equals(b) after the null and class equivalence tests which must be universally applied. Then we could get somewhere. What we are given right now makes keeping the equivalence relationship a lot of work, and therefore distinctly unreliable.

In the universe we actually inhabit, I think that a dogmatic adherence to getClass vs. instanceof is overweening.

Patrick Niemeyer

Posts: 1
Nickname: patniemeye
Registered: May, 2003

Re: Some Objects are More Equal Than Others Posted: May 14, 2003 12:01 PM
Reply to this message Reply
Why not let Manager decide whether it can be equal to an Employee like so:

Manager extends employee {
boolean equals( Object other ) {
if ( !super.equals( other ) ) return false;
// Now I know my employee bits match...
...
}
}

Why does implementing equals acrosss classes have to break encapsulation?

... not advocating this, just seems like its a problem in the domain, not necessarily a hard fast rule.


Thanks,
Pat Niemeyer

Vincent O'Sullivan

Posts: 724
Nickname: vincent
Registered: Nov, 2002

Re: Some Objects are More Equal Than Others Posted: May 14, 2003 5:26 PM
Reply to this message Reply
> Why does implementing equals acrosss classes have to break
> encapsulation?
>
> Pat Niemeyer

Encapsulation is the wrong word for what I meant. I'm sure someone here can supply the correct word for what I'm getting at.

Given instance a of class A and instance b of class B. To have A and B implement equals() such that a.equals(b) and b.equals(a) always agree, implies that they have corresponding code in their respective implementations. If class A is modified so that circumstances when a.equals(b) are changed, then the code in class B must be similarly modified (the interfaces of both classes being unaffected). Thus the implementations of A and B are dependent upon each having knowledge of how the other works. The equals criteria is not 'encapsulated' in a single place.

Frank Mitchell

Posts: 37
Nickname: fmitchell
Registered: Jan, 2003

Re: Some Objects are More Equal Than Others Posted: May 14, 2003 10:39 PM
Reply to this message Reply
Just for reference, there's at least one paper on the problem of "binary methods" like equals():

http://citeseer.nj.nec.com/bruce95binary.html

The upshot seems to be that there's no good solution, short of special cases or multiple dispatch (which have their own problems).

Vincent O'Sullivan

Posts: 724
Nickname: vincent
Registered: Nov, 2002

Re: Some Objects are More Equal Than Others Posted: May 15, 2003 2:00 AM
Reply to this message Reply
> Why not let Manager decide whether it can be equal to an
> Employee...

(I guess I should have included this in my previous reply.)

If a Manager can decide if and when it can be equal to an Employee, then - to preserve symmetry - an Employee must also be able to decide when it can be equal to a Manager based on the same criteria. Now, Manager extends Employee so it has knowledge of the Employee class, however, the Employee class has no knowledge of the Manager class, so the Employee cannot guarantee that symmetry is maintained if the Manager class is modified.

Further, it could be that two Employees are equal if they have the same staff number. The same condition could apply to Managers. It would still be dangerous to allow a Manager to equal an Employee, since it implies the two classes are interchangeable. You would therefore come unstuck if you decided a given Employee was equal to a Manager and then treated the Employee as a Manager (perhaps by casting it or calling say e.makeDecision() when only Managers have a makeDecision() method.

The solution in such cases (one solution anyway) is to extract the commonality into a different objects and compare that. e.g. Manager still extends Employee but Employee contains a Person, leading to:
if ( m.getPerson().equals(e.getPerson()) )...
This would establish that both the Manager m and the Employee e are the same Person whilst avoiding the semantic confusion of implying that m and e are the same.

Vince.

Radoslav Nikolov

Posts: 1
Nickname: rado
Registered: May, 2003

Re: Some Objects are More Equal Than Others Posted: May 15, 2003 4:40 AM
Reply to this message Reply
Objects of different *classes can* be equal. These are "true" for example:
new HashSet().equals(new TreeSet()))
new LinkedList().equals(new ArrayList()))
new HashMap().equals(new TreeMap())

Although they are different classes each pair consists of objects of the same *type* (Set, List, Map). There is a difference (IMO) between types and classes. Each object is of exact one class, but might be of many types. That is the polymorphism - many forms.

Flat View: This topic has 28 replies on 2 pages [ 1  2 | » ]
Topic: Some Objects are More Equal Than Others Previous Topic   Next Topic Topic: Come join me at PyCon 2007!


Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us