¿Cómo ordeno una lista por diferentes parámetros en diferentes tiempos

Tengo una clase llamada Person con múltiples propiedades, por ejemplo:

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

Una gran cantidad de objetos Person se almacenan en una ArrayList . Quiero ordenar esta lista por múltiples parámetros de clasificación, y diferente de vez en cuando. Por ejemplo, puede que una vez quiera ordenar por name ascendente y luego address descendente, y otra vez solo por id descendente.

Y no quiero crear mis propios métodos de clasificación (es decir, quiero usar Collections.sort(personList, someComparator) . ¿Cuál es la solución más elegante que logra esto?

Creo que su enfoque enum es básicamente sólido, pero las instrucciones de cambio realmente necesitan un enfoque más orientado a objetos. 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; } }; } } 

Un ejemplo de uso (con una importación estática).

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

Puede crear comparadores para cada una de las propiedades que desee ordenar y luego probar el “encadenamiento de comparación” 🙂 de esta manera:

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

Una forma es crear un Comparator que tome como argumentos una lista de propiedades para ordenar, como muestra este ejemplo.

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

Se puede usar en código, por ejemplo, así:

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

Un enfoque sería componer Comparator s. Este podría ser un método de biblioteca (estoy seguro de que existe en algún lugar).

 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; } [...] }; } 

Utilizar:

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

Alternativamente, tenga en cuenta que Collections.sort es un tipo estable. Si el rendimiento no es absolutamente crucial, clasifique la orden secundaria antes de la primaria.

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

Comparadores le permite hacer eso muy fácil y naturalmente. Puede crear instancias únicas de comparadores, ya sea en su propia clase Person o en una clase de servicio asociada a su necesidad.
Ejemplos, usando clases 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... 

Si tiene muchos, también puede estructurar el código de sus comparadores de la forma que desee. Por ejemplo, podrías:

  • heredar de otro comparador,
  • tener un CompositeComparator que agrega algunos comparadores existentes
  • tener un NullComparator que maneja casos nulos, luego delega en otro comparador
  • etc …

Creo que acoplar los clasificadores a la clase Person, como en su respuesta, no es una buena idea, porque combina la comparación (generalmente impulsada por el negocio) y el objeto modelo para acercarse el uno al otro. Cada vez que desee cambiar / agregar algo al clasificador, debe tocar la clase de persona, que generalmente es algo que no desea hacer.

El uso de un servicio o algo similar, que proporciona instancias de Comparator, como KLE propuesto, suena mucho más flexible y extensible.

Mi enfoque está basado en el de Yishai. La brecha principal es que no hay forma de ordenar primero el ascenso de un atributo y luego el de otro. Esto no se puede hacer con enumeraciones. Para eso usé clases. Debido a que SortOrder depende fuertemente del tipo I preferido para implementarlo como una clase interna de persona.

La clase ‘Persona’ con clase 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(); } } 

Un ejemplo para usar la clase Person y su 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

Hace poco escribí un Comparador para ordenar múltiples campos dentro de un registro de Cadena delimitado. Le permite definir el delimitador, la estructura de registro y las reglas de clasificación (algunas de las cuales son específicas del tipo). Puede usar esto convirtiendo un registro de Persona en una Cadena delimitada.

La información requerida se asigna al Comparador mismo, ya sea programáticamente o mediante un archivo XML.

XML es validado por un paquete de archivo XSD incrustado. Por ejemplo, a continuación se muestra un diseño de registro delimitado por tabulaciones con cuatro campos (dos de los cuales son ordenables):

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

Entonces usarías esto en Java así:

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

La biblioteca se puede encontrar aquí:

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

Supongamos que hay una Coordinate clase y hay que ordenarla de ambas maneras según la coordenada X y la coordenada Y. Se necesitan dos comparadores differnet para ello. A continuación está la muestra

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