Why does the framework not require Comparable for HashSet… — Cracked Java
// Java Collections Framework · Comparable vs Comparator
MidTheory

Why does the framework not require Comparable for HashSet but does for TreeSet?

HashSet only needs equals and hashCode because it identifies elements by bucket + equality, not by ordering. TreeSet needs Comparable (or an explicit Comparator) because it stores elements in a sorted balanced tree and must answer "is x less than, equal to, or greater than y" at every node to position new elements.

What each set type needs from its element type

CollectionRequires equalsRequires hashCodeRequires compareTo or Comparator
HashSetYesYesNo
LinkedHashSetYesYesNo
TreeSetNo*No*Yes
EnumSet(uses ordinal)(uses ordinal)No
CopyOnWriteArraySetYesNo (linear search)No

*TreeSet uses compareTo for membership, so equals/hashCode are technically irrelevant for set semantics — but you should still implement them consistently with compareTo to avoid surprises.

HashSet internals (why compareTo isn't needed)

// Conceptual HashSet.contains
int h = element.hashCode();           // pick a bucket
Bucket b = table[h & (table.length - 1)];
for (var entry : b) {
    if (entry.equals(element)) return true; // bucket-local equality check
}
return false;

No ordering question is ever asked. Two elements only meet when they hash to the same bucket — and then equals decides. Position in the bucket is insertion order (or unordered), not sorted.

TreeSet internals (why compareTo IS needed)

TreeSet is backed by a red-black tree. To insert an element, the tree must compare it against the root, then recursively against the chosen child, until it finds the right leaf position. Every step asks "less, equal, or greater?" That's the compareTo contract.

Insert 7 into:
     5
    / \
   3   8
  /   / \
 1   6   9

compareTo(7, 5) -> positive, go right
compareTo(7, 8) -> negative, go left
compareTo(7, 6) -> positive, become 6's right child

Result:
     5
    / \
   3   8
  /   / \
 1   6   9
      \
       7
TreeSet insertion walks the tree via compareTo

Without compareTo, the tree can't decide left/right at any node. There's no fallback to "any position" — sorted means sorted.

What about LinkedHashSet?

LinkedHashSet extends HashSet and additionally maintains a doubly-linked list of insertion order. Membership still uses bucket+equals. No compareTo needed.

The TreeSet without Comparable error

class Person { String name; int age; }

var set = new TreeSet<Person>(); // compiles fine
set.add(new Person("Alice", 30));
set.add(new Person("Bob", 25));  // throws ClassCastException

The exception is runtime, not compile-time, because TreeSet<E> is parameterized as TreeSet<E> — the constraint is enforced only when you call add (which casts the element to Comparable).

Fix by either implementing Comparable<Person> or passing a Comparator:

var set = new TreeSet<Person>(Comparator.comparing(p -> p.name));

Mark your status