"Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator 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 TreeSet instance 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 set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface."
Lets see what this means in practice. In this example we have a Transaction object which has an id and amount instance variables. Transactions are considered equal if they have the same id.
public class Transaction { private final String id; private final int amount; public Transaction(String id, int amount) { this.id = id; this.amount = amount; } public int getAmount() { return amount; } @Override public int hashCode() { return id != null ? id.hashCode() : 0; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof Transaction)) return false; return id != null ? id.equals(((Transaction) o).id) : ((Transaction) o).id == null; } }To compare Transaction objects based on the value of the amount so we introduce the relevant Comparator:
public static class TransactionComparator implements ComparatorWe now have everything in place for a small experiment:{ @Override public int compare(Transaction t1, Transaction t2) { return t1.getAmount() - t2.getAmount(); } }
TreeSetThe quote at the top of the post means that as far as the TreeSet (and its friendss in SortedSet interface) is concerned, object equality is derived from compareTo() and not equal(), hence the two transactions t1 and t2 are equal. This is pretty unusual but one could argue that a fair warning was given.set = new TreeSet<>(new TransactionComparator()); Transaction t1 = new Transaction("T1", 100); Transaction t2 = new Transaction("T2", 100); System.out.printf("set did not already contain t1: %b\n", set.add(t1)); // set did not already contain t1: true System.out.printf("set did not already contain t2: %b\n", set.add(t2)); // set did not already contain t2: false System.out.printf("|set| = %d\n", set.size()); // |set| = 1
Except that "The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface." doesn't really match:
System.out.printf("t1 is in set: %b\n", set.contains(t1)); // t1 is in set: true System.out.printf("t2 is in set: %b\n", set.contains(t2)); // t2 is in set: trueAnd there we have it: |set| == 1 and set contains two elements. I think that it is more correct to say that "All bets are off regarding the behavior of a set if its ordering is inconsistent with equals"
-- Gist link