Groovy’s Collection.minus(Collection)

If you’ve done much Groovy development, you’ve probably used all the handy helper methods that it provides to augment the core JRE classes.  Collections, in particular, have some really helpful additions in the form of operator extension.  For example, the minus operator works like this:

assert [1, "a", true, true, false, 5.3] - [true, 5.3] == [1, "a", false]

This is all sorts of handy, but I found an interesting bug in Groovy 1.8.4′s implementation.  Behind the scenes, it uses two different Comparators to do the equality checks needed to perform the subtraction.  However, the Comparators are a little off…

The first Comparator is actually just the DefaultTypeTransformation.compareTo method, and it gracefully handles non-Comparable, non-equal objects with the same hashCode.  It also handles nulls (which are always first), and a bunch of standard type conversions (e.g., char -> String when compared to a String).

The second Comparator, however, it less well behaved.  It’s called NumberAwareComparator, and delegates to DefaultTypeTransformation.compareTo first.  But if that fails, it falls back to a hashCode comparison, which creates a very interesting bug: if two non-equal objects have the same hashCode, NumberAwareComparator will return zero from it’s compareTo method, which will be interpreted as equality in a variety of contexts.

The workaround that I’ve found is to explicitly implement Comparable in the domain model at the appropriate level(s) so that you always have a mutually-comparable collection, and you can implement compareTo in a way consistent with equals.  Implementing Comparable like this will ensure that DefaultTypeTransformation.compareTo will successfully handle the comparison, thus preventing falling through to NumberAwareComparator.

Sometimes, it is the library.  : )