Como faço para classificar uma lista por diferentes parâmetros em diferentes horários

Eu tenho uma class chamada Person com várias propriedades, por exemplo:

 public class Person { private int id; private String name, address; // Many more properties. } 

Muitos objects Person são armazenados em um ArrayList . Eu quero classificar essa lista por vários parâmetros de sorting e diferentes ao longo do tempo. Por exemplo, eu poderia uma vez querer classificar por name ascendente e, em seguida, address descendente, e outra vez apenas por id decrescente.

E eu não quero criar meus próprios methods de sorting (ou seja, eu quero usar Collections.sort(personList, someComparator) . Qual é a solução mais elegante que alcança isso?

Eu acho que sua abordagem enum é basicamente som, mas as instruções switch realmente precisam de uma abordagem mais orientada a object. Considerar:

 enum PersonComparator implements Comparator { 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 decending(final Comparator other) { return new Comparator() { public int compare(Person o1, Person o2) { return -1 * other.compare(o1, o2); } }; } public static Comparator getComparator(final PersonComparator... multipleOptions) { return new Comparator() { public int compare(Person o1, Person o2) { for (PersonComparator option : multipleOptions) { int result = option.compare(o1, o2); if (result != 0) { return result; } } return 0; } }; } } 

Um exemplo de uso (com uma importação estática).

 public static void main(String[] args) { List list = null; Collections.sort(list, decending(getComparator(NAME_SORT, ID_SORT))); } 

Você pode criar comparadores para cada uma das propriedades que você pode querer ordenar e então tentar “encadeamento de comparadores” 🙂 assim:

 public class ChainedComparator implements Comparator { private List> simpleComparators; public ChainedComparator(Comparator... simpleComparators) { this.simpleComparators = Arrays.asList(simpleComparators); } public int compare(T o1, T o2) { for (Comparator comparator : simpleComparators) { int result = comparator.compare(o1, o2); if (result != 0) { return result; } } return 0; } } 

Uma maneira é criar um Comparator que tome como argumentos uma lista de propriedades para classificar, como mostra este exemplo.

 public class Person { private int id; private String name, address; public static Comparator 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 { 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; } } } 

Ele pode então ser usado no código por exemplo como este:

 cp = Person.getComparator(Person.SortParameter.ADDRESS_ASCENDING, Person.SortParameter.NAME_DESCENDING); Collections.sort(personList, cp); 

Uma abordagem seria compor os Comparator . Este poderia ser um método de biblioteca (tenho certeza que existe em algum lugar por aí).

 public static  Comparator compose( final Comparator primary, final Comparator secondary ) { return new Comparator() { public int compare(T a, T b) { int result = primary.compare(a, b); return result==0 ? secondary.compare(a, b) : result; } [...] }; } 

Usar:

 Collections.sort(people, compose(nameComparator, addressComparator)); 

Como alternativa, observe que Collections.sort é uma sorting estável. Se o desempenho não for absolutamente crucial, você classificará a ordem secundária antes da primária.

 Collections.sort(people, addressComparator); Collections.sort(people, nameComparator); 

Comparadores permite que você faça isso com muita facilidade e naturalidade. Você pode criar instâncias únicas de comparadores, seja em sua própria class Person ou em uma class Service associada à sua necessidade.
Exemplos, usando classs internas anônimas:

  public static final Comparator NAME_ASC_ADRESS_DESC = new Comparator() { 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 ID_DESC = new Comparator() { 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... 

Se você tiver muitos, você também pode estruturar seu código de comparação da maneira que preferir. Por exemplo, você poderia:

  • herdar de outro comparador,
  • tem um CompositeComparator que agrega alguns comparadores existentes
  • tem um NullComparator que lida com casos nulos e, em seguida, delega para outro comparador
  • etc …

Acho que acoplar os classificadores à class Person, como em sua resposta, não é uma boa ideia, porque une a comparação (geralmente orientada a negócios) e o object de modelo para fechar um ao outro. Cada vez que você quiser alterar / adicionar algo ao classificador, será necessário tocar na class de pessoa, que geralmente é algo que você não deseja fazer.

Usando um Serviço ou algo semelhante, que fornece instâncias do Comparador, como o KLE propôs, soa muito mais flexível e extensível.

Minha abordagem é baseada no de Yishai. A principal lacuna é que não há como classificar primeiro para um atributo e depois para outro. Isso não pode ser feito com enumerações. Para isso eu usei classs. Como o SortOrder depende fortemente do tipo, eu preferi implementá-lo como uma class interna de pessoa.

A class ‘Person’ com a class interna ‘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 { 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 combineSortOrders(final SortOrder... multipleSortOrders) { return new Comparator() { 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(); } } 

Um exemplo para usar a class Person e seu 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 listPersons = new ArrayList(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

Recentemente, escrevi um Comparador para classificar vários campos em um registro String delimitado. Ele permite que você defina o delimitador, a estrutura do registro e as regras de sorting (algumas das quais são específicas do tipo). Você pode usar isso convertendo um registro Person em uma String delimitada.

As informações necessárias são propagadas para o próprio Comparador, seja programaticamente ou por meio de um arquivo XML.

O XML é validado por um arquivo XSD incorporado ao pacote. Por exemplo, abaixo está um layout de registro delimitado por tabulações com quatro campos (dois dos quais são classificáveis):

   	  Column One   Column Two   Column Three 2 true false true   Column Four 1 true true true yyyy-MM-dd   

Você usaria isso em java assim:

 Comparator comparator = new RowComparator( new XMLStructureReader(new File("layout.xml"))); 

Biblioteca pode ser encontrada aqui:

http://sourceforge.net/projects/multicolumnrowcomparator/

Suponha que uma class Coordinate esteja lá e tenha que classificá-la nos dois sentidos de acordo com a coordenada X e a coordenada Y. Dois diferentes comparadores são necessários para isso. Abaixo está a amostra

 class Coordinate { int x,y; public Coordinate(int x, int y) { this.x = x; this.y = y; } static Comparator getCoordinateXComparator() { return new Comparator() { @Override public int compare(Coordinate Coordinate1, Coordinate Coordinate2) { if(Coordinate1.x < Coordinate2.x) return 1; else return 0; } // compare using Coordinate x }; } static Comparator getCoordinateYComparator() { return new Comparator() { @Override public int compare(Coordinate Coordinate1, Coordinate Coordinate2) { if(Coordinate1.y < Coordinate2.y) return 1; else return 0; } // compare using Coordinate y }; } }