Ordenar ArrayList de objetos personalizados por propiedad


1145

Leí acerca de cómo ordenar ArrayLists usando un comparador, pero en todos los ejemplos que la gente usa, compareToque según algunas investigaciones es un método para Strings.

Quería ordenar una ArrayList de objetos personalizados por una de sus propiedades: un objeto Date ( getStartDay()). Normalmente los comparo, item1.getStartDate().before(item2.getStartDate())así que me preguntaba si podría escribir algo como:

public class CustomComparator {
    public boolean compare(Object object1, Object object2) {
        return object1.getStartDate().before(object2.getStartDate());
    }
}

public class RandomName {
    ...
    Collections.sort(Database.arrayList, new CustomComparator);
    ...
}


2
La respuesta de @Yishai en esta publicación demuestra el uso elegante de enum para la clasificación personalizada y la clasificación agrupada (argumentos múltiples) utilizando el encadenamiento de comparación.
gunalmel

Respuestas:


1538

Desde Dateimplementos Comparable, tiene un compareTométodo al igual que lo Stringhace.

Entonces su costumbre Comparatorpodría verse así:

public class CustomComparator implements Comparator<MyObject> {
    @Override
    public int compare(MyObject o1, MyObject o2) {
        return o1.getStartDate().compareTo(o2.getStartDate());
    }
}

El compare()método debe devolver un int, por lo que no podría devolver directamente un booleancomo estaba planeando.

Su código de clasificación sería casi como lo escribió:

Collections.sort(Database.arrayList, new CustomComparator());

Una forma un poco más corta de escribir todo esto, si no necesita reutilizar su comparador, es escribirlo como una clase anónima en línea:

Collections.sort(Database.arrayList, new Comparator<MyObject>() {
    @Override
    public int compare(MyObject o1, MyObject o2) {
        return o1.getStartDate().compareTo(o2.getStartDate());
    }
});

Ya que

Ahora puede escribir el último ejemplo en una forma más corta utilizando una expresión lambda para Comparator:

