Sure, but then you need to reinvent everything. You can't even use java.util.List, as it'll call .equals() on contained objects whenever you call .contains().
I'm trying to find a solution that I can for example add to lombok's @Data/@EqualsAndHashCode generation, which is compatible with JPA but which doesn't break the equals contract. On Oct 17, 8:58 am, Ricky Clarkson <[email protected]> wrote: > 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 <[email protected]> > 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 [email protected]. > > To unsubscribe from this group, send email to > > [email protected]. > > For more options, visit this group > > athttp://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 [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/javaposse?hl=en.
