.Equals()

23 views
Skip to first unread message

Robert Fischer

unread,
Oct 26, 2007, 6:02:11 PM10/26/07
to java...@googlegroups.com
Hey there. Jumping on because 1) the list was recommended to me, and 2)
your conversation on #equals seemed good. I've got a few posts on
#equals on my blog, which I linked to off of this post:
http://enfranchisedmind.com/blog/archive/2007/10/26/417

I'm yet to encounter a general inheritance-safe solution for equals
beyond externalizing the equals/compareTo implementation. Check out the
TwoDeePoint and ThreeDeePoint examples in my blog: if you've got a good
solution for that, you'll be my hero.

The best suggestion I have is that if the classes of two objects aren't
equal, then they shouldn't be equal. That's the safest way to implement
a built-in version of #equals/#compraeTo.

~~ Robert.

Martin OConnor

unread,
Oct 27, 2007, 10:12:07 AM10/27/07
to The Java Posse
Your class hierarchy is flawed. ThreeDeePoint does not naturally
extend TwoDeePoint, because a Three Dimiensional point is not a kind
of Two Dimensional point. They represent two very distinct concepts.
Essentially, when you extend TwoDeePoint with ThreeDeePoint, you are
saying that ThreeDeePoint is a special kind of TwoDeePoint. I would
submit, therefore that the problems you are observing are less due to
the impelmentation of Object.equals(), and more as a result of your
chosen class hierarchy design.

On Oct 26, 11:02 pm, Robert Fischer <Robert.Fisc...@SmokejumperIT.com>
wrote:

Christian Catchpole

unread,
Oct 27, 2007, 4:38:03 PM10/27/07
to The Java Posse
A 3D point is a 2D point which has the added property of a Z
coordinate. The point still lives on a 2D plane but now the plane has
a position as well. Now, but this logic, you could add a temporal
value to track the points position in time (not just space). But you
would ask youself, is this right way to do it. For time, perhaps not,
for 2 to 3D... grey area.. But I see Martin's argument. :)

Did Einstein think a 3D point was a special kind of 2D point? Or did
he think they were all 1D points effected by relativity. :)

Christian Catchpole

unread,
Oct 27, 2007, 4:58:49 PM10/27/07
to The Java Posse
BUT the real decider is, does referencing the object by it's base type
still give you a useable relationship. If you use the base of a
InputStream or a Map, thats fine because the added extras of the
extending class should not effect you here. In the case of a 3D
point, all points are revelant and just looking at the 2D coordintes
flattens them in space. So even if technically you can argue a
relationship (like I did in the last post), it's probably best to look
at how the classes are used. But the 2 vs 3d point thing is
interesting. You might have a distanceBetween(Point) method which has
a different implementation between 2 and 3D.

Martin OConnor

unread,
Oct 28, 2007, 7:20:27 AM10/28/07
to The Java Posse
What I was trying to explain, (although poorly), or more precisely
hinting at, is The Liskov Substitution Principle. This is an Object
Oriented design principle that states that any reference to a base
class should be replaceable with a reference to a derived class
without affecting functionality. What my previous post had attempted
to say is that since the TwoDeePoint and ThreeDeePoint classes do not
adhere to The Liskov Substitution Principle, the design, in this
particular case is flawed.

Christian Catchpole

unread,
Oct 28, 2007, 7:37:35 AM10/28/07
to The Java Posse
Yeah, you are probably right. I was sort of working through it myself
and came to the same conclusion. I have a habit of posting, thinking
some more about it and posting again... :)

Reinier Zwitserloot

unread,
Oct 28, 2007, 8:00:12 AM10/28/07
to The Java Posse
Exactly. Letting 3DPoint extend 2DPoint is a mistake. a 3DPoint might
be able to have '2Dpointy' nature, which in java may be expressed with
an interface, but, personally, that sounds like a mistake to me as
well. Translating a 3D point to a 2D point is not a valid operation
without a context, it's that simple. So, what IS the context? It
depends on camera, the plane you're translating to, and/or a host of
other concepts. I imagine there's perhaps a Plane class someplace with
the following method signature:

