Peter
The contract for the equals method states that equals should be both
transitive and commutative. The logical conclusion of that rule means
that 2 objects can only be equal if they are of the same type.
Reinier Zwitserloot wrote:
> Yes, it follows.
No it doesn't. Again, I agree with nearly everything you wrote,
but I'm quite sure my example (given at the end of this posting) works
(at least in principle, I never tested it).
My example is surely not very practical, especially there's little hope for autogenerated equals.
> Reason #1: Try it. you'll get an AssertError; new Integer(2) is NOT
> equal to new Double(2.0)!!! This is a little wonky, because "2 == 2.0"
> evaluates to true.
I think he just wanted to demostrate the principle.
> Here's the theory: An integer and a double are never equals to each
> other. It's comparing apples and oranges. The .equals() method
> actually allows you to compare apples to oranges; "someString".equals
> (new ArrayList()); is perfectly legit, and obviously returns false.
> Similarly, new Integer(2).equals(new Double(2.0)) is perfectly legit
> and returns false because they aren't even the same type.
>
> However, java math operations (which == is, if it involves number
> types on both sides) do not allow using two different types. However,
> in certain circumstances, a cast to convert one of the two elements to
> the other (or, in rare cases, convert -both-) is implicit, if one of
> them can be cast to the other with no loss (such as int to double,
> byte to int, or float to double).
Three of the implicit conversions are lossy: int or long to float and long to double.
Either sun didn't know better, or they prefered to mimic C.
> That's what's really happening in '2
> == 2.0' - it does not mean that int 2 is equal to double 2.0. It
> simply means that '(double)2 == 2.0' is true, and that '2 == 2.0' has
> that cast in it too, it's just implicit.
>
> Fun source of puzzlers, of course. Still, .equals() of Integer and
> Double don't break the rule.
>
> Reason #2: It follows if you accept certain pragmatic axiomas. For
> example, you can get around the rule if a subclass is not allowed to
> change the state space in any way. For example, one can argue that
> ArrayList and LinkedList are both Lists in the purest sense - they do
> not have extra (publically visible) state. This isn't like a Shape and
> a Circle, where the Circle adds 'radius' to the state space. ArrayList
> and LinkedList break the rule, in fact: They will be equals() to each
> other if they both contain the same elements in the same order.
> However, this kind of implementation-based splitting is extremely
> rare, and lombok does not cater to the rare cases. Another way around
> it is if you say that the new state space is not considered in equals
> comparisons, but then you'd have the weird situation that 2 circles
> are equal if their center is in the same place, even if their radii
> are different, which is silly.
I think even in such a case there can be an useful equals like this:
Shape.equals(Object o) {
...
if (!(o instanceof Shape)) return false;
Shape s = (Shape) o;
if (getCenter().equals(s.getCenter()) return false;
if (s.getClass() == Shape.class) return true;
return s.equals(this); // delegate to subclass as it's better qualified
}
But there're at least two problems with it (despite it's overcomplicated):
It's quite errorprone on it's own and additionally you must override equals in each subclass.
I believe I could get it work even with some Square-s and Rectangle-s, but it's not easy.
> Another one is where you extend a class
> just to add features that purely operate on the original space. This
> is kind of like LinkedList, which adds Stack methods to an ordinary
> list. This kind of design sucks in too many ways to count, and lombok
> tends not to cater to outdated patterns.
>
> Either way, implementing this stuff is impossible to automate, and
> thus, even if it was both common and a good idea, lombok just cannot
> go there. See my first post in this thread for why automating it is
> hard to impossible.
>
>
> NB: Just as an FYI, comparing 2 with 2.0 is not going to go wrong due
> to floating point imprecision. A 'double' can express with perfect
> accuracy every single number that is expressable as an 'int'. A double
> can NOT represent every legal long without loss, but it CAN represent
> every feasible timestamp (in getCurrentTimeMillis() format) - at least
> every timestamp between the big bang and the heat death of the
> universe. Things go pearshaped if you try to store non-integral
> numbers in them and expect perfect accuracy.
Regards, Maaartin.
public abstract class Matrix {
Matrix(int size) {
this.size = size; // disallow subclassing outside of the package
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof Matrix)) return false;
final Matrix other = (Matrix) obj;
if (size != other.size) return false;
for (int i=0; i<size; ++i) {
for (int j=0; j<size; ++j) {
if (get(i, j) != other.get(i, j)) return false;
}
}
return true;
}
public abstract double get(int i, int j);
public int getSize() {
return size;
}
@Override
public final int hashCode() {
return size; // very stupid and inefficient but correct
}
private final int size;
}
public class DiagonalMatrix extends Matrix {
public DiagonalMatrix(int size) {
super(size);
data = new double[size];
}
@Override
public double get(int i, int j) {
return i!=j ? 0 : data[i];
}
private final double[] data;
}
public class IdentityMatrix extends Matrix {
public IdentityMatrix(int size) {
super(size);
}
@Override
public double get(int i, int j) {
return i!=j ? 0 : 1;
}
}
public class SquareMatrix extends Matrix {
public SquareMatrix(int size) {
super(size);
data = new double[size][size];
}
@Override
public double get(int i, int j) {
return data[i][j];
}
private final double[][] data;
}