Groovy’s compareTo operator and Equality
Tags: groovy
Published: 2020-05-20 11:57:18

---
title: Groovy’s compareTo operator and Equality
published: true
date: 2019-05-11 16:54:23 UTC
tags: groovy
canonical_url: 
---

Let’s assume we have a Book class that looks like this:

```groovy
@CompileStatic
class Book {
  String name
  String author
  Float price
}

```

Within our app we have several places where a widget is presented to the end-user, such as an API that drives a mobile app, an API that drives a single-page-application (like react/vue/etc), or even something rendered server-side like a GSP.

The data is represented well, and the customer is happy, but now there’s a business rule that states that all books should be presented sorted alphabetically. We can do this several ways:

- Sort it in a controller
- Sort it in the view itself
- (Edit) Specify the sort order in the domain class

Edit: If you are using a data source that supports it, you can specify sort columns and order directly in the `mapping` closure. The point of this post still stands outside the context of GORM though. If you have a list of any POGO you could be affected.

If we sort it in the controller it looks like this:

```groovy
@ReadOnly
def index() {
   // old code
   //respond Book.list()

   // new code to sort by name
   respond Book.list().sort { it.name }
}

```

This will work, but some developers may want to record the fact that a book’s “natural order” is that of being sorted by name. In other words, the fact is stored on the domain itself of how it _should_ be sorted. Let’s impliment this using java’s `compareTo` method:

```groovy
@CompileStatic
class Book implements Comparable<Book> {
    String name
    String author
    Float price

    @Override
    int compareTo(Book other) {
        // Sort by name ascending
        return this.name <=> other.name
    }
}

```

Now our controller method looks like this:

```groovy
@ReadOnly
def index() {
    // old code
    //respond Book.list()

    // new code to sort, this time by using the pre-defined "natural order"
    respond Book.list().sort()
}

```

## THIS HAS A VERY SERIOUS SIDE-EFFECT

The above is _not_ an ideal solution, and it’s best illustrated with a concrete example.

```groovy
def book1 = new Book(name: 'The Gadfly', author: "Ethel Voynich", price: 19.95)
def book2 = new Book(name: 'The Gadfly', author: "Johnny Copycat", price: 9.95)

assert book1 != book2 // THIS FAILS!!! GROOVY THINKS THEY ARE EQUAL!

```

It’s a bit worrisome that the two books are equal. This is clearly not the case.

### What’s happening?

I’ll get to the technical reasons in a minute, but based on observation, it appears that it’s only checking the `name` field of our class for equality, and stopping there.

At this point all we know is the language is incorrectly interpreting the idea of equality between two books. It thinks that just because the book names match, that the book objects must be equal.

### Let’s try to fix this

We introduced natrual ordering, but it has clearly affected the definition of equality for books. Therefore let’s try to rectify this situation be defining an `equals` method so there’s no confusion:

```groovy
@CompileStatic
class Book implements Comparable<Book> {
    String name
    String author
    Float price

    @Override
    int compareTo(Book other) {
        // Sort by name ascending
        return this.name <=> other.name
    }

    @Override
    boolean equals(Book other) {
        this.name == other.name &&
            this.author == other.author &&
            this.price == other.price
    }
}

```

Now we’re expressing the idea that a book can only equal another book if the name, author, and price match.

Let’s test this:

```groovy
def book1 = new Book(name: 'The Gadfly', author: "Ethel Voynich", price: 19.95)
def book2 = new Book(name: 'The Gadfly', author: "Johnny Copycat", price: 9.95)

assert !book1.equals(book2) // This passes, we should be ok
assert book1 != book2 // THIS STILL FAILS!!! WHY IS THIS?!

```

### Why didn’t defining `equals` fix `==`?

After all, doesn’t the `==` operator just delegate to the `equals` method in groovy?

Let’s look closer at the documentation: [http://docs.groovy-lang.org/latest/html/documentation/index.html#\_behaviour\_of\_code\_code](http://docs.groovy-lang.org/latest/html/documentation/index.html#_behaviour_of_code_code)

> In Java == means equality of primitive types or identity for objects. In Groovy == translates to a.compareTo(b)==0, if they are Comparable, and a.equals(b) otherwise. To check for identity, there is is. E.g. a.is(b).

Oof. This really hinders our ability to define a natural order. Even if we define `equals`, it will be ignored if our class implements `Comparable`.

### What is the solution?

Let’s look at the Java recommendations for the `Comparable` interface: [https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html](https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html)

> It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave “strangely” when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.

In groovy, if we follow this recommendataion everything works fine. So let’s make `compareTo` consistent with `equals`:

```groovy
@CompileStatic
class Book implements Comparable<Book> {
    String name
    String author
    Float price

    @Override
    int compareTo(Book other) {
        // Sort by name ascending
        int cmp = this.name <=> other.name

        // Then sort by author, then price
        if (!cmp) {
            cmp = this.author <=> other.author
        }

        if (!cmp) {
            cmp = this.price <=> other.price
        }

        return cmp
    }

    @Override
    boolean equals(Book other) {
        this.name == other.name &&
            this.author == other.author &&
            this.price == other.price
    }
}

```

Now when we call `compareTo` on another instance it will only return `0` when the instances are equal.

```groovy
def book1 = new Book(name: 'The Gadfly', author: "Ethel Voynich", price: 19.95)
def book2 = new Book(name: 'The Gadfly', author: "Johnny Copycat", price: 9.95)

assert book1.compareTo(book2) != 0 // passes
assert !book1.equals(book2) // passes
assert book1 != book2 // passes

```

### That’s too much code. Is there a better way?

We can use Groovy’s built-in transforms to achieve the same result with far less code:

```groovy
@CompileStatic
@Sortable(includes = ['name', 'author', 'price'])
@EqualsAndHashCode // Not required, but here for completeness
class Book {
    String name
    String author
    Float price
}

```

The `@Sortable` annotation implements the `Comparable` interface for us. Furthermore, if the `includes` argument is used it will check the fields in the order they are listed.

I’ve listed the `@EqualsAndHashCode` transformation here as well to generate those methods, simply because we had them in the above snippets. They aren’t strictly necessary to make our test pass since Groovy will only look at `compareTo`, but I think that documenting that _we_ are defining equality _somewhere_ in this class is important, and this is a way to achieve that.

Just a word of caution: be sure to include all fields in the `@Sortable` includes list. Otherwise the natural order of the class becomes inconsistent with the equality.

### What if I want to use a custom `equals` method?

I can’t think of a reason you would need this, but it _should_ be able to do something like this:

```groovy
    @Override
    boolean equals(Book other) {
        // custom definition of equality
        // ...
        return equalsObject(other)
    }

    @Override
    int compareTo(Book other) {
        if (this.equals(other)) {
            return 0

        } else {
            // Normal sort logic from above:

            // Sort by name ascending
            int cmp = this.name <=> other.name

            // Then sort by author, then price
            if (!cmp) {
                cmp = this.author <=> other.author
            }

            if (!cmp) {
                cmp = this.price <=> other.price
            }

            return cmp
        }
    }

```

Remember, the key is to ensure that the natural order is consistent with equality.

I hope this helps someone out!