Sorting

Correct sorting is very important. Incorrect sorting often leads to subtle defects or sloppy display results. For some developers, sorting is an after thought. They don't give enough consideration to confirming their sort orders or to their code behind their sorting.

Definitive Sorting

It is best when sorting routines always yield the same result, regardless of of how often the sort is performed on the same data.

Otherwise, repeated sorting may result in different orders. This may manifest when displaying data where two different rows unexpectedly switch order. Take for example a search results page, where the results are updated as the user types. Let's say the user is searching for contacts by last name. They type in Sm and sees the results:

Smith, Joe
Smith, John
Smith, Mary

Then the user types i for Smi but sees the results:

Smith, Mary
Smith, Joe
Smith, John

Notice Smith, Mary unexpectedly jumped to the top. This would be confusing to the user and would give the appearance of glitchy software.

Less experienced developers often construct sort routines that touch 1 data point. That data point may not be unique. In the example above, the developer was only sorting on the last name.

I use an approach where the last data point considered for sorting is against a unique data point. As such, if all previous data points are equal, the last data point will yield a non-equal comparison result, keeping the resulting sort order consistent.

Consider our contacts are modeled in a Person class:

class Person
{
    private PersonID personID;
    private String firstName;
    private String lastName;

    PersonID getPersonID() { return personID; }
    String getFirstName() { return firstName; }
    String getLastName() { return lastName; }
}

The incorrect sort routine, only considering 1 data point, might look like this:

class PersonList extends ArrayList<Person>
{
    void sort()
    {
        sort(Comparator.comparing(Person::getLastName));
    }
}

When the last names are equal, and depending on the first name and PersonID values, the sort above may return varying results over repeated calls.

However, we can achieve a definitive sort order by always including the unique PersonID as the last criteria in our sort:

class PersonList extends ArrayList<Person>
{
    void sort()
    {
        sort(Comparator.comparing(Person::getLastName)
            .thenComparing(Person::getFirstName)
            .thenComparing(Person::getPersonID));
    }
}

Often times, sorting issues are obvious when similar data is viewed in a display. But other times, the sorting maybe very subtle and not noticed on common data sets.

Code Reuse

In Java, given the ease of writing sorting code, developers often sort collections in the particular code where the sorting is needed. This can result in the same sorting code duplicated across the code base. This code is then harder to maintain and may result in issues, such as inconsistent sorting in different places.

I like to encapsulate my sorting routines into my List objects. See my previous post, Types for Modeling Data, about the benefits of List objects.

class PersonList extends ArrayList<Person>
{
    void sort()
    {
        sort(Comparator.comparing(Person::getPersonID));
    }
}

Since your code will already have a PersonList, it's natural to call sort on the instance:

personList.sort();

Then supporting various sort orders is easy:

class PersonList extends ArrayList<Person>
{
    void sortByID()
    {
        sort(Comparator.comparing(Person::getPersonID));
    }

    void sortByFirstName()
    {
        sort(Comparator.comparing(Person::getFirstName)
            .thenComparing(Person::getLastName)
            .thenComparing(Person::getPersonID));
    }

    void sortByLastName()
    {
        sort(Comparator.comparing(Person::getLastName)
            .thenComparing(Person::getFirstName)
            .thenComparing(Person::getPersonID));
    }
}

Extending Comparator

You can take the helper objects even further. Suppose you're building a UI and it needs to allow for user selected sorting. It would be convenient to be able to store the sort order in a variable. The variable then directs the UI to indicate to the user which order is currently selected and the same variable can drive the sort oder:

class PersonComparator implements Comparator<Person>
{
    enum Order
    {
        PersonID(Comparator.comparing(Person::getPersonID)),
        FirstName(Comparator.comparing(Person::getFirstName)),
            .thenComparing(Person::getLastName))
            .thenComparing(Person::getPersonID)),
        LastName(Comparator.comparing(Person::getLastName)),
            .thenComparing(Person::getFirstName))
            .thenComparing(Person::getPersonID));

        private final Comparator<Person> comparator;

        Order(Comparator<Person> comparator)
        {
            this.comparator = comparator;
        }
    };

    Order order = Order.LastName;
    boolean descending;

    public int compare(Person o1, Person o2)
    {
        return descending ? order.comparator.compare(o2, o1)
            : order.comparator.compare(o1, o2);
    }
}

Then making use of a PersonComparator variable for sorting becomes pretty simple:

// default sort
PersonComparator personComparator = new PersonComparator();
personList.sort(personComparator);

// change sort to first name, descending
personComparator.order = PersonComparator.Order.FirstName;
personComparator.descending = true;
personList.sort(personComparator);

// change sort to PersonID
personComparator.order = PersonComparator.Order.PersonID;
personComparator.descending = false;
personList.sort(personComparator);