Сортировка объектов Java с использованием нескольких ключей
У меня есть коллекция объектов 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);
}
});
Хорошо, я делаю это довольно часто, но это решение не пахнет правильно. Он плохо масштабируется, и его легко испортить. Разумеется, должен быть лучший способ сортировки уток с помощью нескольких ключей! Кто-нибудь знает о лучшем решении?
EDIT удалены ненужные ветки else
Ответы
Ответ 1
Решение Java 8:
Comparator<Duck> cmp = Comparator.comparing(Duck::getWeight)
.thenComparing(Duck::getAge)
.thenComparing(Duck::getName);
Ура для лямбда, ссылки на методы и методы по умолчанию:)! Жаль, что мы должны определить геттеры или использовать явные lambdas, например:
Comparator<Duck> cmp = Comparator
.comparing((Duck duck)-> duck.weight)
.thenComparing((Duck duck)-> duck.age)
.thenComparing(duck-> duck.name);
Ввод типа не будет работать с неявными lambdas, поэтому вам нужно указать тип аргумента первых двух lambdas. Подробнее в этом ответе Брайана Гетца.
Ответ 2
Guava является более элегантным:
return ComparisonChain.start()
.compare(d1.weight, d2.weight)
.compare(d1.age, d2.age)
.compare(d1.name, d2.name)
.result();
Apache commons-lang имеет аналогичную конструкцию, CompareToBuilder
.
Ответ 3
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();
}
});
Ответ 4
Во-первых, ваше решение не так медленно.
Если вам действительно нужен другой метод, дайте каждой утке "оценку", которая по сути представляет собой единственное число, которое является суммой их трех характеристик, но с огромным весом (извините почти неизбежный каламбур) за вес, меньшим один для возраста; и очень маленькое имя.
Вы можете выделить ~ 10 бит для каждой характеристики, поэтому для каждого признака вы должны находиться в диапазоне 0..1023
.
score = ( (weight << 10) + age) << 10 + name;
Это, вероятно, совершенно ненужно, но что угодно:)
Ответ 5
Вы можете использовать CompareToBuilder из Apache Commons Lang. (Это объясняет сопоставимые, но работает и для компаратора).
Ответ 6
Вы можете использовать цепочку 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
Ответ 7
Я только что переписал ваш код без вложенных инструкций. Вам сейчас нравится?
@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);
}