Sort Java objects using multiple keys

I have a collection of Duck objects, and I would like to sort them with a few keys .

class Duck { DuckAge age; //implements Comparable DuckWeight weight; //implements Comparable String name; } List<Duck> ducks = Pond.getDucks(); 

eg. I want to sort them first by their weights and a second time by age . If two ducks have the same weight and exact age, then let them differentiate them using their names as a tertiary key . I could do something like this:

 Collections.sort(ducks, new Comparator<Duck>(){ @Override public int compare(Duck d1, Duck d2){ int weightCmp = d1.weight.compareTo(d2.weight); if (weightCmp != 0) { return weightCmp; } int ageCmp = d1.age.compareTo(d2.age); if (ageCmp != 0) { return ageCmp; } return d1.name.compareTo(d2.name); } }); 

Well, I do this quite often, but this solution doesn't smell right. It does not scale well and is easy to spoil. Of course, there should be a better way to sort ducks with just a few keys! Does anyone know of a better solution?

EDIT removed unnecessary else branches

+47
java sorting comparator comparable
Nov 07 '11 at 12:22
source share
7 answers

Java 8 solution:

 Comparator<Duck> cmp = Comparator.comparing(Duck::getWeight) .thenComparing(Duck::getAge) .thenComparing(Duck::getName); 

Hooray for lambda, links to default methods and methods :)! Too bad we have to define getters or use explicit lambdas, for example:

 Comparator<Duck> cmp = Comparator .comparing((Duck duck)-> duck.weight) .thenComparing((Duck duck)-> duck.age) .thenComparing(duck-> duck.name); 

Type input will not work with implicit lambdas, so you need to specify the argument type of the first two lambdas. See more in this answer by Brian Goetz .

+8
Jul 16 '14 at 20:49
source share

Guava is more elegant:

 return ComparisonChain.start() .compare(d1.weight, d2.weight) .compare(d1.age, d2.age) .compare(d1.name, d2.name) .result(); 

Apache commons-lang has a similar construction, CompareToBuilder .

+48
Nov 07 '11 at 12:29
source share
 List<Duck> ducks = new ArrayList<Duck>(); Collections.sort(ducks, new Comparator<Duck>() { @Override public int compare(Duck o1, Duck o2) { return new org.apache.commons.lang.builder.CompareToBuilder(). append(o1.weight, o2.weight). append(o1.age, o2.age). append(o1.name, o2.name). toComparison(); } }); 
+20
Nov 07 '11 at 12:30
source share

Firstly, your decision is not so slow.

If you really need a different method, give each duck a โ€œratingโ€, which is essentially a singular, which is the sum of their three characteristics, but with enormous weight (excuse the almost inevitable pun) for a weight less than one for age; and a very small name.

You can allocate ~ 10 bits for each characteristic, so for each attribute you should be in the range 0..1023 .

 score = ( (weight << 10) + age) << 10 + name; 

This is probably completely unnecessary, but anything :)

+14
Nov 07 '11 at 12:30
source share

You can use CompareToBuilder from Apache Commons Lang . (This explains the comparable, but works for the comparator as well).

+6
Nov 07 '11 at 12:26
source share

You can use the BeanComparators chain from Commons BeanUtils:

 Comparator comparator = new BeanComparator("weight", new BeanComparator("age")); 

http://commons.apache.org/beanutils/v1.8.3/apidocs/org/apache/commons/beanutils/BeanComparator.html

+4
Nov 07 '11 at 12:28
source share

I just rewrote your code without nested instructions. Do you like it now?

 @Override public int compare(Duck d1, Duck d2){ int weightCmp = d1.weight.compareTo(d2.weight); if (weightCmp != 0) { return weightCmp; } int ageCmp = d1.age.compareTo(d2.age); if (ageCmp != 0) { return ageCmp; } return d1.name.compareTo(d2.age); } 
+4
Nov 07 '11 at 12:29
source share



All Articles