Wie sortiere ich in Java nach zwei Feldern?


170

Ich habe eine Reihe von Objekten person (int age; String name;).

Wie kann ich dieses Array alphabetisch nach Namen und dann nach Alter sortieren?

Welchen Algorithmus würden Sie dafür verwenden?

Antworten:


221

Sie können Collections.sortwie folgt verwenden:

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> wird jetzt nach Namen und dann nach Alter sortiert.

String.compareTo"Vergleicht zwei Zeichenfolgen lexikografisch" - aus den Dokumenten .

Collections.sortist eine statische Methode in der nativen Sammlungsbibliothek. Für die eigentliche Sortierung müssen Sie lediglich einen Komparator bereitstellen, der definiert, wie zwei Elemente in Ihrer Liste verglichen werden sollen: Dies wird durch die Bereitstellung einer eigenen Implementierung der compareMethode erreicht.


10
Sie können auch einen Typparameter hinzufügen, um Comparatorzu vermeiden, dass die Eingaben umgewandelt werden müssen.
Biziclop

@ Ralph: Ich habe meine Antwort geändert und eine kurze Beschreibung hinzugefügt.
Richard H

Da das OP bereits eine eigene Objektklasse hat, wäre eine Implementierung sinnvoller Comparable. Siehe die Antwort von @ berry120
Zulaxia

1
Mini-Code-Überprüfung: Die else-Klausel ist redundant, da die erste Rückgabe als Schutzklausel fungiert. Tolle Antwort, hat mir sehr gut gefallen.
Tom Saleeba

29
Da diese Frage / Antwort immer noch verknüpft wird, beachten Sie bitte, dass dies mit Java SE 8 viel einfacher wurde. Wenn es Getter gibt, können Sie schreibenComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce

142

Für diejenigen, die die Java 8-Streaming-API verwenden können, gibt es einen übersichtlicheren Ansatz, der hier gut dokumentiert ist: Lambdas und Sortierung

Ich habe nach dem Äquivalent zum C # LINQ gesucht:

.ThenBy(...)

Ich habe den Mechanismus in Java 8 im Komparator gefunden:

.thenComparing(...)

Hier ist also das Snippet, das den Algorithmus demonstriert.

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

Schauen Sie sich den Link oben an, um eine übersichtlichere Art und eine Erklärung zu erhalten, wie die Java-Typinferenz die Definition im Vergleich zu LINQ etwas umständlicher macht.

Hier ist der vollständige Unit-Test als Referenz:

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

Wie komplex wäre diese Art der Verkettung von Komparatoren? Sortieren wir im Wesentlichen jedes Mal, wenn wir die Komparatoren verketten? Also machen wir eine NlogN-Operation für jeden Komparator?
John Baum

17
Wenn es Getter gibt, können Sie schreibenComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce

1
Verwenden Sie thenComparingIntfür Alter (int)
Puce

Die Syntax mit dem labda '->' funktioniert bei mir nicht. Die Person :: getLastName tut dies.
Noldy

Was ist nach dem Erstellen des Komparators erforderlich, um einen Stream zu erstellen, ihn mit dem Komparator zu sortieren und dann zu sammeln? Können Sie Collections.sort(people, comparator);stattdessen einfach verwenden ?
Anish Sana

105

Verwenden des Java 8 Streams-Ansatzes ...

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

Und der Java 8 Lambda-Ansatz ...

//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());
        } 
    });

Zuletzt...

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

19

Sie müssen Ihre eigenen implementieren Comparatorund dann verwenden: zum Beispiel

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

Ihr Komparator könnte ungefähr so ​​aussehen:

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

Der Komparator vergleicht zuerst die Namen. Wenn sie nicht gleich sind, gibt er das Ergebnis des Vergleichs zurück, andernfalls gibt er das Vergleichsergebnis zurück, wenn das Alter beider Personen verglichen wird.

Dieser Code ist nur ein Entwurf: Da die Klasse unveränderlich ist, können Sie sich vorstellen, einen Singleton daraus zu erstellen und stattdessen für jede Sortierung eine neue Instanz zu erstellen.


16

Sie können den Java 8 Lambda-Ansatz verwenden, um dies zu erreichen. So was:

persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

15

