Classificando objects Java usando várias chaves

Eu tenho uma coleção de objects Duck e gostaria de classificá-los usando várias chaves .

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

por exemplo. Eu quero classificá-los principalmente por seus pesos e, secundariamente, por sua idade . Se dois patos têm exatamente o mesmo peso e exatamente a mesma idade, então vamos diferenciá-los usando seus nomes como uma chave terciária . Eu poderia fazer algo assim:

 Collections.sort(ducks, new Comparator(){ @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); } }); 

Bem, eu faço isso com bastante frequência, mas esta solução não cheira bem. Não escala bem, e é fácil errar. Certamente deve haver uma maneira melhor de classificar os patos usando várias chaves! Alguém sabe de uma solução melhor?

EDIT removeu desnecessárias else ramificações

Goiaba é mais elegante:

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

O commons-lang do Apache tem uma construção similar, o CompareToBuilder .

 List ducks = new ArrayList(); Collections.sort(ducks, new Comparator() { @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(); } }); 

Em primeiro lugar, sua solução não é tão lenta.

Se você realmente quer outro método, então dê a cada pato uma “pontuação” que é essencialmente um único número que é a sum de suas três características, mas com um grande peso (desculpe o trocadilho quase inevitável) por peso, um menor para a idade ; e um muito pequeno para o nome.

Você pode alocar ~ 10 bits para cada característica, então para cada característica você deve estar no intervalo 0..1023 .

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

Isso é provavelmente desnecessário, mas seja o que for 🙂

Solução Java 8:

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

Hooray para lambdas, referências de methods e methods padrão :)! Pena que temos que definir getters, ou usar lambdas explícitos , assim:

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

A inferência de tipos não funciona com lambdas implícitos, portanto, você precisa especificar o tipo de argumento dos dois primeiros lambdas. Mais detalhes nesta resposta por Brian Goetz .

Você pode usar o CompareToBuilder do Apache Commons Lang . (Explica comparável, mas funciona também para o Comparator).

Você pode usar BeanComparators encadeados do 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

Acabei de rewrite seu código sem declarações aninhadas. Você gosta disso agora?

 @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); }