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.
On Oct 26, 11:02 pm, Robert Fischer <Robert.Fisc...@SmokejumperIT.com>
wrote:
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. :)
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.
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.
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.
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.
> > problems.- Hide quoted text -
Wow what a very sweet topic!
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
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:
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.
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.