public 2DPoint translate(3DPoint p);

That would make perfect sense. As a general rule of thumb, unless a
class can -ALWAYS- be viewed, -without- context, as a superclass, you
should not extend. Extend is a last resort that needs to be used only
if the relation is virtually perfect. Liskov Substitution Principle is
a fine way of applying this idea (substitution at all times is more or
less the same thing as a perfect fit without context).

Having a 3DPoint extend or even implement 2DPoint would imply that it
can translate either without context (so probably against the z=0 x/y
axis plane, but now you're stuck with that, and that does seem pretty
poor for future flexibility; Plane.XYaxis.map(3dPointThingie) is
almost as short and way cooler, or by referring to a static context
(get the relevant plane from a static variable someplace), which is an
ugly and unmaintainable hack.

NB: I'm aware 2DPoint is not a valid identifier, but it's easier to
read for the sake of discussion.

Reinier Zwitserloot

unread,
Oct 28, 2007, 8:16:19 AM10/28/07
to The Java Posse
Here's an interesting tactic for heterogenous equals, by the way:

Let's say you have a class which is explicitly designed for
subclasses*. You'd even like the entire hierarchy to be equals
capable. In general the subclasses are much more aware about whether
or not they are equal to the base (Let's say you are going to be
stubborn and want this silly 3DPoint extends 2DPoint nonsense to stand
- a 3DPoint knows if it is equal to a 2DPoint (if z = 0) but a 2DPoint
does not). You could try this, in your 2DPoint's equals:

public boolean equals(Object other) {
if ( !(other instanceof 2DPoint) ) return false;
if ( !(other.getClass().equals(2DPoint.class)) ) {
//other is a subclass of us! We best defer to it!
return other.equals(this);
}
2DPoint o = (2DPoint)other;
return o.x == x && o.y == y;
}

This only covers the case where you are comparing subclass with base
class, not subclass with other subclass. That's way more complicated.
You can still do this but it rapidly degenerates into unmaintainable
dreck by letting each subclass do an explicit '2DPoint-equivalency'
check.

In other words, 3DPoint would first check if it is similar to a
2DPoint (z = 0). If this is true, it will generate an equivalent
2DPoint object, and, if the object being compared to is a (subclass
of) 2DPoint, it will then do the same trick - it will ask the OTHER
object if it is equal to the supplied 2DPoint, -not- 'this' (which is
a 3DPoint). If so, they are 'equal'. If all subclasses pull this
stunt, it'll work while keeping all the rules about equals() intact:

- a.equals(b) implies b.equals(a). !a.equals(b) implies !b.equals(a).
- a.equals(a) is always true.
- a.equals(b) and b.equals(c) imply a.equals(c).

If 3DPoint is itself extensible you need to combine these two ideas
and your equals starts becoming a page long of complicated code. Hence
the hairy unmaintainable dreck reference.

But, if it is absolutely crucial to have, it CAN be done.

*) As much as it pains me, over the years I have come to see the light
and wish that final was the default on classes. It's not without its
flaws but considering everything it's slightly better than the
alternative. But that is a discussion for another thread. The
ramblings in this very post also offer a bit of a backup to this idea;
equals is one fine case which just goes all haywire if you start
extending classes, especially ones that weren't designed with
extension in mind.

Christian Catchpole

unread,
Oct 28, 2007, 3:55:37 PM10/28/07
to The Java Posse
Yeah, translating (projecting) a 3D point to 2D requires a point of
view. I was thought just talking about "can 3D points be looked at as
2D just without Z" - which is true. It's not a projection, but it's
uncommon to wan to do this and the equals methods and other such
things have it come unstuck.

I think we all agree that class extension is not the silver bullet
promised by OO. It's useful for InputStreams and Collections, but
when the contract skews along the hierarchy, thats where we get the
problems.

Martin OConnor

unread,
Oct 28, 2007, 10:42:26 PM10/28/07
to The Java Posse
Translation is interesting and all but its just an operation that
produces a 2D point from a 3D point or vice versa. This doesn't escape
the fact that a 2D point *is not* a 3D point and a 3D point *is not* a
2D point. The fact that one can be translated to the other does not
justify the class relationship as given in the OP's example.

