¿Cómo ordenar por dos campos en Java?

171

Tengo una variedad de objetos person (int age; String name;).

¿Cómo puedo ordenar esta matriz alfabéticamente por nombre y luego por edad?

¿Qué algoritmo usarías para esto?

Damir
fuente

Respuestas:

221

Puedes usar Collections.sortlo siguiente:

private static void order(List<Person> persons) {

    Collections.sort(persons, new Comparator() {

        public int compare(Object o1, Object o2) {

            String x1 = ((Person) o1).getName();
            String x2 = ((Person) o2).getName();
            int sComp = x1.compareTo(x2);

            if (sComp != 0) {
               return sComp;
            } 

            Integer x1 = ((Person) o1).getAge();
            Integer x2 = ((Person) o2).getAge();
            return x1.compareTo(x2);
    }});
}

List<Persons> ahora está ordenado por nombre, luego por edad.

String.compareTo"Compara dos cadenas lexicográficamente" - de los documentos .

Collections.sortes un método estático en la biblioteca de colecciones nativa. Realiza la clasificación real, solo necesita proporcionar un comparador que defina cómo deben compararse dos elementos de su lista: esto se logra al proporcionar su propia implementación del comparemétodo.

Richard H
fuente
10
También puede agregar un parámetro de tipo Comparatorpara evitar tener que emitir las entradas.
biziclop
@Ralph: modifiqué mi respuesta y agregué una breve descripción.
Richard H
Dado que el OP ya tiene su propia clase de objeto, tendría más sentido implementarlo Comparable. Ver la respuesta de @ berry120
Zulaxia
1
Mini revisión de código: la cláusula else es redundante porque la primera devolución actúa como una cláusula de protección. Gran respuesta, sin embargo, funcionó de maravilla para mí.
Tom Saleeba
30
Como esta pregunta / respuesta aún se vincula, tenga en cuenta que con Java SE 8 esto se volvió mucho más simple. Si hay captadores, puede escribirComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce
143

Para aquellos que pueden usar la API de transmisión Java 8, hay un enfoque más ordenado que está bien documentado aquí: Lambdas y clasificación

Estaba buscando el equivalente de C # LINQ:

.ThenBy(...)

Encontré el mecanismo en Java 8 en el Comparador:

.thenComparing(...)

Así que aquí está el fragmento que demuestra el algoritmo.

    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

Consulte el enlace de arriba para obtener una forma más ordenada y una explicación sobre cómo la inferencia de tipos de Java hace que sea un poco más complicado de definir en comparación con LINQ.

Aquí está la prueba de unidad completa para referencia:

@Test
public void testChainedSorting()
{
    // Create the collection of people:
    ArrayList<Person> people = new ArrayList<>();
    people.add(new Person("Dan", 4));
    people.add(new Person("Andi", 2));
    people.add(new Person("Bob", 42));
    people.add(new Person("Debby", 3));
    people.add(new Person("Bob", 72));
    people.add(new Person("Barry", 20));
    people.add(new Person("Cathy", 40));
    people.add(new Person("Bob", 40));
    people.add(new Person("Barry", 50));

    // Define chained comparators:
    // Great article explaining this and how to make it even neater:
    // http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

    // Sort the stream:
    Stream<Person> personStream = people.stream().sorted(comparator);

    // Make sure that the output is as expected:
    List<Person> sortedPeople = personStream.collect(Collectors.toList());
    Assert.assertEquals("Andi",  sortedPeople.get(0).name); Assert.assertEquals(2,  sortedPeople.get(0).age);
    Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
    Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
    Assert.assertEquals("Bob",   sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
    Assert.assertEquals("Bob",   sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
    Assert.assertEquals("Bob",   sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
    Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
    Assert.assertEquals("Dan",   sortedPeople.get(7).name); Assert.assertEquals(4,  sortedPeople.get(7).age);
    Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3,  sortedPeople.get(8).age);
    // Andi     : 2
    // Barry    : 20
    // Barry    : 50
    // Bob      : 40
    // Bob      : 42
    // Bob      : 72
    // Cathy    : 40
    // Dan      : 4
    // Debby    : 3
}

/**
 * A person in our system.
 */
public static class Person
{
    /**
     * Creates a new person.
     * @param name The name of the person.
     * @param age The age of the person.
     */
    public Person(String name, int age)
    {
        this.age = age;
        this.name = name;
    }

    /**
     * The name of the person.
     */
    public String name;

    /**
     * The age of the person.
     */
    public int age;

    @Override
    public String toString()
    {
        if (name == null) return super.toString();
        else return String.format("%s : %d", this.name, this.age);
    }
}
Luke Machowski
fuente
¿Cuál sería la complejidad de este tipo de encadenamiento de comparadores? ¿Estamos esencialmente ordenando cada vez que encadenamos los comparadores? Entonces, ¿hacemos una operación NlogN para cada comparador?
John Baum
17
Si hay captadores, puede escribirComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce,
1
Use thenComparingIntfor age (int)
Puce
La sintaxis con el labda '->' no funciona para mí. La persona :: getLastName lo hace.
Noldy
Después de crear el comparador, ¿cuál es la necesidad de crear una secuencia, ordenarla con el comparador y luego recopilarla? ¿Puedes usar Collections.sort(people, comparator);en su lugar?
Anish Sana
107

Uso del enfoque de Java 8 Streams ...

//Creates and sorts a stream (does not sort the original list)       
persons.stream().sorted(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

Y el enfoque Java 8 Lambda ...

//Sorts the original list Lambda style
persons.sort((p1, p2) -> {
        if (p1.getName().compareTo(p2.getName()) == 0) {
            return p1.getAge().compareTo(p2.getAge());
        } else {
            return p1.getName().compareTo(p2.getName());
        } 
    });

Por último...

//This is similar SYNTAX to the Streams above, but it sorts the original list!!
persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
Bradley D
fuente
19

Debe implementar el suyo Comparatory luego usarlo: por ejemplo

Arrays.sort(persons, new PersonComparator());

Su comparador podría verse un poco así:

public class PersonComparator implements Comparator<? extends Person> {

  public int compare(Person p1, Person p2) {
     int nameCompare = p1.name.compareToIgnoreCase(p2.name);
     if (nameCompare != 0) {
        return nameCompare;
     } else {
       return Integer.valueOf(p1.age).compareTo(Integer.valueOf(p2.age));
     }
  }
}

El comparador primero compara los nombres; si no son iguales, devuelve el resultado de compararlos; de lo contrario, devuelve el resultado de comparación al comparar las edades de ambas personas.

Este código es solo un borrador: debido a que la clase es inmutable, se podría pensar en construir un singleton de ella, en lugar de crear una nueva instancia para cada clasificación.

Ralph
fuente
16

Puede utilizar el enfoque Java 8 Lambda para lograr esto. Me gusta esto:

persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
Zia Ul Mustafa
fuente
15

Haga que su clase de persona implemente Comparable<Person>y luego implemente el método compareTo, por ejemplo:

public int compareTo(Person o) {
    int result = name.compareToIgnoreCase(o.name);
    if(result==0) {
        return Integer.valueOf(age).compareTo(o.age);
    }
    else {
        return result;
    }
}

Eso se ordenará primero por nombre (sin distinción entre mayúsculas y minúsculas) y luego por edad. Luego puede ejecutar Arrays.sort()o Collections.sort()en la colección o matriz de objetos Persona.

Michael Berry
fuente
Generalmente prefiero esto a hacer un Comparador, ya que, como dice berry120, puede ordenar con métodos integrados, en lugar de tener que usar siempre su Comparador personalizado.
Zulaxia
4

La guayaba ComparisonChainproporciona una forma limpia de hacerlo. Consulte este enlace .

Una utilidad para realizar una declaración de comparación encadenada. Por ejemplo:

   public int compareTo(Foo that) {
     return ComparisonChain.start()
         .compare(this.aString, that.aString)
         .compare(this.anInt, that.anInt)
         .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
         .result();
   }
Pritesh Mhatre
fuente
4

Puedes hacer así:

List<User> users = Lists.newArrayList(
  new User("Pedro", 12), 
  new User("Maria", 10), 
  new User("Rafael",12)
);

users.sort(
  Comparator.comparing(User::getName).thenComparing(User::getAge)
);
rafambbr
fuente
3

Use Comparatory luego ponga objetos Collection, luegoCollections.sort();

class Person {

    String fname;
    String lname;
    int age;

    public Person() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getFname() {
        return fname;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public String getLname() {
        return lname;
    }

    public void setLname(String lname) {
        this.lname = lname;
    }

    public Person(String fname, String lname, int age) {
        this.fname = fname;
        this.lname = lname;
        this.age = age;
    }

    @Override
    public String toString() {
        return fname + "," + lname + "," + age;
    }
}

public class Main{

    public static void main(String[] args) {
        List<Person> persons = new java.util.ArrayList<Person>();
        persons.add(new Person("abc3", "def3", 10));
        persons.add(new Person("abc2", "def2", 32));
        persons.add(new Person("abc1", "def1", 65));
        persons.add(new Person("abc4", "def4", 10));
        System.out.println(persons);
        Collections.sort(persons, new Comparator<Person>() {

            @Override
            public int compare(Person t, Person t1) {
                return t.getAge() - t1.getAge();
            }
        });
        System.out.println(persons);

    }
}
Jigar Joshi
fuente
3

Crea tantos comparadores como sea necesario. Después, llame al método "thenComparing" para cada categoría de pedido. Es una forma de hacerlo por Streams. Ver:

//Sort by first and last name
System.out.println("\n2.Sort list of person objects by firstName then "
                                        + "by lastName then by age");
Comparator<Person> sortByFirstName 
                            = (p, o) -> p.firstName.compareToIgnoreCase(o.firstName);
Comparator<Person> sortByLastName 
                            = (p, o) -> p.lastName.compareToIgnoreCase(o.lastName);
Comparator<Person> sortByAge 
                            = (p, o) -> Integer.compare(p.age,o.age);

//Sort by first Name then Sort by last name then sort by age
personList.stream().sorted(
    sortByFirstName
        .thenComparing(sortByLastName)
        .thenComparing(sortByAge)
     ).forEach(person->
        System.out.println(person));        

Aspecto: ordenar objetos definidos por el usuario en múltiples campos - Comparador (secuencia lambda)

Fabian Brandão
fuente
3

Sería cuidadoso al usar Guava's ComparisonChainporque crea una instancia de él por elemento comparado, por lo que estaría viendo una creación de N x Log Ncadenas de comparación solo para comparar si está ordenando, o Ninstancias si está iterando y verificando la igualdad.

En su lugar, crearía una estática Comparatorutilizando la API de Java 8 más nueva si es posible o la OrderingAPI de Guava que le permite hacer eso, aquí hay un ejemplo con Java 8:

import java.util.Comparator;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsLast;

private static final Comparator<Person> COMPARATOR = Comparator
  .comparing(Person::getName, nullsLast(naturalOrder()))
  .thenComparingInt(Person::getAge);

@Override
public int compareTo(@NotNull Person other) {
  return COMPARATOR.compare(this, other);
}

Aquí se explica cómo usar la OrderingAPI de Guava : https://github.com/google/guava/wiki/OrderingExplained

Guido Medina
fuente
1
"... crea una instancia de este por elemento comparado ..." - esto no es cierto. Al menos en las versiones modernas de Guava, llamar al comparemétodo no crea nada, pero devuelve una de las instancias singleton LESS, GREATERo ACTIVEdependiendo del resultado de la comparación. Este es un enfoque altamente optimizado y no agrega memoria ni sobrecarga de rendimiento.
Yoory N.
Si; Acabo de mirar el código fuente ahora, veo lo que quieres decir, pero estaría más inclinado a usar la nueva API de comparación de Java 8 en aras de las dependencias.
Guido Medina
2

O puede explotar el hecho de que Collections.sort()(o Arrays.sort()) es estable (no reordena elementos que son iguales) y use un Comparatorpara ordenar por edad primero y luego otro para ordenar por nombre.

En este caso específico, esta no es una muy buena idea, pero si tiene que poder cambiar el orden de clasificación en tiempo de ejecución, podría ser útil.

biziclop
fuente
2

Puede usar el comparador en serie genérico para ordenar las colecciones por múltiples campos.

import org.apache.commons.lang3.reflect.FieldUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
* @author MaheshRPM
*/
public class SerialComparator<T> implements Comparator<T> {
List<String> sortingFields;

public SerialComparator(List<String> sortingFields) {
    this.sortingFields = sortingFields;
}

public SerialComparator(String... sortingFields) {
    this.sortingFields = Arrays.asList(sortingFields);
}

@Override
public int compare(T o1, T o2) {
    int result = 0;
    try {
        for (String sortingField : sortingFields) {
            if (result == 0) {
                Object value1 = FieldUtils.readField(o1, sortingField, true);
                Object value2 = FieldUtils.readField(o2, sortingField, true);
                if (value1 instanceof Comparable && value2 instanceof Comparable) {
                    Comparable comparable1 = (Comparable) value1;
                    Comparable comparable2 = (Comparable) value2;
                    result = comparable1.compareTo(comparable2);
                } else {
                    throw new RuntimeException("Cannot compare non Comparable fields. " + value1.getClass()
                            .getName() + " must implement Comparable<" + value1.getClass().getName() + ">");
                }
            } else {
                break;
            }
        }
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
    return result;
}
}
Maheshkumar
fuente
0
Arrays.sort(persons, new PersonComparator());



import java.util.Comparator;

public class PersonComparator implements Comparator<? extends Person> {

    @Override
    public int compare(Person o1, Person o2) {
        if(null == o1 || null == o2  || null == o1.getName() || null== o2.getName() ){
            throw new NullPointerException();
        }else{
            int nameComparisonResult = o1.getName().compareTo(o2.getName());
            if(0 == nameComparisonResult){
                return o1.getAge()-o2.getAge();
            }else{
                return nameComparisonResult;
            }
        }
    }
}


class Person{
    int age; String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Versión actualizada:

public class PersonComparator implements Comparator<? extends Person> {

   @Override
   public int compare(Person o1, Person o2) {

      int nameComparisonResult = o1.getName().compareToIgnoreCase(o2.getName());
      return 0 == nameComparisonResult?o1.getAge()-o2.getAge():nameComparisonResult;

   }
 }
fmucar
fuente
El manejo de excepciones de puntero nulo es agradable y deja en claro que no funcionaría con nulo, pero de todos modos se plantearía
Ralph el
Tienes toda la razón. Recientemente solía verificar algunos valores para copiar de un lugar a otro y ahora lo sigo haciendo en todas partes.
fmucar
0

Para una clase Bookcomo esta:

package books;

public class Book {

    private Integer id;
    private Integer number;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "book{" +
                "id=" + id +
                ", number=" + number +
                ", name='" + name + '\'' + '\n' +
                '}';
    }
}

ordenar la clase principal con objetos simulados

package books;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");

        Book b = new Book();

        Book c = new Book();

        Book d = new Book();

        Book e = new Book();

        Book f = new Book();

        Book g = new Book();
        Book g1 = new Book();
        Book g2 = new Book();
        Book g3 = new Book();
        Book g4 = new Book();




        b.setId(1);
        b.setNumber(12);
        b.setName("gk");

        c.setId(2);
        c.setNumber(12);
        c.setName("gk");

        d.setId(2);
        d.setNumber(13);
        d.setName("maths");

        e.setId(3);
        e.setNumber(3);
        e.setName("geometry");

        f.setId(3);
        f.setNumber(34);
        b.setName("gk");

        g.setId(3);
        g.setNumber(11);
        g.setName("gk");

        g1.setId(3);
        g1.setNumber(88);
        g1.setName("gk");
        g2.setId(3);
        g2.setNumber(91);
        g2.setName("gk");
        g3.setId(3);
        g3.setNumber(101);
        g3.setName("gk");
        g4.setId(3);
        g4.setNumber(4);
        g4.setName("gk");





        List<Book> allBooks = new ArrayList<Book>();

        allBooks.add(b);
        allBooks.add(c);
        allBooks.add(d);
        allBooks.add(e);
        allBooks.add(f);
        allBooks.add(g);
        allBooks.add(g1);
        allBooks.add(g2);
        allBooks.add(g3);
        allBooks.add(g4);



        System.out.println(allBooks.size());


        Collections.sort(allBooks, new Comparator<Book>() {

            @Override
            public int compare(Book t, Book t1) {
                int a =  t.getId()- t1.getId();

                if(a == 0){
                    int a1 = t.getNumber() - t1.getNumber();
                    return a1;
                }
                else
                    return a;
            }
        });
        System.out.println(allBooks);

    }


   }
Jackey Dada Bob
fuente
0

No estoy seguro de si es feo escribir el compartimento dentro de la clase Persona en este caso. Lo hizo así:

public class Person implements Comparable <Person> {

    private String lastName;
    private String firstName;
    private int age;

    public Person(String firstName, String lastName, int BirthDay) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = BirthDay;
    }

    public int getAge() {
        return age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    @Override
    public int compareTo(Person o) {
        // default compareTo
    }

    @Override
    public String toString() {
        return firstName + " " + lastName + " " + age + "";
    }

    public static class firstNameComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.firstName.compareTo(o2.firstName);
        }
    }

    public static class lastNameComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.lastName.compareTo(o2.lastName);
        }
    }

    public static class ageComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.age - o2.age;
        }
    }
}
public class Test {
    private static void print() {
       ArrayList<Person> list = new ArrayList();
        list.add(new Person("Diana", "Agron", 31));
        list.add(new Person("Kay", "Panabaker", 27));
        list.add(new Person("Lucy", "Hale", 28));
        list.add(new Person("Ashley", "Benson", 28));
        list.add(new Person("Megan", "Park", 31));
        list.add(new Person("Lucas", "Till", 27));
        list.add(new Person("Nicholas", "Hoult", 28));
        list.add(new Person("Aly", "Michalka", 28));
        list.add(new Person("Adam", "Brody", 38));
        list.add(new Person("Chris", "Pine", 37));
        Collections.sort(list, new Person.lastNameComperator());
        Iterator<Person> it = list.iterator();
        while(it.hasNext()) 
            System.out.println(it.next().toString()); 
     }  
}    
Tiago Redaelli
fuente