Ways to sort lists of objects in Java based on multiple fields
PDFAs 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:
- Pizza’s 34cm:
- Anchovy (34cm, tomato, cheese, Anchovies)
- Prosciutto (34cm, tomato, cheese and ham)
- Chicken Special (34cm, tomato, cheese, chicken and turkey pieces)
- Vulcano (34cm, tomato, cheese, mushrooms and ham)
- Peperone (34cm, tomato, cheese, mushrooms, ham, capsicum, chili peppers and onions)
- Pizza’s 30cm:
- Anchovy (30cm, tomato, cheese, Anchovies)
- Prosciutto (30cm, tomato, cheese and ham)
- Chicken Special (30cm, tomato, cheese, chicken and turkey pieces)
- Vulcano (30cm, tomato, cheese, mushrooms and ham)
- Peperone (30cm, tomato, cheese, mushrooms, ham, capsicum, chili peppers and onions)
- Pizza’s 26cm:
- Anchovy (26cm, tomato, cheese, Anchovies)
- Prosciutto (26cm, tomato, cheese and ham)
- Chicken Special (26cm, tomato, cheese, chicken and turkey pieces)
- Vulcano (26cm, tomato, cheese, mushrooms and ham)
- Peperone (26cm, tomato, cheese, mushrooms, ham, capsicum, chili peppers and onions)
You can find working code on this gist.
Contents
Messy and convoluted: Sorting by hand
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Collections.sort(pizzas, new Comparator<Pizza>() { @Override public int compare(Pizza p1, Pizza p2) { int sizeCmp = p1.size.compareTo(p2.size); if (sizeCmp != 0) { return sizeCmp; } int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings); if (nrOfToppingsCmp != 0) { return nrOfToppingsCmp; } return p1.name.compareTo(p2.name); } }); |
The reflective way: Sorting with BeanComparator
1 2 3 4 5 6 | ComparatorChain chain = new ComparatorChain(Arrays.asList( new BeanComparator("size"), new BeanComparator("nrOfToppings"), new BeanComparator("name"))); Collections.sort(pizzas, chain); |
Getting there: Sorting with Google Guava’s ComparisonChain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Collections.sort(pizzas, new Comparator<Pizza>() { @Override public int compare(Pizza p1, Pizza p2) { return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result(); // or in case the fields can be null: /* return ComparisonChain.start() .compare(p1.size, p2.size, Ordering.natural().nullsLast()) .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) .compare(p1.name, p2.name, Ordering.natural().nullsLast()) .result(); */ } }); |
Sorting with Apache Commons CompareToBuilder
1 2 3 4 5 6 | Collections.sort(pizzas, new Comparator<Pizza>() { @Override public int compare(Pizza p1, Pizza p2) { return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison(); } }); |
Ultimately it comes down to flavor and the need for flexibility (Guava’s ComparisonChain) vs. concise code (Apache’s CompareToBuilder).
Tags: java • programming • sorting
Fred
Thanks!
jojo
Apache Commons CompareToBuilder => Nice!
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
Vic
good addition with nulls cases
https://marcin-chwedczuk.github.io/comparing-with-nullsFirst-and-nullsLast