I like the type class approach:

interface Equal<A> {
  boolean eq(A a, A b);
}

Then wherever you need to be able to compare two objects you need to
make sure you have an Equal<SomeType> around.  You can use this to
control 'which meaning' of equality is applied.

interface Hash<A> {
  hash(A a);
}

Same thing but for hashcodes.  Of course, this won't really help with
existing libraries that depend on equals(Object), but I can't fault
the idea.

The Functional Java library has Equal and Hash, similar to the above
but as abstract classes with lots of convenience methods.  It also has
a number of collections that use those types.

I just realised my name's at the top of the Javadoc, which is odd as
I've made one commit ever, which I think was to fix a typo. :)

On Sun, Oct 17, 2010 at 7:06 AM, Reinier Zwitserloot <reini...@gmail.com> wrote:
> For those who DONT know why equals() is really complicated, scroll to
> the end for an explanation. Without knowing about it this post is
> probably not going to make much sense. If you understand why a
> hypothetical "ColoredList extends ArrayList" class, which adds a color
> property to any list, MUST have an equals implementation that says
> that a red empty list is equal to a blue empty list, even though that
> seems silly, you don't need to read the footnote.
>
> What we really need is for AbstractList's equals() method to be
> intelligent enough to realize if 'other' is a subclass of AbstractList
> that isn't adding any state that is relevant for equality, in which
> case it can do its comparison as usual, or, if 'other' is a subclass
> that DOES add state relevant for equality, such as a color property.
> If that is the case, AbstractList's equals method should conclude
> immediately with: Not equal, even if the contents are.
>
> A few people have proposed such a system, including a somewhat well
> known writeup by Venners and Odersky. It's very long so I'll explain
> the gist here, but the full paper can be found here:
> http://www.artima.com/lejava/articles/equality.html
>
> What they propose is adding a protected boolean canEquals(Object o)
> method. The equals() method will actually call other.canEquals(this),
> and if that is false, return false. The standard implementation of any
> canEquals method pretty much always looks like: return (o instanceof
> Point3D);, where Point3D is replaced with the closest parent (or
> yourself) that added equality-significant state. Thus, ArrayList and
> LinkedList would not override AbstractList's canEquals (which has:
> return (o instanceof AbstractList);), but something like a ColoredList
> WOULD override and replace it with "return (o instanceof
> ColoredList)". This works.... provided you don't forget to override
> the canEquals() method, which, as its certainly not a standard java
> idiom is easy to forget, and it also introduces another method to the
> API.
>
> My flash of insight here is to use this trick to entirely avoid the
> need for a canEquals method *AND* automatically do the right thing,
> leaving virtually no room for accidental error:
>
> if (!(o instanceof Self)) return false;
> Method m1 = o.getClass().getMethod("equals", Object.class);
> Method m2 = Self.class.getMethod("equals", Object.class);
> if (m1 != m2) return false;
>
> The idea is: If a  hypothetical other.equals(this) call would end up
> using the same equals method as myself, then these objects could be
> equal, even if their actual types don't match.  A new equivalence
> relation, like Point3D or colouredlist, HAVE to override equals so
> they can include their new property (z for Point3D, colour for
> ColouredList) in the comparison. However, an implementation detail,
> such as ArrayList and LinkedList, or a JPA proxy, have absolutely no
> need for overriding AbstractList/Point's equals method, and in fact,
> they don't. I've double-checked the java sources, neither LinkedList
> nor ArrayList override AbstractList's default equals implementation.
>
> I guess there's a somewhat theoretical space where a subclass
> overrides equals() for efficiency reasons, but that's probably an
> acceptable price to pay to gain the advantage of not having another
> method cluttering up the API, and a far smaller chance of breaking the
> contract by forgetting to override canEquals.
>
> Am I missing something, or is this too hacky a solution?
>
>
> FOOTNOTE: Why is equals problematic?
>
> Equality in java is a lot more problematic than you might at first
> glance think. Josh Bloch, when he wrote effective java, proposed the
> following template for writing equals methods. Let's assume we have a
> simple point class:
>
> public boolean equals(Object o) {
>    if (o == null) return false;
>    if (o == this) return true;
>    if (!(o instanceof Point)) return false;
>    if (((Point)o).x != this.x) return false;
>    if (((Point)o).y != this.y) return false;
>    return true;
> }
>
> Simple enough. But wrong. In the second edition, the instanceof check
> was revised to this:
>
> if (o.getClass() != this.getClass()) return false;
>
> and the reason is the equals contract, which says that equality in
> java must be reflexive (if a.equals(b), then b.equals(a) must also
> hold), symmetric (a.equals(a) must always hold) and transitive (if
> a.equals(b), and b.equals(c), then a.equals(c) must hold). symmetric
> is simple enough, but the others aren't. Let's say there's a subclass
> of Point named 3d point, which adds a z coordinate.
>
> Equals is easily rewritten to include: if (((Point3D)o).z != this.z)
> return false; - but what should Point3D do when you give it a Point
> class? There's only one thing to do, because of the reflexive rule: It
> should compare x and y and not compare z (as the non-3D point has
> none). It HAS to do this - because when calling
> point2d.equals(point3d), that's what happens, and you have to do the
> same as it.
>
> But now we're in deep trouble. If [0, 0, 1] is equal to [0, 0], and
> [0, 0] is in turn equal to [0, 0, 2], we are forced by the
> transitivity rule to conclude that [0, 0, 1] is equal to [0, 0, 2].
> But that's preposterous! Nobody  would expect these 2 different points
> in 3D space to nevertheless be .equals() to each other. And yet,
> that's the ONLY way to get equality right if Point is written with
> that instanceof check.
>
> This is why Josh revised effective java. But now we have a problem:
> Technically, one should only use subclassing when changing the nature
> of objects. i.e. you have a class named "Shape" and you subclass it to
> create "Square". It's perfectly allright than any random shape is
> never equal to a square, but unfortunately there's a lot of
> subclassing merely for implementation details. For example, LinkedList
> and ArrayList are virtual similes of each other and certainly model
> the same construct, they are just different implementations.
> AbstractList's equals() method is basically broken because a
> LinkedList can be equals to an ArrayList - it uses the instanceof
> style. As long as you only create implementations which don't add new
> state of their own, you're fine, but if you ever create a ColoredList
> class, which gives all lists color, you MUST write its equals method
> so that an empty red list is equal to an empty blue list, even though
> that seems ridiculous. After all, you can't change ArrayList's
> equals() method, and it will ignore that color property. Then, by way
> of transitivity, red lists equal blue lists if their contents are
> equal. Your hands are tied.
>
> --
> You received this message because you are subscribed to the Google Groups 
> "The Java Posse" group.
> To post to this group, send email to javapo...@googlegroups.com.
> To unsubscribe from this group, send email to 
> javaposse+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/javaposse?hl=en.
>
>

-- 
You received this message because you are subscribed to the Google Groups "The 
Java Posse" group.
To post to this group, send email to javapo...@googlegroups.com.
To unsubscribe from this group, send email to 
javaposse+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/javaposse?hl=en.

Reply via email to