What are the benefits of running runtime exceptions on compile time checks for collections that impose comparable types?


When implementing my own data structures which require a comparable type I've always done so like this:

public class ComparableCollection<E extends Comparable<E>> { ... }

This obviously enforces the comparable constraint at compile time. But I've been a student for the last couple years and have somehow overlooked the fact that the Java implementations for collections that enforce a comparable type do not do so at compile time but rather at runtime by possibly throwing a ClassCastException while adding an element; e.g.:

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, ... { ... }

TreeSet is backed by a NavigableMap which, if it's Comparator is null, attempts to cast the key like so:

Comparable<? super K> k = (Comparable<? super K>) key;

Now, if the type being inserted isn't comparable a ClassCastException is thrown.

What are the real benefits of this design over enforcing the constraint at compile time?

The benefit is that you can then use TreeSet with objects that do not implement Comparable but for which you can provide a Comparator.

For more about the differences between Comparable an Comparator, see: Java : Comparable vs Comparator