Ways to sort lists of objects in Java based on multiple fields

PDF

As I was again trying to remember how to sort a list of objects in Java bases on multiple keys, I had the luminous idea to finally write it down.

We have a list of pizza’s, and I want them sorted according to size, number of toppings and furthermore by name. This means that there will be groups ordered by size and within those groups the pizza’s are ordered into groups by number of toppings and in those groups the pizza’s are ordered by name.

We want to end up with a list like this:

  1. Pizza’s 34cm:
  2. Anchovy (34cm, tomato, cheese, Anchovies)
  3. Prosciutto (34cm, tomato, cheese and ham)
  4. Chicken Special (34cm, tomato, cheese, chicken and turkey pieces)
  5. Vulcano (34cm, tomato, cheese, mushrooms and ham)
  6. Peperone (34cm, tomato, cheese, mushrooms, ham, capsicum, chili peppers and onions)
  7. Pizza’s 30cm:
  8. Anchovy (30cm, tomato, cheese, Anchovies)
  9. Prosciutto (30cm, tomato, cheese and ham)
  10. Chicken Special (30cm, tomato, cheese, chicken and turkey pieces)
  11. Vulcano (30cm, tomato, cheese, mushrooms and ham)
  12. Peperone (30cm, tomato, cheese, mushrooms, ham, capsicum, chili peppers and onions)
  13. Pizza’s 26cm:
  14. Anchovy (26cm, tomato, cheese, Anchovies)
  15. Prosciutto (26cm, tomato, cheese and ham)
  16. Chicken Special (26cm, tomato, cheese, chicken and turkey pieces)
  17. Vulcano (26cm, tomato, cheese, mushrooms and ham)
  18. Peperone (26cm, tomato, cheese, mushrooms, ham, capsicum, chili peppers and onions)

Messy and convoluted: Sorting by hand

This requires a lot of typing, maintenance and is error prone.

The reflective way: Sorting with BeanComparator

Obviously this is is more concise, but even more error prone as you loose your direct reference to the fields by using Strings instead. Now if a field is renamed, the compiler won’t even report a problem. Moreover, because this solution uses reflection, the sorting is much slower.

Getting there: Sorting with Google Guava’s ComparisonChain

This is much better, but requires some boiler plate code for the most common use case: null-values should be values less by default. For null-fields, you have to provide an extra directive to Guava what to do in that case. This is a flexible mechanism if you want to do something specific, but often you want the default case (ie. 1, a, b, z, null).

Sorting with Apache Commons CompareToBuilder

Like Guava’s ComparisonChain, this library class sorts easily on multiple fields, but also defines default behavior for null values (ie. 1, a, b, z, null). However, you can’t specify anything else either, unless you provide your own Comparator.

Ultimately it comes down to flavor and the need for flexibility (Guava’s ComparisonChain) vs. concise code (Apache’s CompareToBuilder).

Tags:
  • Fred

    Thanks!

    Reply

  • jojo

    Apache Commons CompareToBuilder => Nice!

    Reply

  • Vito

    *Sorry fom my ENG.
    Chained comparator is good.
    But works not as you expect if there is a null values in fields.
    Or I can’t understand how it works 🙂

    I got this behavior in this example:

    // Item(id, age, size, cost)
    List items = new ArrayList();
    items.add(new Item(2, 40, 40, 100));
    items.add(new Item(1, 30, 30, 200));
    items.add(new Item(4, 30, 40, null));
    items.add(new Item(3, 20, 20, null));
    items.add(new Item(6, 30, null, null));
    items.add(new Item(5, 20, null, null));
    items.add(new Item(8, null, null, null));
    items.add(new Item(7, null, null, null));

    // comparator for “cost” field
    class CostComparator implements Comparator {
    @Override
    public int compare(Item o1, Item o2) {
    if (o1.cost == null) return 1; // trying nulls to last order
    if (o2.cost == null) return -1;

    return o1.cost – o2.cost;
    }
    }

    compiler says that there are nulls only in first bunch of field only for 1st comparator (in our example it is “cost”)
    and nothing about others, in this case I must check nulls and move them to last position.
    *there are no nullsCheck in other comparators, and compiler says nothing.. hmm..

    I’m trying sort collection in next order: cost -> size -> age -> id
    (nulls must be last)

    Collections.sort(items, new ItemChainedComparator(
    new CostComparator(),
    new SizeComparator(),
    new AgeComparator(),
    new IdComparator()
    ));

    and you see in output that ID 4 3 6 5 8 7 in wrong order.
    properly works only first comparator in chain 🙁

    Output:
    ID:2 AGE:40 SIZE:40 COST:100
    ID:1 AGE:30 SIZE:30 COST:200
    ID:4 AGE:30 SIZE:40 COST:null
    ID:3 AGE:20 SIZE:20 COST:null
    ID:6 AGE:30 SIZE:null COST:null
    ID:5 AGE:20 SIZE:null COST:null
    ID:8 AGE:null SIZE:null COST:null
    ID:7 AGE:null SIZE:null COST:null

    Reply

Leave a Reply