Object inheritance is a valuable tool, but like all tools, must be
used correctly - The right tool for the right job. IMHO I don't think
object oriented inheritance promised any such silver bullet. One needs
to do proper object oriented analysis and design. All too often,
people rush into specifying relationships that in reality do not hold
up, because it looks like such a relationship might exist. In reality,
applying good object oriented principles should almost always root out
such flawed relationships from a design.

Chica

unread,
Oct 29, 2007, 3:43:38 AM10/29/07
to The Java Posse

> > problems.- Hide quoted text -

Wow what a very sweet topic!

Christian Catchpole

unread,
Oct 29, 2007, 5:17:13 AM10/29/07
to The Java Posse
I having actually written 3D projection code which has PointOfView,
Rotation and Coordinate classes. The Coordinate class has both 3d and
the 2d projection values in the same class - which i know will make
some people shake their heads.. but it just made the whole thing so
easy :). I also wrote a Z order comparator so I needed only use
Arrays.sort() to do my front to back Z ordering.. :)

public void paint(Graphics g, PointOfView pov, Style style) {
Node[] nodeArray = (Node[])nodeList.toArray(new
Node[nodeList.size()]);

for (Node node : nodeArray) {
node.rotate(pov);
}

// sort Z points
Arrays.sort(nodeArray);

// project points
for (Node node : nodeArray) {
node.project(pov);
}

// paint nodes
for (Node node : nodeArray) {
node.paint(g, pov, style);
}

// paint labels
for (Node node : nodeArray) {
node.paintLabel(g, pov, style);
}
}

On Oct 29, 5:43 pm, Chica <Garcialo...@gmail.com> wrote:


> On Oct 29, 12:42?am, Martin OConnor <marti...@gmail.com> wrote:
>
>
>
> > Translation is interesting and all but its just an operation that
> > produces a 2D point from a 3D point or vice versa. This doesn't escape
> > the fact that a 2D point *is not* a 3D point and a 3D point *is not* a
> > 2D point. The fact that one can be translated to the other does not
> > justify the class relationship as given in the OP's example.
>
> > Object inheritance is a valuable tool, but like all tools, must be
> > used correctly - The right tool for the right job. IMHO I don't think
> > object oriented inheritance promised any such silver bullet. One needs
> > to do proper object oriented analysis and design. All too often,
> > people rush into specifying relationships that in reality do not hold
> > up, because it looks like such a relationship might exist. In reality,
> > applying good object oriented principles should almost always root out
> > such flawed relationships from a design.
>
> > On Oct 28, 7:55 pm, Christian Catchpole <ato...@catchpole.net> wrote:
>
> > > Yeah, translating (projecting) a 3D point to 2D requires a point of

> > > view. ?I was thought just talking about "can 3D points be looked at as
> > > 2D just without Z" - which is true. ?It's not a projection, but it's


> > > uncommon to wan to do this and the equals methods and other such
> > > things have it come unstuck.
>
> > > I think we all agree that class extension is not the silver bullet

> > > promised by OO. ?It's useful for InputStreams and Collections, but

Kevin Wong

unread,
Oct 29, 2007, 12:18:16 PM10/29/07
to The Java Posse
To clarify, what exactly is the desired behaviour of the equals
implementation here? Assume TwoDeePoint p1 = (1,2) and ThreeDeePoint
pz = (1,2,z). Do you want p1.equals(pz) to return true? This would
be wrong because there would be no way to satisfy transitivity.
Assume ThreeDeePoint p2 = (1,2,3) and ThreeDeePoint p3 = (1,2,4).
p1.equals(p2) would return true, p1.equals(p3) would return true, but
p2.equals(p3) would return false; transitivity broken. This is unless
you want to handle the z=0 case explicitly, which would be another
story.

I think the more common case involves subclasses the introduce fields
that do not have implied default values in the superclass (one could
arge that TwoDeePoint's implied value for z is 0). Perhaps it would
be more productive to discuss the Point/ColorPoint example in Josh
Block's Effective Java Programming Language Guide (Addison-Wesley,
2001). In that case, it would be stretch to assert that a
ColorPoint's equality with a Point should be dependent on its color
being set to null (such as the z=0 case).

