Как отсортировать список по разным параметрам в разное время
у меня есть класс с именем Person
с несколькими свойствами, например:
public class Person {
private int id;
private String name, address;
// Many more properties.
}
большое Person
-предметы, хранящиеся в ArrayList<Person>
. Я хочу отсортировать этот список по нескольким параметрам сортировки и время от времени отличаться. Например, я мог бы один раз захотеть отсортировать по name
восхождение, а затем address
спуск, а в другой раз просто мимо id
по убыванию.
и я не хочу создавать свои собственные методы сортировки (т. е. я хочу использовать Collections.sort(personList, someComparator)
. Что такое самое элегантное решение, которое этого достигает?
9 ответов:
Я думаю, что ваш подход перечисления в основном звучит, но операторы 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))); }
вы можете создать компараторы для каждого из свойств, которые вы можете отсортировать, а затем попробовать "цепочку компараторов": -) вот так:
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; } }
один из способов-создать
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);
одним из подходов было бы составить
Comparator
ы. Это может быть методом библиотеке (я уверен, что он существует где-то там).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);
компараторы позволяют сделать это очень легко и естественно. Вы можете создать отдельные экземпляры компараторов либо в самом классе 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, который обрабатывает нулевые случаи, а затем делегирует другому компаратору
- etc...
Я думаю, что связывание сортировщиков с классом Person, как и в вашем ответе, не является хорошей идеей, потому что он соединяет сравнение (обычно управляемое бизнесом) и объект модели, чтобы приблизиться друг к другу. Каждый раз, когда вы хотите что-то изменить/добавить сортировщик, вам нужно коснуться класса person, что обычно вы не хотите делать.
использование сервиса или чего-то подобного, который предоставляет экземпляры компаратора, такие как KLE proposed, звучит более гибко и раздвижной.
мой подход строится на Yishai В. основной пробел заключается в том, что нет никакого способа сортировки сначала по возрастанию для атрибута, а после этого для другого. Это не может быть сделано с перечислениями. Для этого я использовал классы. Потому что сортировщик сильно зависит от типа, который я предпочел реализовать как внутренний класс человека.
класс '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 и его Порядок сортировки:
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
недавно я написал компаратор для сортировки нескольких полей в записи строки с разделителями. Он позволяет определить разделитель, структуру записи и правила сортировки (некоторые из которых зависят от типа). Вы можете использовать это путем преобразования записи человека в строку с разделителями.
необходимая информация передается в сам компаратор программно или через 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")));
библиотека находится здесь:
предположим, что класс
Coordinate
есть, и нужно сортировать его в обоих направлениях в соответствии с координатами X и y. Для этого необходимы два компаратора differnet. Ниже приведен пример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 }; } }