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 e.g. TreeSet behaves like compareTo()==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?
It is recommended that compareTo only returns 0 , if a call to equals on the same objects would return true : The natural ordering for a class C is said to be consistent with equals if and only if e1. compareTo(e2) == 0 has the same boolean value as e1. equals(e2) for every e1 and e2 of class C.
2) CompareTo must be in consistent with equals method e.g. if two objects are equal via equals() , there compareTo() must return zero otherwise if those objects are stored in SortedSet or SortedMap they will not behave properly.
Java String compareTo() MethodThe method returns 0 if the string is equal to the other string. A value less than 0 is returned if the string is less than the other string (less characters) and a value greater than 0 if the string is greater than the other string (more characters).
This is mostly used for sorting, so the results returned by compare(...) are a comparison of ranks and 0 means equality in rank.
Extract from the java.util.SortedSet
javadoc:
Note that the ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be consistent with equals if the sorted set is to correctly implement the Set interface. (See the Comparable interface or Comparator interface for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a sorted set performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal. The behavior of a sorted set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
Hence, in other words, SortedSet
breaks (or "extends") the general contracts for Object.equals()
and Comparable.compareTo
. See the contract for compareTo
:
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact. The recommended language is "Note: this class has a natural ordering that is inconsistent with equals."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With