In regards to a p1.equals(pz) == false solution for the TwoDeePoint/
ThreeDeePoint problem, consider this (based on http://www.ddj.com/java/184405053):

public class TwoDeePoint
{
public int x = 0;
public int y = 0;

protected boolean blindlyEquals(Object o) {
if (!(o instanceof TwoDeePoint))
return false;
TwoDeePoint p = (TwoDeePoint)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
if (!(o instanceof TwoDeePoint))
return false;
TwoDeePoint p = (TwoDeePoint)o;
return (this.blindlyEquals(p) && p.blindlyEquals(this));
}

}

public class ThreeDeePoint extends TwoDeePoint
{
public int z = 0;

protected boolean blindlyEquals(Object o) {
if (!(o instanceof ThreeDeePoint))
return false;
ThreeDeePoint p = (ThreeDeePoint)o;
return (super.blindlyEquals(p) &&
p.z == this.z);
}

}

import static org.junit.Assert.*;


public class Test
{
@org.junit.Test
public void testEquals() throws Throwable
{
final TwoDeePoint a = new TwoDeePoint();
a.x = 1;
a.y = 2;
final ThreeDeePoint b = new ThreeDeePoint();
b.x = a.x;
b.y = a.y;
b.z = 3;
final ThreeDeePoint c = new ThreeDeePoint();
c.x = a.x;
c.y = a.y;
c.z = 3;
final ThreeDeePoint d = new ThreeDeePoint();
d.x = a.x;
d.y = a.y;
d.z = 4;

assertFalse(a.equals(b));
assertEquals(a.equals(b), b.equals(a));
assertTrue(b.equals(c));
assertFalse(b.equals(d));
}
}

Overriding blindlyEquals frees subclasses from the constraint of
symmetry, as that is provided by the inherited implementation of
equals.

On Oct 26, 6:02 pm, Robert Fischer <Robert.Fisc...@SmokejumperIT.com>
wrote:

Robert Fischer

unread,
Oct 29, 2007, 12:25:33 PM10/29/07
to java...@googlegroups.com
Your first paragraph gets at exactly the issue with having an instance-based #equals/#compareTo.

The delegating-equals approach is interesting.  I tried to dig at that spot before, but never got the implementation quite right.  I'm going to think on that some more.

~~ Robert.

Martin OConnor

unread,
Oct 29, 2007, 2:06:42 PM10/29/07
to The Java Posse
Why are you making things more complicated by having ThreeDeePoint
extend TwoDeePoint? Its like trying to force a square peg in a round
hole! A straight comparison between a ThreeDeePoint and a TwoDeePoint
should *ALWAYS* return false. Both ThreeDeePoint and TwoDeePoint
should extend Object (not explicitly of course), and if for instance
you want to compare a TwoDeePoint to a ThreeDeePoint in a certain
space, then translate the TwoDeePoint into the appropriate
ThreeDeePoint space and compare the output from the translation for
equality.

On Oct 29, 4:25 pm, Robert Fischer <Robert.Fisc...@SmokejumperIT.com>
wrote:


> Your first paragraph gets at exactly the issue with having an instance-based #equals/#compareTo.
> The delegating-equals approach is interesting. I tried to dig at that spot before, but never got the implementation quite right. I'm going to think on that some more.
> ~~ Robert.

> Kevin Wong wrote:To clarify, what exactly is the desired behaviour of the equals implementation here? Assume TwoDeePoint p1 = (1,2) and ThreeDeePoint pz = (1,2,z). Do you want p1.equals(pz) to return true? This would be wrong because there would be no way to satisfy transitivity. Assume ThreeDeePoint p2 = (1,2,3) and ThreeDeePoint p3 = (1,2,4). p1.equals(p2) would return true, p1.equals(p3) would return true, but p2.equals(p3) would return false; transitivity broken. This is unless you want to handle the z=0 case explicitly, which would be another story. I think the more common case involves subclasses the introduce fields that do not have implied default values in the superclass (one could arge that TwoDeePoint's implied value for z is 0). Perhaps it would be more productive to discuss the Point/ColorPoint example in Josh Block's Effective Java Programming Language Guide (Addison-Wesley, 2001). In that case, it would be stretch to assert that a ColorPoint's equality with a Point should be dependent on its color being set to null (such as the z=0 case). In regards to a p1.equals(pz) == false solution for the TwoDeePoint/ ThreeDeePoint problem, consider this (based onhttp://www.ddj.com/java/184405053):public class TwoDeePoint { public int x = 0; public int y = 0; protected boolean blindlyEquals(Object o) { if (!(o instanceof TwoDeePoint)) return false; TwoDeePoint p = (TwoDeePoint)o; return (p.x == this.x && p.y == this.y); } public boolean equals(Object o) { if (!(o instanceof TwoDeePoint)) return false; TwoDeePoint p = (TwoDeePoint)o; return (this.blindlyEquals(p) && p.blindlyEquals(this)); } } public class ThreeDeePoint extends TwoDeePoint { public int z = 0; protected boolean blindlyEquals(Object o) { if (!(o instanceof ThreeDeePoint)) return false; ThreeDeePoint p = (ThreeDeePoint)o; return (super.blindlyEquals(p) && p.z == this.z); } } import static org.junit.Assert.*; public class Test { @org.junit.Test public void testEquals() throws Throwable { final TwoDeePoint a = new TwoDeePoint(); a.x = 1; a.y = 2; final ThreeDeePoint b = new ThreeDeePoint(); b.x = a.x; b.y = a.y; b.z = 3; final ThreeDeePoint c = new ThreeDeePoint(); c.x = a.x; c.y = a.y; c.z = 3; final ThreeDeePoint d = new ThreeDeePoint(); d.x = a.x; d.y = a.y; d.z = 4; assertFalse(a.equals(b)); assertEquals(a.equals(b), b.equals(a)); assertTrue(b.equals(c)); assertFalse(b.equals(d)); } } Overriding blindlyEquals frees subclasses from the constraint of symmetry, as that is provided by the inherited implementation of equals. On Oct 26, 6:02 pm, Robert Fischer<Robert.Fisc...@SmokejumperIT.com>wrote:Hey there. Jumping on because 1) the list was recommended to me, and 2) your conversation on #equals seemed good. I've got a few posts on #equals on my blog, which I linked to off of this post:http://enfranchisedmind.com/blog/archive/2007/10/26/417I'm yet to encounter a general inheritance-safe solution for equals beyond externalizing the equals/compareTo implementation. Check out the TwoDeePoint and ThreeDeePoint examples in my blog: if you've got a good solution for that, you'll be my hero. The best suggestion I have is that if the classes of two objects aren't equal, then they shouldn't be equal. That's the safest way to implement a built-in version of #equals/#compraeTo. ~~ Robert.

Kevin Wong

unread,
Oct 29, 2007, 4:06:06 PM10/29/07
to The Java Posse
Like I said, the correctness of ThreeDeePoint extending TwoDeePoint is
dubious, so it makes for a poor test case of the equals question.
ColorPoint extending Point, however, seems sensible and might provide
a more productive basis of discussion.

On Oct 29, 2:06 pm, Martin OConnor <marti...@gmail.com> wrote:
> Why are you making things more complicated by having ThreeDeePoint
> extend TwoDeePoint? Its like trying to force a square peg in a round
> hole! A straight comparison between a ThreeDeePoint and a TwoDeePoint
> should *ALWAYS* return false. Both ThreeDeePoint and TwoDeePoint
> should extend Object (not explicitly of course), and if for instance
> you want to compare a TwoDeePoint to a ThreeDeePoint in a certain
> space, then translate the TwoDeePoint into the appropriate
> ThreeDeePoint space and compare the output from the translation for
> equality.
>
> On Oct 29, 4:25 pm, Robert Fischer <Robert.Fisc...@SmokejumperIT.com>
> wrote:> Your first paragraph gets at exactly the issue with having an instance-based #equals/#compareTo.
> > The delegating-equals approach is interesting. I tried to dig at that spot before, but never got the implementation quite right. I'm going to think on that some more.
> > ~~ Robert.

> > Kevin Wong wrote:To clarify, what exactly is the desired behaviour of the equals implementation here? Assume TwoDeePoint p1 = (1,2) and ThreeDeePoint pz = (1,2,z). Do you want p1.equals(pz) to return true? This would be wrong because there would be no way to satisfy transitivity. Assume ThreeDeePoint p2 = (1,2,3) and ThreeDeePoint p3 = (1,2,4). p1.equals(p2) would return true, p1.equals(p3) would return true, but p2.equals(p3) would return false; transitivity broken. This is unless you want to handle the z=0 case explicitly, which would be another story. I think the more common case involves subclasses the introduce fields that do not have implied default values in the superclass (one could arge that TwoDeePoint's implied value for z is 0). Perhaps it would be more productive to discuss the Point/ColorPoint example in Josh Block's Effective Java Programming Language Guide (Addison-Wesley, 2001). In that case, it would be stretch to assert that a ColorPoint's equality with a Point should be dependent on its color being set to null (such as the z=0 case). In regards to a p1.equals(pz) == false solution for the TwoDeePoint/ ThreeDeePoint problem, consider this (based onhttp://www.ddj.com/java/184405053):publicclass TwoDeePoint { public int x = 0; public int y = 0; protected boolean blindlyEquals(Object o) { if (!(o instanceof TwoDeePoint)) return false; TwoDeePoint p = (TwoDeePoint)o; return (p.x == this.x && p.y == this.y); } public boolean equals(Object o) { if (!(o instanceof TwoDeePoint)) return false; TwoDeePoint p = (TwoDeePoint)o; return (this.blindlyEquals(p) && p.blindlyEquals(this)); } } public class ThreeDeePoint extends TwoDeePoint { public int z = 0; protected boolean blindlyEquals(Object o) { if (!(o instanceof ThreeDeePoint)) return false; ThreeDeePoint p = (ThreeDeePoint)o; return (super.blindlyEquals(p) && p.z == this.z); } } import static org.junit.Assert.*; public class Test { @org.junit.Test public void testEquals() throws Throwable { final TwoDeePoint a = new TwoDeePoint(); a.x = 1; a.y = 2; final ThreeDeePoint b = new ThreeDeePoint(); b.x = a.x; b.y = a.y; b.z = 3; final ThreeDeePoint c = new ThreeDeePoint(); c.x = a.x; c.y = a.y; c.z = 3; final ThreeDeePoint d = new ThreeDeePoint(); d.x = a.x; d.y = a.y; d.z = 4; assertFalse(a.equals(b)); assertEquals(a.equals(b), b.equals(a)); assertTrue(b.equals(c)); assertFalse(b.equals(d)); } } Overriding blindlyEquals frees subclasses from the constraint of symmetry, as that is provided by the inherited implementation of equals. On Oct 26, 6:02 pm, Robert Fischer<Robert.Fisc...@SmokejumperIT.com>wrote:Hey there. Jumping on because 1) the list was recommended to me, and 2) your conversation on #equals seemed good. I've got a few posts on #equals on my blog, which I linked to off of this post:http://enfranchisedmind.com/blog/archive/2007/10/26/417I'myet to encounter a general inheritance-safe solution for equals beyond externalizing the equals/compareTo implementation. Check out the TwoDeePoint and ThreeDeePoint examples in my blog: if you've got a good solution for that, you'll be my hero. The best suggestion I have is that if the classes of two objects aren't equal, then they shouldn't be equal. That's the safest way to implement a built-in version of #equals/#compraeTo. ~~ Robert.

Robert Fischer

unread,
Oct 29, 2007, 6:59:58 PM10/29/07
to java...@googlegroups.com
The 2DPoint/3DPoint wasn't intended to be a practical example, but (rather) a token illustration of a general problem.  If "Point" and "ColorPoint" are accepted as a legitimate use of inheritance, then we can use that -- it would have the same problem.
Reply all
Reply to author
Forward
0 new messages