Collections.sort(Database.arrayList, 
                        (o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

Y Listtiene un sort(Comparator)método, por lo que puede acortar esto aún más:

Database.arrayList.sort((o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

Este es un idioma tan común que hay un método incorporado para generar un Comparatorpara una clase con una Comparableclave:

Database.arrayList.sort(Comparator.comparing(MyObject::getStartDate));

Todos estos son formas equivalentes.


33
+1 por mencionar que debería regresar inty que es mejor usar Date#compareTo()para esto. Por qué esto no se votó por encima de la otra respuesta está más allá de mí. Este enlace también puede ser útil: Tutorial para ordenar objetos en Sun.com .
BalusC

55
Creo que la mejor respuesta también debería incluir la forma correcta de hacerlo en Java 8. Collections.sort (list, Comparator.comparing (MyObject :: getStartDate)); que se lee mejor y es menos propenso a errores. Es muy fácil escribir return o1.getStartDate (). CompareTo (o1.getStartDate ());
Kuba

2
La clase de
comparación

44
@Kuba mejor aún, úsalo List.sort().
shmosel

3
Esta solución no funciona en la API de Android <24. ¿Saben ustedes una solución para esto?
Jim Clermonts

194

Las clases que tienen un orden de clasificación natural (un Número de clase, como ejemplo) deben implementar la interfaz Comparable, mientras que las clases que no tienen un orden de clasificación natural (una Cátedra de clase, como ejemplo) deben contar con un Comparador (o un Comparador anónimo) clase).

Dos ejemplos:

public class Number implements Comparable<Number> {
    private int value;

    public Number(int value) { this.value = value; }
    public int compareTo(Number anotherInstance) {
        return this.value - anotherInstance.value;
    }
}

public class Chair {
    private int weight;
    private int height;

    public Chair(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }
    /* Omitting getters and setters */
}
class ChairWeightComparator implements Comparator<Chair> {
    public int compare(Chair chair1, Chair chair2) {
        return chair1.getWeight() - chair2.getWeight();
    }
}
class ChairHeightComparator implements Comparator<Chair> {
    public int compare(Chair chair1, Chair chair2) {
        return chair1.getHeight() - chair2.getHeight();
    }
}

Uso:

List<Number> numbers = new ArrayList<Number>();
...
Collections.sort(numbers);

List<Chair> chairs = new ArrayList<Chair>();
// Sort by weight:
Collections.sort(chairs, new ChairWeightComparator());
// Sort by height:
Collections.sort(chairs, new ChairHeightComparator());

// You can also create anonymous comparators;
// Sort by color:
Collections.sort(chairs, new Comparator<Chair>() {
    public int compare(Chair chair1, Chair chair2) {
        ...
    }
});

Lo intenté, pero cuando quiero acceder a la clase comparativa ChairWeightComparator fuera de cualquier otra clase, no obtengo acceso a esta clase (por supuesto que no, ya que no es pública). ¿Necesito crear una nueva clase pública de ChairWeightComparator en un archivo separado? - ¿Soy realmente el primero en probar esto después de 3 años o me perdí algo?
user387184

@ user387184: simplemente hazlo público y colócalo en su propio archivo (preferiblemente también en su propio paquete) y podrás usarlo en todas partes en tu proyecto. ¡No es necesario crear una clase adicional!
Björn

quieres decir crear un nuevo archivo, no una clase y poner el código: "class ChairWeightComparator implementa Comparator <Chair> {...."?
user387184

@ user387184, exactamente, pero con la palabra clave publicen frente de class.
Björn

157

Para ordenar un ArrayList, puede usar el siguiente fragmento de código:

Collections.sort(studList, new Comparator<Student>(){
    public int compare(Student s1, Student s2) {
        return s1.getFirstName().compareToIgnoreCase(s2.getFirstName());
    }
});


Eso ordena pero da doble valor para cada elemento
Sam

44

Sí tu puedes. Hay dos opciones para comparar elementos, la interfaz Comparable y el Comparador interfaz .

Ambas interfaces permiten un comportamiento diferente. Comparable le permite hacer que el objeto actúe como acaba de describir Strings (de hecho, String implementa Comparable). El segundo, Comparador, le permite hacer lo que está pidiendo. Lo harías así:

Collections.sort(myArrayList, new MyComparator());

Eso hará que el método Collections.sort use su comparador para su mecanismo de clasificación. Si los objetos en ArrayList implementan comparables, puede hacer algo como esto:

Collections.sort(myArrayList);

La clase Colecciones contiene varias de estas herramientas útiles y comunes.


42

JAVA 8 expresión lambda

Collections.sort(studList, (Student s1, Student s2) ->{
        return s1.getFirstName().compareToIgnoreCase(s2.getFirstName());
});

O

Comparator<Student> c = (s1, s2) -> s1.firstName.compareTo(s2.firstName);
studList.sort(c)

3
OCollections.sort(studList, Comparator.comparing(Student::getFirstName));
Holger

55
.. ostudList.sort(Comparator.comparing(Student::getFirstName));
Alexis C.

El orden de clasificación siempre será ascendente en su caso. También me he encargado del orden de clasificación en mi ejemplo. Gracias señores.
Clasificador

33

Con Java 8 puede usar una referencia de método para su comparador:

import static java.util.Comparator.comparing;

Collections.sort(list, comparing(MyObject::getStartDate));

@ user387184 desafortunadamente Android no es compatible con Java 8, aunque puede haber una solución alternativa (no lo he probado).
Assylias

14

Como las tecnologías aparecen todos los días, la respuesta cambiará con el tiempo. Eché un vistazo a LambdaJ y parece muy interesante.

Puedes intentar resolver estas tareas con LambdaJ . Puede encontrarlo aquí: http://code.google.com/p/lambdaj/

Aquí tienes un ejemplo:

Ordenar iterativo

List<Person> sortedByAgePersons = new ArrayList<Person>(persons);
Collections.sort(sortedByAgePersons, new Comparator<Person>() {
        public int compare(Person p1, Person p2) {
           return Integer.valueOf(p1.getAge()).compareTo(p2.getAge());
        }
});

Ordenar con lambda

List<Person> sortedByAgePersons = sort(persons, on(Person.class).getAge()); 

Por supuesto, tener este tipo de belleza impacta en el rendimiento (un promedio de 2 veces), pero ¿puedes encontrar un código más legible?


Eso ordena pero da doble valor para cada elemento cómo evitarlo
Sam

@ Sam, no debería ... está funcionando como se esperaba. A menos que esté utilizando una nueva versión con un error, le recomiendo que la publique en el foro. De todos modos, esta respuesta fue publicada antes de Java 8, si la usas, será mucho mejor que usar lambdaj
Federico Piazza

Tuve que eliminar incluso elementos en un bucle foreach, de lo contrario me da el doble de cada contenido.
Sam

13
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;

public class test {

public static class Person {
    public String name;
    public int id;
    public Date hireDate;

    public Person(String iname, int iid, Date ihireDate) {
        name = iname;
        id = iid;
        hireDate = ihireDate;
    }

    public String toString() {
        return name + " " + id + " " + hireDate.toString();
    }

    // Comparator
    public static class CompId implements Comparator<Person> {
        @Override
        public int compare(Person arg0, Person arg1) {
            return arg0.id - arg1.id;
        }
    }

    public static class CompDate implements Comparator<Person> {
        private int mod = 1;
        public CompDate(boolean desc) {
            if (desc) mod =-1;
        }
        @Override
        public int compare(Person arg0, Person arg1) {
            return mod*arg0.hireDate.compareTo(arg1.hireDate);
        }
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    SimpleDateFormat df = new SimpleDateFormat("mm-dd-yyyy");
    ArrayList<Person> people;
    people = new ArrayList<Person>();
    try {
        people.add(new Person("Joe", 92422, df.parse("12-12-2010")));
        people.add(new Person("Joef", 24122, df.parse("1-12-2010")));
        people.add(new Person("Joee", 24922, df.parse("12-2-2010")));
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    Collections.sort(people, new Person.CompId());
    System.out.println("BY ID");
    for (Person p : people) {
        System.out.println(p.toString());
    }

    Collections.sort(people, new Person.CompDate(false));
    System.out.println("BY Date asc");
    for (Person p : people) {
        System.out.println(p.toString());
    }
    Collections.sort(people, new Person.CompDate(true));
    System.out.println("BY Date desc");
    for (Person p : people) {
        System.out.println(p.toString());
    }

}

}

77
Bienvenido a stackoverflow. Esta pregunta fue respondida hace algún tiempo. Antes de resucitar hilos viejos, asegúrese de que su respuesta agregue algo significativo al hilo.
Leigh

1
Por favor agregue una explicación a su respuesta.
Arashsoft

Solo compila y ejecuta. El código es el comentario y la explicación.
CharlesW

9

La mejor manera fácil con JAVA 8 es para ordenar alfabéticamente en inglés

Implementación de clase

public class NewspaperClass implements Comparable<NewspaperClass>{
   public String name;

   @Override
   public int compareTo(NewspaperClass another) {
      return name.compareTo(another.name);
   }
}

Ordenar

  Collections.sort(Your List);

Si desea ordenar por un alfabeto que contiene caracteres que no están en inglés, puede usar Locale ... Debajo del código, use la clasificación de caracteres turcos ...

Implementación de clase

public class NewspaperClass implements Comparator<NewspaperClass> {
   public String name;
   public Boolean isUserNewspaper=false;
   private Collator trCollator = Collator.getInstance(new Locale("tr_TR"));



   @Override
   public int compare(NewspaperClass lhs, NewspaperClass rhs) {
      trCollator.setStrength(Collator.PRIMARY);
      return trCollator.compare(lhs.name,rhs.name);
   }
}

Ordenar

Collections.sort(your array list,new NewspaperClass());

9

Referencia de función y método

El Collections.sortmétodo puede ordenar Listusando unComparator pase. Eso Comparatorse puede implementar utilizando el Comparator.comparingmétodo donde puede pasar una referencia de método según sea necesario Function. Afortunadamente, el código real es mucho más simple y más corto que esta descripción.

Para Java 8:

Collections.sort(list, comparing(ClassName::getName));

o

Collections.sort(list, comparing(ClassName::getName).reversed());

Otra forma es

Collections.sort(list, comparing(ClassName::getName, Comparator.nullsLast(Comparator.naturalOrder())));

1
La llamada requiere API nivel 24
akshay bhange


6

Java 8 Lambda acorta el género.

Collections.sort(stdList, (o1, o2) -> o1.getName().compareTo(o2.getName()));

2
Collections.sort(stdList, Comparator.comparing(SomeClass::getName));
bcsb1001


5

Sí, eso es posible, por ejemplo, en esta respuesta clasifico por la propiedad vde la claseIndexValue

    // Sorting by property v using a custom comparator.
    Arrays.sort( array, new Comparator<IndexValue>(){
        public int compare( IndexValue a, IndexValue b ){
            return a.v - b.v;
        }
    });

Si observa aquí, estoy creando una clase interna anónima (que es el Java para cierres) y la paso directamente al sortmétodo de la claseArrays

Su objeto también puede implementarse Comparable(eso es lo que hace String y la mayoría de las bibliotecas principales en Java), pero eso definiría el "orden de clasificación natural" de la clase en sí mismo, y no le permite conectar otras nuevas.


1
... pero que puedes anular con Comparator:)
BalusC

5

Encontré que la mayoría de estas respuestas, si no todas, se basan en la clase subyacente (Objeto) para implementar una interfaz comparable o tener una interfaz comparable de ayuda.

¡No con mi solución! El siguiente código le permite comparar el campo del objeto al conocer su nombre de cadena. Puede modificarlo fácilmente para no usar el nombre, pero luego debe exponerlo o construir uno de los Objetos con los que desea comparar.

Collections.sort(anArrayListOfSomeObjectPerhapsUsersOrSomething, new ReflectiveComparator(). new ListComparator("name"));

public class ReflectiveComparator {
    public class FieldComparator implements Comparator<Object> {
        private String fieldName;

        public FieldComparator(String fieldName){
            this.fieldName = fieldName;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        public int compare(Object object1, Object object2) {
            try {
                Field field = object1.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);

                Comparable object1FieldValue = (Comparable) field.get(object1);
                Comparable object2FieldValue = (Comparable) field.get(object2);

                return object1FieldValue.compareTo(object2FieldValue);
            }catch (Exception e){}

            return 0;
        }
    }

    public class ListComparator implements Comparator<Object> {
        private String fieldName;

        public ListComparator(String fieldName) {
            this.fieldName = fieldName;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        public int compare(Object object1, Object object2) {
            try {
                Field field = object1.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                Comparable o1FieldValue = (Comparable) field.get(object1);
                Comparable o2FieldValue = (Comparable) field.get(object2);

                if (o1FieldValue == null){ return -1;}
                if (o2FieldValue == null){ return 1;}
                return o1FieldValue.compareTo(o2FieldValue);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException("Field doesn't exist", e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Field inaccessible", e);
            }
        }
    }
}

5

Puedes probar el pedido de guayaba :

Function<Item, Date> getStartDate = new Function<Item, Date>() {
    public Date apply(Item item) {
        return item.getStartDate();
    }
};

List<Item> orderedItems = Ordering.natural().onResultOf(getStartDate).
                          sortedCopy(items);

5

Puedes ordenar usando java 8

yourList.sort(Comparator.comparing(Classname::getName));

or

yourList.stream().forEach(a -> a.getBObjects().sort(Comparator.comparing(Classname::getValue)));

3

Estos fragmentos de código pueden ser útiles. Si desea ordenar un objeto en mi caso, quiero ordenar por nombre de volumen:

public List<Volume> getSortedVolumes() throws SystemException {
    List<Volume> volumes = VolumeLocalServiceUtil.getAllVolumes();
    Collections.sort(volumes, new Comparator<Volume>() {
        public int compare(Volume o1, Volume o2) {
            Volume p1 = (Volume) o1;
            Volume p2 = (Volume) o2;
            return p1.getVolumeName().compareToIgnoreCase(
                    p2.getVolumeName());
        }
    });
    return volumes;
}

Esto funciona. Lo uso en mi jsp.


3

Con esta biblioteca aquí puede ordenar la lista de objetos personalizados en varias columnas. La biblioteca utiliza características de la versión 8.0. La muestra también está disponible allí. Aquí hay una muestra para hacer

SortKeys sortKeys = new SortKeys();
sortKeys.addField("firstName")
            .addField("age", true); // This (true) will sort the age descending

// Other ways to specify a property to the sorter are
//      .addField("lastName", String.class);
//      .addField("dob", Date.class, true);

// Instantiate a ListSorter
ListSorter listSorter = new ListSorter();

// Pass the data to sort (listToSort) and the "by keys" to sort (sortKeys)
List sortedList = (List<Person>) listSorter.sortList(listToSort, sortKeys);

3

Puede echar un vistazo a esta presentación en el Foro Java en Stuttgart, Alemania, en 2016.

Solo unas pocas diapositivas usan el idioma alemán, el 99% del contenido es código fuente Java "basado en inglés"; me gusta

someCollection.sort(
  OurCustomComparator
    .comparing(Person::getName)
    .thenComparing(Person::getId)
);

dónde OurCustomComparator está utilizando métodos predeterminados (y otras ideas interesantes). Como se muestra, conduce a un código muy conciso para elegir algún método getter para ordenar; y encadenamiento súper simple (o inversión) de criterios de clasificación.

Si te gusta Java8, encontrarás una gran cantidad de material para comenzar.


3

Nuevo desde 1.8 es un método List.sort () en lugar de usar Collection.sort (), por lo que llama directamente a mylistcontainer.sort ()

Aquí hay un fragmento de código que muestra la función List.sort ():

List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Fruit("Kiwi","green",40));
fruits.add(new Fruit("Banana","yellow",100));
fruits.add(new Fruit("Apple","mixed green,red",120));
fruits.add(new Fruit("Cherry","red",10));

// a) using an existing compareto() method
fruits.sort((Fruit f1,Fruit f2) -> f1.getFruitName().compareTo(f2.getFruitName()));
System.out.println("Using String.compareTo(): " + fruits);
//Using String.compareTo(): [Apple is: mixed green,red, Banana is: yellow, Cherry is: red, Kiwi is: green]

// b) Using a comparable class
fruits.sort((Fruit f1,Fruit f2) -> f1.compareTo(f2));  
System.out.println("Using a Comparable Fruit class (sort by color): " + fruits);
// Using a Comparable Fruit class (sort by color): [Kiwi is green, Apple is: mixed green,red, Cherry is: red, Banana is: yellow]

La clase de frutas es:

public class Fruit implements Comparable<Fruit>
{
    private String name;
    private String color;
    private int quantity;

    public Fruit(String name,String color,int quantity)
    { this.name = name; this.color = color; this.quantity = quantity; }

    public String getFruitName() { return name; }        
    public String getColor() { return color; }  
    public int getQuantity() { return quantity; }

    @Override public final int compareTo(Fruit f) // sorting the color
    {
        return this.color.compareTo(f.color);
    }     
    @Override public String toString()
    {   
        return (name + " is: " + color);
    }
} // end of Fruit class   


1

Prefiero este proceso:

public class SortUtil
{    
    public static <T> List<T> sort(List<T> list, String sortByProperty)
    {
            Collections.sort(list, new BeanComparator(sortByProperty));
            return list;
    }
}

List<T> sortedList = SortUtil<T>.sort(unsortedList, "startDate");

Si su lista de objetos tiene una propiedad llamada startDate, llame a use this una y otra vez. Incluso puedes encadenarlos startDate.time.

Esto requiere que el objeto a ser Comparablelo que significa que necesita una compareTo, equalsy hashCodela implementación.

Sí, podría ser más rápido ... Pero ahora no tiene que hacer un nuevo comparador para cada tipo de género. Si puede ahorrar tiempo de desarrollo y renunciar al tiempo de ejecución, puede optar por este.


3
1, esta respuesta se dio 2 horas antes con el código de trabajo proporcionado también. No es necesario volver a publicar la misma solución y desordenar el foro, especialmente porque BeanComparator no es una clase estándar, por lo que no es realmente una solución si el póster no sabe de qué está hablando. Si le gusta la sugerencia original, puede votarla y agregar un comentario si lo desea.
camickr

0

El uso de Java 8 puede definir el uso Comparatorde una línea usandoComparator.comparing()

Utilice cualquiera de las siguientes formas:

Opción 1:

listToBeSorted.sort(Comparator.comparing(CustomObject::getStartDate));

Opcion 2:

Collections.sort(listToBeSorted, Comparator.comparing(CustomObject::getStartDate));

1
Parece ser un duplicado de esta respuesta de 3 años antes.
Basil Bourque

1
No exactamente, solo la opción 2 aquí es similar a la opción 1 en la respuesta mencionada. Siento que dos respuestas diferentes pueden tener al menos algunas similitudes si responden a la misma pregunta.
Sahil Chhabra

0

Su clase personalizada puede implementar la interfaz "Comparable", que requiere una implementación del método CompareTo. En el método CompareTo, puede definir lo que significa que un objeto es menor o mayor que el otro objeto. Entonces, en su ejemplo, puede verse más o menos así:

public class MyCustomClass implements Comparable<MyCustomClass>{

..........

 @Override
public int compareTo(MyCustomClass a) {
    if(this.getStartDate().before(a.getStartDate())){
        return -1;
    }else if(a.getStartDate().before(this.getStartDate())){
        return 1;
    }else {
        return 0;
    }
}

Un número negativo indica que esto es más pequeño que el objeto con el que se compara. Un número positivo indica que esto es mayor que el objeto comparado con el objeto y un cero significa que los objetos son iguales.

Luego puede usar collections.sort (myList) para ordenar su lista sin tener que alimentar en un comparador. Este método también tiene la ventaja de ordenar las cosas automáticamente si utiliza una estructura de datos de recopilación ordenada como un TreeSet o un TreeMap.

Puede consultar este artículo si desea leer más sobre la interfaz comparable (divulgación: soy el autor;)) https://nullbeans.com/the-java-comparable-interface-automatic-sort-of-collections/


0

También puede usar Springs PropertyComparator si solo tiene una ruta de propiedad de cadena a la propiedad (anidada) que desea ordenar:

List<SomeObject> list = ...;
PropertyComparator<HitWithInfo> propertyComparator = new PropertyComparator<>(
    "property.nested.myProperty", false, true);
list.sort(propertyComparator);

El inconveniente es que este comparador ignora silenciosamente las propiedades que no existen o no son accesibles y lo maneja como valor nulo para la comparación. Esto significa que debe probar cuidadosamente dicho comparador o validar la existencia de la ruta de la propiedad de alguna manera.


0

usando la API de flujo de Java-8 puede ordenar un ArrayListpor:

 Comparator<Person> birthdayComparator = Comparator.comparing(Person::getBirthday);
 List<Person> sortedList = list.stream().sorted(birthdayComparator).collect(toList());

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.