Comparable<Person>Lassen Sie Ihre Personenklasse implementieren und dann die compareTo-Methode implementieren, zum Beispiel:

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

Das wird zuerst nach Namen (ohne Berücksichtigung der Groß- und Kleinschreibung) und dann nach Alter sortiert. Sie können dann Arrays.sort()oder Collections.sort()auf der Sammlung oder dem Array von Personenobjekten ausführen .


Im Allgemeinen ziehe ich dies der Erstellung eines Komparators vor, da Sie, wie berry120 sagt, dann mit integrierten Methoden sortieren können, anstatt immer Ihren benutzerdefinierten Komparator verwenden zu müssen.
Zulaxia

4

Guava ComparisonChainbietet eine saubere Möglichkeit, dies zu tun. Siehe diesen Link .

Ein Dienstprogramm zum Ausführen einer verketteten Vergleichsanweisung. Beispielsweise:

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

4

Sie können dies tun:

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

3

Verwendung Comparatorund setzen Sie dann Objekte in Collection, dannCollections.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);

    }
}

3

Erstellen Sie so viele Komparatoren wie nötig. Rufen Sie anschließend für jede Auftragskategorie die Methode "thenComparing" auf. Es ist eine Art von Streams. Sehen:

//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));        

Look: Benutzerdefiniertes Objekt nach mehreren Feldern sortieren - Komparator (Lambda-Stream)


3

Ich wäre vorsichtig, wenn ich Guavas verwende, ComparisonChainda es eine Instanz davon pro verglichenem Element erstellt, sodass Sie sich eine Erstellung von N x Log NVergleichsketten ansehen würden, um zu vergleichen, ob Sie sortieren oderN Instanzen, wenn Sie iterieren und auf Gleichheit prüfen.

Ich würde stattdessen eine Statik erstellen, Comparatorwenn möglich mit der neuesten Java 8-API oder der Guava- OrderingAPI, mit der Sie dies tun können. Hier ein Beispiel mit 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);
}

So verwenden Sie die Guava- OrderingAPI: https://github.com/google/guava/wiki/OrderingExplained


1
"... erstellt eine Instanz davon pro verglichenem Element ..." - das ist nicht wahr. Zumindest in modernen Versionen von Guava, dem Aufruf von compareMethode hat nichts zu schaffen, sondern gibt eine von Singleton - Instanzen LESS, GREATERoder in ACTIVEAbhängigkeit vom Ergebnis des Vergleichs. Dies ist ein hochoptimierter Ansatz, der keinen Speicher- oder Leistungsaufwand verursacht.
Yoory N.

Ja; Ich habe mir jetzt nur den Quellcode angesehen, ich verstehe, was Sie meinen, aber ich wäre eher geneigt, die neue Java 8-Vergleichs-API aus Gründen der Abhängigkeiten zu verwenden.
Guido Medina

2

Oder Sie können die Tatsache ausnutzen, dass Collections.sort()(oder Arrays.sort()) stabil ist (es ordnet keine gleichwertigen Elemente neu an) und zuerst mit a Comparatornach Alter und dann mit einem anderen nach Namen sortieren.

In diesem speziellen Fall ist dies keine sehr gute Idee, aber wenn Sie in der Lage sein müssen, die Sortierreihenfolge zur Laufzeit zu ändern, kann dies hilfreich sein.


2

Sie können den generischen seriellen Komparator verwenden, um Sammlungen nach mehreren Feldern zu sortieren.

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

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

}

Aktualisierte Version:

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;

   }
 }

Die Nullpointer-Ausnahmebehandlung ist nett und macht deutlich, dass es mit null nicht funktionieren würde, aber es würde trotzdem ausgelöst
Ralph

Du liegst absolut richtig. Ich habe kürzlich einige Werte überprüft, um sie von einem Ort zum anderen zu kopieren, und jetzt mache ich das überall.
Fmucar

0

Für eine Klasse Bookwie diese:

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

Hauptklasse mit Scheinobjekten sortieren

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

    }


   }

0

Ich bin mir nicht sicher, ob es in diesem Fall hässlich ist, das Kompartiment innerhalb der Person-Klasse zu schreiben. Hat es so gefallen:

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()); 
     }  
}    
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.