There's a big difference between instanceof
and class comparison. Remember that (for example) a HashMap
instance is instanceof
AbstractMap
, but AbstractMap
's equals
shouldn't consider other
an AbstractMap
if it's actually a HashMap
. instanceof
tests allow for an instance of the type or any of its subtypes and are therefore not symmetric. equals
requires symmetry. (Example below.)
The Netbeans code does seem rather stilted and could be written more concisely, but the operations, in the order given, make sense as a default starting point. (Although I'd've included a this
check.) It's a rare pair of classes that can have usefully have equal
instances, so using getClass
for a default implementation is reasonable.
Just to remind us all of the contract:
- It is reflexive: for any non-
null
reference value x
, x.equals(x)
should return true
.
- It is symmetric: for any non-
null
reference values x
and y
, x.equals(y)
should return true
if and only if y.equals(x)
returns true
.
- It is transitive: for any non-
null
reference values x
, y
, and z
, if x.equals(y)
returns true
and y.equals(z)
returns true
, then x.equals(z)
should return true
.
- It is consistent: for any non-
null
reference values x and
y, multiple invocations of
x.equals(y)consistently return
trueor consistently return
false, provided no information used in
equals` comparisons on the objects is modified.
- For any non-
null
reference value x
, x.equals(null)
should return false
.
In my early Java days in the 90's I broke this contract (unwittingly), and based on the answers to this question, I'm not the only one. I used instanceof
(and super.equals
), like this:
class Parent {
private int foo;
protected int getFoo() {
return foo;
}
Parent(int f) {
this.foo = f;
}
// ...
@Override
public boolean equals(Object obj) {
// WRONG, see below
if (obj == this) {
return true;
}
if (!(obj instanceof Parent)) {
return false;
}
return this.foo == ((Parent)obj).foo;
}
}
class Child extends Parent {
private int bar;
Child(int f, int b) {
super(f);
this.bar = b;
}
// ...
@Override
public boolean equals(Object obj) {
// WRONG, see below
if (obj == this) {
return true;
}
if (!(obj instanceof Child)) {
return false;
}
return super.equals(obj) && this.bar == ((Child)obj).bar;
}
}
The problem there is that it's not symmetric:
Parent p = new Parent(1);
Child c = new Child(1, 2);
System.out.println(p.equals(c)); // true
System.out.println(c.equals(p)); // false
That violates the contract.