Сортировка объектов Java с использованием нескольких ключей - PullRequest
49 голосов
/ 07 ноября 2011

У меня есть коллекция объектов Duck, и я хотел бы отсортировать их, используя несколько ключей .

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

например. Я хочу отсортировать их в основном по весу , а по возрасту . Если две утки имеют одинаковый вес и одинаковый возраст, давайте различать их, используя их имена в качестве третичного ключа . Я мог бы сделать что-то вроде этого:

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

Ну, я делаю это довольно часто, но это решение не пахнет неправильно. Это плохо масштабируется, и это легко испортить. Наверняка должен быть лучший способ сортировки уток по нескольким ключам! Кто-нибудь знает лучшее решение?

РЕДАКТИРОВАТЬ удалены ненужные else ветви

Ответы [ 7 ]

49 голосов
/ 07 ноября 2011

Гуава более элегантно:

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

Apache commons-lang имеет аналогичную конструкцию, CompareToBuilder.

20 голосов
/ 07 ноября 2011
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();
  }
});
14 голосов
/ 07 ноября 2011

Во-первых, ваше решение не , что медленно.

Если вы действительно хотите другой метод, тогда дайте каждой утке «оценку», которая, по сути, представляет собой одно число, которое является суммойиз их трех характеристик, но с огромным весом (извините за почти неизбежный каламбур) для веса, меньший для возраста;и очень маленький для имени.

Вы можете выделить ~ 10 бит для каждой характеристики, поэтому для каждой характеристики вы должны находиться в диапазоне 0..1023.

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

Этовероятно, совершенно не нужно, но что угодно:)

10 голосов
/ 17 июля 2014

Решение Java 8:

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

Ура для лямбд, ссылок на методы и методов по умолчанию :)!Жаль, что мы должны определить методы получения или использовать явные лямбда-выражения , например, так:

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

Вывод типа не будет работать с неявными лямбда-выражениями, поэтому вы должны указать тип аргумента:первые две лямбды.Подробнее в этот ответ Брайана Гетца .

6 голосов
/ 07 ноября 2011

Вы можете использовать CompareToBuilder из Apache Commons Lang . (Это объясняет сопоставимость, но работает и для Comparator).

4 голосов
/ 07 ноября 2011

Я только что переписал ваш код без вложенных операторов else. Тебе нравится сейчас?

@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 голосов
/ 07 ноября 2011

Вы можете использовать цепочку BeanComparators от 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

...