Because it's hard (impossible?) to make it right, especially the symmetric property.
Say you have class Vehicle
and class Car extends Vehicle
. Vehicle.equals()
yields true
if the argument is also a Vehicle
and has the same weight. If you want to implement Car.equals()
it should yield true
only if the argument is also a car, and except weight, it should also compare make, engine, etc.
Now imagine the following code:
Vehicle tank = new Vehicle();
Vehicle bus = new Car();
tank.equals(bus); //can be true
bus.equals(tank); //false
The first comparison might yield true
if by coincidence tank and bus have the same weight. But since tank is not a car, comparing it to a car will always yield false
.
You have few work-arounds:
strict: two objects are equal if and only if they have exactly the same type (and all properties are equal). This is bad, e.g. when you subclass barely to add some behaviour or decorate the original class. Some frameworks are subclassing your classes as well without you noticing (Hibernate, Spring AOP with CGLIB proxies...)
loose: two objects are equal if their types are "compatible" and they have same contents (semantically). E.g. two sets are equal if they contain the same elements, it doesn't matter that one is HashSet
and the other is TreeSet
(thanks @veer for pointing that out).
This can be misleading. Take two LinkedHashSet
s (where insertion order matters as part of the contract). However since equals()
only takes raw Set
contract into account, the comparison yields true
even for obviously different objects:
Set<Integer> s1 = new LinkedHashSet<Integer>(Arrays.asList(1, 2, 3));
Set<Integer> s2 = new LinkedHashSet<Integer>(Arrays.asList(3, 2, 1));
System.out.println(s1.equals(s2));