Как отсортировать список по разным параметрам с разным временем
У меня есть класс с именем Person
с несколькими свойствами, например:
public class Person {
private int id;
private String name, address;
// Many more properties.
}
Много Person
-объектов хранятся в ArrayList<Person>
. Я хочу отсортировать этот список по нескольким параметрам сортировки и время от времени отличаться. Например, я мог бы однажды отсортировать по name
по возрастанию, а затем address
по убыванию, а в другое время просто по id
по убыванию.
И я не хочу создавать свои собственные методы сортировки (т.е. я хочу использовать Collections.sort(personList, someComparator)
. Какое самое элегантное решение для этого?
Ответы
Ответ 1
Я думаю, что ваш подход enum в основном звучит, но операторам switch действительно нужен более объектно-ориентированный подход. Рассмотрим:
enum PersonComparator implements Comparator<Person> {
ID_SORT {
public int compare(Person o1, Person o2) {
return Integer.valueOf(o1.getId()).compareTo(o2.getId());
}},
NAME_SORT {
public int compare(Person o1, Person o2) {
return o1.getFullName().compareTo(o2.getFullName());
}};
public static Comparator<Person> decending(final Comparator<Person> other) {
return new Comparator<Person>() {
public int compare(Person o1, Person o2) {
return -1 * other.compare(o1, o2);
}
};
}
public static Comparator<Person> getComparator(final PersonComparator... multipleOptions) {
return new Comparator<Person>() {
public int compare(Person o1, Person o2) {
for (PersonComparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}
Пример использования (со статическим импортом).
public static void main(String[] args) {
List<Person> list = null;
Collections.sort(list, decending(getComparator(NAME_SORT, ID_SORT)));
}
Ответ 2
Вы можете создавать компараторы для каждого из свойств, которые вы, возможно, захотите отсортировать, а затем попробуйте "цепочка компаратора":-) следующим образом:
public class ChainedComparator<T> implements Comparator<T> {
private List<Comparator<T>> simpleComparators;
public ChainedComparator(Comparator<T>... simpleComparators) {
this.simpleComparators = Arrays.asList(simpleComparators);
}
public int compare(T o1, T o2) {
for (Comparator<T> comparator : simpleComparators) {
int result = comparator.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
}
Ответ 3
Один из способов - создать Comparator
, который принимает в качестве аргумента список свойств для сортировки, как показано в этом примере.
public class Person {
private int id;
private String name, address;
public static Comparator<Person> getComparator(SortParameter... sortParameters) {
return new PersonComparator(sortParameters);
}
public enum SortParameter {
ID_ASCENDING, ID_DESCENDING, NAME_ASCENDING,
NAME_DESCENDING, ADDRESS_ASCENDING, ADDRESS_DESCENDING
}
private static class PersonComparator implements Comparator<Person> {
private SortParameter[] parameters;
private PersonComparator(SortParameter[] parameters) {
this.parameters = parameters;
}
public int compare(Person o1, Person o2) {
int comparison;
for (SortParameter parameter : parameters) {
switch (parameter) {
case ID_ASCENDING:
comparison = o1.id - o2.id;
if (comparison != 0) return comparison;
break;
case ID_DESCENDING:
comparison = o2.id - o1.id;
if (comparison != 0) return comparison;
break;
case NAME_ASCENDING:
comparison = o1.name.compareTo(o2.name);
if (comparison != 0) return comparison;
break;
case NAME_DESCENDING:
comparison = o2.name.compareTo(o1.name);
if (comparison != 0) return comparison;
break;
case ADDRESS_ASCENDING:
comparison = o1.address.compareTo(o2.address);
if (comparison != 0) return comparison;
break;
case ADDRESS_DESCENDING:
comparison = o2.address.compareTo(o1.address);
if (comparison != 0) return comparison;
break;
}
}
return 0;
}
}
}
Затем он может быть использован в коде, например:
cp = Person.getComparator(Person.SortParameter.ADDRESS_ASCENDING,
Person.SortParameter.NAME_DESCENDING);
Collections.sort(personList, cp);
Ответ 4
Один из подходов - составить Comparator
s. Это может быть метод библиотеки (я уверен, что он существует где-то там).
public static <T> Comparator<T> compose(
final Comparator<? super T> primary,
final Comparator<? super T> secondary
) {
return new Comparator<T>() {
public int compare(T a, T b) {
int result = primary.compare(a, b);
return result==0 ? secondary.compare(a, b) : result;
}
[...]
};
}
Использование:
Collections.sort(people, compose(nameComparator, addressComparator));
В качестве альтернативы обратите внимание, что Collections.sort
является устойчивым. Если производительность не имеет решающего значения, вы сортируете вторичный порядок до первичного.
Collections.sort(people, addressComparator);
Collections.sort(people, nameComparator);
Ответ 5
Компараторы позволяют делать это очень легко и естественно. Вы можете создавать отдельные экземпляры компараторов либо в своем классе Person, либо в классе Service, связанном с вашими потребностями.
Примеры, используя анонимные внутренние классы:
public static final Comparator<Person> NAME_ASC_ADRESS_DESC
= new Comparator<Person>() {
public int compare(Person p1, Person p2) {
int nameOrder = p1.getName().compareTo(p2.getName);
if(nameOrder != 0) {
return nameOrder;
}
return -1 * p1.getAdress().comparedTo(p2.getAdress());
// I use explicit -1 to be clear that the order is reversed
}
};
public static final Comparator<Person> ID_DESC
= new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return -1 * p1.getId().comparedTo(p2.getId());
// I use explicit -1 to be clear that the order is reversed
}
};
// and other comparator instances as needed...
Если у вас их много, вы также можете структурировать свой код компараторов любым удобным вам способом. Например, вы можете:
- наследуется от другого компаратора,
- имеет CompositeComparator, который объединяет некоторые существующие компараторы.
- имеет NullComparator, который обрабатывает нулевые случаи, затем делегирует другому компаратору
- и т.д...
Ответ 6
Я думаю, что объединение сортировщиков с классом Person, как и в вашем ответе, не является хорошей идеей, поскольку оно сочетает сравнение (обычно с бизнес-движением) и объект модели, чтобы они были близки друг к другу.
Каждый раз, когда вы хотите изменить/добавить что-то сортировщик, вам нужно прикоснуться к классу person, что обычно не то, что вы не хотите делать.
Использование службы или что-то подобное, предоставляющее экземпляры Comparator, например, предложенные KLE, звучит более гибко и расширяемо.
Ответ 7
Мой подход основан на Yishai's. Основной недостаток заключается в том, что нет способа отсортировать первое восхождение по атрибуту и после этого отказаться от другого. Это невозможно сделать с перечислениями. Для этого я использовал классы. Поскольку SortOrder сильно зависит от типа, который я предпочитал реализовать как внутренний класс человека.
Класс 'Person' с внутренним классом SortOrder:
import java.util.Comparator;
public class Person {
private int id;
private String firstName;
private String secondName;
public Person(int id, String firstName, String secondName) {
this.id = id;
this.firstName = firstName;
this.secondName = secondName;
}
public abstract static class SortOrder implements Comparator<Person> {
public static SortOrder PERSON_ID = new SortOrder() {
public int compare(Person p1, Person p2) {
return Integer.valueOf(p1.getId()).compareTo(p2.getId());
}
};
public static SortOrder PERSON_FIRST_NAME = new SortOrder() {
public int compare(Person p1, Person p2) {
return p1.getFirstName().compareTo(p2.getFirstName());
}
};
public static SortOrder PERSON_SECOND_NAME = new SortOrder() {
public int compare(Person p1, Person p2) {
return p1.getSecondName().compareTo(p2.getSecondName());
}
};
public static SortOrder invertOrder(final SortOrder toInvert) {
return new SortOrder() {
public int compare(Person p1, Person p2) {
return -1 * toInvert.compare(p1, p2);
}
};
}
public static Comparator<Person> combineSortOrders(final SortOrder... multipleSortOrders) {
return new Comparator<Person>() {
public int compare(Person p1, Person p2) {
for (SortOrder personComparator: multipleSortOrders) {
int result = personComparator.compare(p1, p2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getSecondName() {
return secondName;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("Person with id: ");
result.append(id);
result.append(" and firstName: ");
result.append(firstName);
result.append(" and secondName: ");
result.append(secondName);
result.append(".");
return result.toString();
}
}
Пример использования класса Person и его SortOrder:
import static multiplesortorder.Person.SortOrder.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import multiplesortorder.Person;
public class Application {
public static void main(String[] args) {
List<Person> listPersons = new ArrayList<Person>(Arrays.asList(
new Person(0, "...", "..."),
new Person(1, "...", "...")
));
Collections.sort(listPersons, combineSortOrders(PERSON_FIRST_NAME, invertOrder(PERSON_ID)));
for (Person p: listPersons) {
System.out.println(p.toString());
}
}
}
oRUMOo
Ответ 8
Недавно я написал Comparator для сортировки нескольких полей в строковой записи с разделителями. Он позволяет определить разделитель, структуру записи и правила сортировки (некоторые из которых относятся к конкретному типу). Вы можете использовать это, преобразовывая запись Person в строку с разделителями.
Необходимая информация высевается самому компаратору либо программно, либо через файл XML.
XML проверяется встроенным XSD файлом пакета. Например, ниже представлен макет записи с разделителями табуляции с четырьмя полями (два из которых сортируются):
<?xml version="1.0" encoding="ISO-8859-1"?>
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<delimiter>	</delimiter>
<column xsi:type="Decimal">
<name>Column One</name>
</column>
<column xsi:type="Integer">
<name>Column Two</name>
</column>
<column xsi:type="String">
<name>Column Three</name>
<sortOrder>2</sortOrder>
<trim>true</trim>
<caseSensitive>false</caseSensitive>
<stripAccents>true</stripAccents>
</column>
<column xsi:type="DateTime">
<name>Column Four</name>
<sortOrder>1</sortOrder>
<ascending>true</ascending>
<nullLowSortOrder>true</nullLowSortOrder>
<trim>true</trim>
<pattern>yyyy-MM-dd</pattern>
</column>
</row>
Затем вы использовали бы это в java следующим образом:
Comparator<String> comparator = new RowComparator(
new XMLStructureReader(new File("layout.xml")));
Библиотеку можно найти здесь:
http://sourceforge.net/projects/multicolumnrowcomparator/
Ответ 9
Предположим, что существует класс Coordinate
, и нужно сортировать его в обоих направлениях в соответствии с X-координатой и Y-координатой. Для этого нужны два компаратора. Ниже приведен образец
class Coordinate
{
int x,y;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
static Comparator<Coordinate> getCoordinateXComparator() {
return new Comparator<Coordinate>() {
@Override
public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
if(Coordinate1.x < Coordinate2.x)
return 1;
else
return 0;
}
// compare using Coordinate x
};
}
static Comparator<Coordinate> getCoordinateYComparator() {
return new Comparator<Coordinate>() {
@Override
public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
if(Coordinate1.y < Coordinate2.y)
return 1;
else
return 0;
}
// compare using Coordinate y
};
}
}