Let's have a class Person
. Person has a name and height.
Equals and hashCode() takes into account only name. Person is comparable (or we implement comparator for it, does not matter which one). Persons are compared by height.
It seems reasonable to expect a situation where two different persons can have same height, but eg. TreeSet behaves like comapareTo()==0 means equals, not merely same size.
To avoid this, comparison can secondarily look at something else if size is the same, but then it cannot be used to detect same sized different objects.
Example:
import java.util.Comparator;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
public class Person implements Comparable<Person> {
private final String name;
private int height;
public Person(String name,
int height) {
this.name = name;
this.height = height;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
@Override
public int compareTo(Person o) {
return Integer.compare(height, o.height);
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Person other = (Person) obj;
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
public int hashCode() {
int hash = 5;
hash = 13 * hash + Objects.hashCode(this.name);
return hash;
}
public String toString() {
return "Person{" + name + ", height = " + height + '}';
}
public static class PComparator1 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
return o1.compareTo(o2);
}
}
public static class PComparator2 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
int r = Integer.compare(o1.height, o2.height);
return r == 0 ? o1.name.compareTo(o2.name) : r;
}
}
public static void test(Set<Person> ps) {
ps.add(new Person("Ann", 150));
ps.add(new Person("Jane", 150));
ps.add(new Person("John", 180));
System.out.println(ps.getClass().getName());
for (Person p : ps) {
System.out.println(" " + p);
}
}
public static void main(String[] args) {
test(new HashSet<Person>());
test(new TreeSet<Person>());
test(new TreeSet<>(new PComparator1()));
test(new TreeSet<>(new PComparator2()));
}
}
result:
java.util.HashSet
Person{Ann, height = 150}
Person{John, height = 180}
Person{Jane, height = 150}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{Jane, height = 150}
Person{John, height = 180}
Do you have idea why it is so?
Extract from the
java.util.SortedSet
javadoc:Hence, in other words,
SortedSet
breaks (or "extends") the general contracts forObject.equals()
andComparable.compareTo
. See the contract forcompareTo
:When you give Person a Comparator that compares instances on the height attribute of the Person, it really means that two Person instances are the same if they have the same height. You will have to make a Comparator that is specific for class Person.
TreeSet
doesn't operate using hash codes and equality - it only operates on the basis of the comparator you give it. Note that the Javadoc states:In your case, your comparison *isn't consistent with
equals
, so your set doesn't obey the general contract ofSet
.Why not just add more aspects to the comparison so that only equal elements compare with a result of 0?
you can fix it by using name for another comparison when the heights are equal
since names are unique this will only return 0 if
this.equals(o)
It is recommended that
compareTo
only returns0
, if a call toequals
on the same objects would returntrue
:(From the JDK 1.6 Javadocs)
It is strongly recommended, but not strictly required that
(x.compareTo(y)==0) == (x.equals(y))
[1]So it's fine that you compare with a different criteria than the used on
equals
granted that you document it.However it would be better if your compare by the same criteria and if needed provide a custom comparator which works on the new criteria ( height in the case of Person )