Jak porównywać obiekty według wielu pól

237

Załóżmy, że masz kilka obiektów o kilku polach, które można porównać:

public class Person {

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

    /* Constructors */

    /* Methods */

}

W tym przykładzie, kiedy pytasz, czy:

a.compareTo(b) > 0

możesz zapytać, czy nazwisko a występuje przed b, czy a jest starsze niż b itp.

Jaki jest najczystszy sposób na wielokrotne porównywanie tego rodzaju obiektów bez niepotrzebnego bałaganu lub narzutu?

  • java.lang.Comparable interfejs umożliwia porównanie tylko przez jedno pole
  • Dodając liczne porównania metod (tj compareByFirstName(), compareByAge()itp ...) jest bałagan w mojej opinii.

Więc jak najlepiej to zrobić?

Yuval Adam
źródło
3
dlaczego to jest CW? To doskonale poprawne pytanie programistyczne.
Elie,
2
Czy zdajesz sobie sprawę, że Porównywalny umożliwia porównywanie według dowolnej liczby pól?
DJClayworth,

Odpowiedzi:

81

Możesz zaimplementować obiekt Comparatorporównujący dwa Personobiekty i zbadać dowolną liczbę pól. Możesz wprowadzić zmienną w swoim komparatorze, która mówi mu, z którym polem ma się porównać, chociaż prawdopodobnie łatwiej byłoby po prostu napisać wiele komparatorów.

Elie
źródło
5
Właściwie wolę pomysł użycia jednego komparatora. Nie sądzę, aby ta odpowiedź była zła, ale każdy, kto ją przeczyta, zdecydowanie powinien sprawdzić odpowiedź Steve Kuo poniżej.
Felipe Leão
Wiele komparatorów było tylko wtedy, gdy chcesz różnych metod porównywania, które nie są funkcją samych danych - tj. Czasami chcesz porównać według nazwy, innym razem według wieku itp. Aby porównać według wielu pól jednocześnie, tylko jeden komparator byłoby konieczne.
Elie,
399

W Javie 8:

Comparator.comparing((Person p)->p.firstName)
          .thenComparing(p->p.lastName)
          .thenComparingInt(p->p.age);

Jeśli masz metody akcesora:

Comparator.comparing(Person::getFirstName)
          .thenComparing(Person::getLastName)
          .thenComparingInt(Person::getAge);

Jeśli klasa implementuje Porównywalny, taki komparator może być użyty w metodzie CompareTo:

@Override
public int compareTo(Person o){
    return Comparator.comparing(Person::getFirstName)
              .thenComparing(Person::getLastName)
              .thenComparingInt(Person::getAge)
              .compare(this, o);
}
Enigo
źródło
5
Szczególnie obsada (Person p)jest ważna dla komparatorów łańcuchowych.
Membersound
5
Jak wydajne jest porównywanie dużej liczby obiektów, np. Podczas sortowania? Czy musi tworzyć nowe Comparatorwystąpienia w każdym połączeniu?
jjurm,
4
Otrzymuję wyjątek NullPointerException, gdy jedno z pól, które porównuję, ma wartość NULL, jak ciąg. Czy jest jakiś sposób, aby zachować ten format porównania, ale pozwolić, aby był on bezpieczny?
rveach
3
@jjurm .thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))- pierwszy selektor pola, a następnie komparator
gavenkoa
2
@jjurm, gdy używasz go, compareTojak pokazano powyżej, Comparatorjest tworzony za każdym razem, gdy wywoływana jest metoda. Można temu zapobiec, przechowując komparator w prywatnym statycznym polu końcowym.
Gandalf
165

Powinieneś wdrożyć Comparable <Person>. Zakładając, że wszystkie pola nie będą miały wartości zerowej (dla uproszczenia), że wiek jest liczbą całkowitą i porównanie rankingu jest pierwsze, ostatnie, wiek, compareTometoda jest dość prosta:

public int compareTo(Person other) {
    int i = firstName.compareTo(other.firstName);
    if (i != 0) return i;

    i = lastName.compareTo(other.lastName);
    if (i != 0) return i;

    return Integer.compare(age, other.age);
}
Steve Kuo
źródło
10
jeśli zaimplementujesz Porównywalny <Osoba>, wówczas metodą jest porównajTo (Osoba p) .. wygląda na to, że ta odpowiedź została pomieszana z metodą porównania <T o1, T o2> Komparatora
Mike
5
Nie jest to zalecane. Użyj Komparatora, jeśli masz wiele pól.
indika
1
jest to obecnie najlepsze rozwiązanie (lepsze niż więcej komparatorów)
Vasile Surdu
4
@indika, jestem ciekawy: dlaczego nie jest to zalecane? Porównywanie za pomocą więcej niż jednej właściwości wydaje mi się całkowicie w porządku.
ars-longa-vita-brevis
4
@ ars-longa-vita-brevis, Jeśli używasz porównywalnego, logika sortowania musi należeć do tej samej klasy, której obiekty są sortowane, więc nazywa się to naturalnym porządkowaniem obiektów. Za pomocą komparatora możesz pisać niestandardową logikę sortowania poza klasą Person. Jeśli chcesz porównać obiekty Person tylko według imienia lub nazwiska, nie możesz użyć tej logiki. Musisz to napisać jeszcze raz,
indika
78

(od sposobów sortowania list obiektów w Javie na podstawie wielu pól )

Działający kod w tej treści

Korzystanie z Java 8 lambda (dodano 10 kwietnia 2019 r.)

Java 8 ładnie rozwiązuje to przez lambda (chociaż Guava i Apache Commons mogą nadal oferować większą elastyczność):

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

Dzięki odpowiedzi @ gaoagong poniżej .

Bałagan i zawiłości: sortowanie ręczne

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        int sizeCmp = p1.size.compareTo(p2.size);  
        if (sizeCmp != 0) {  
            return sizeCmp;  
        }  
        int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
        if (nrOfToppingsCmp != 0) {  
            return nrOfToppingsCmp;  
        }  
        return p1.name.compareTo(p2.name);  
    }  
});  

Wymaga to dużo pisania, konserwacji i jest podatne na błędy.

Refleksyjny sposób: sortowanie za pomocą BeanComparator

ComparatorChain chain = new ComparatorChain(Arrays.asList(
   new BeanComparator("size"), 
   new BeanComparator("nrOfToppings"), 
   new BeanComparator("name")));

Collections.sort(pizzas, chain);  

Oczywiście jest to bardziej zwięzłe, ale jeszcze bardziej podatne na błędy, ponieważ tracisz bezpośrednie odniesienie do pól za pomocą Ciągów (zamiast bezpieczeństwa typów, auto-refaktoryzacji). Teraz, jeśli zmieniono nazwę pola, kompilator nawet nie zgłosi problemu. Ponadto, ponieważ w tym rozwiązaniu wykorzystuje się odbicie, sortowanie jest znacznie wolniejsze.

Jak się tam dostać: Sortowanie za pomocą Google Guava's CompareChain

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
        // or in case the fields can be null:  
        /* 
        return ComparisonChain.start() 
           .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
           .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
           .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
           .result(); 
        */  
    }  
});  

Jest to o wiele lepsze, ale wymaga trochę kodu płyty kotłowej dla najczęstszego przypadku użycia: wartości zerowe powinny być domyślnie mniej cenione. W przypadku pól zerowych musisz podać Guava dodatkową dyrektywę, co robić w takim przypadku. Jest to elastyczny mechanizm, jeśli chcesz zrobić coś konkretnego, ale często chcesz domyślny przypadek (np. 1, a, b, z, null).

Sortowanie za pomocą Apache Commons CompareToBuilder

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
    }  
});  

Podobnie jak Guava's CompareChain, ta klasa biblioteki łatwo sortuje według wielu pól, ale także definiuje domyślne zachowanie dla wartości zerowych (tj. 1, a, b, z, null). Nie możesz jednak podać niczego innego, chyba że podasz własny komparator.

A zatem

Ostatecznie sprowadza się to do smaku i potrzeby elastyczności (Guava's CompareChain) vs. zwięzłego kodu (Apache's CompareToBuilder).

Metoda bonusowa

Znalazłem dobre rozwiązanie, które łączy wiele komparatorów w kolejności pierwszeństwa na CodeReview w MultiComparator:

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

Ofcourse Apache Commons Collections ma już do tego zastosowanie:

ComparatorUtils.chainedComparator (ComparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
Benny Bottema
źródło
22

@Patrick Aby posortować więcej niż jedno pole, spróbuj po prostu ComparatorChain

Łańcuch komparatora to komparator, który otacza jeden lub więcej komparatorów po kolei. ComparatorChain wywołuje każdy Komparator w sekwencji, aż albo 1) dowolny pojedynczy Komparator zwróci wynik niezerowy (i ten wynik zostanie następnie zwrócony), lub 2) KomparatorChain zostanie wyczerpany (i zostanie zwrócone zero). Ten typ sortowania jest bardzo podobny do sortowania wielokolumnowego w SQL, a ta klasa pozwala klasom Java naśladować takie zachowanie podczas sortowania listy.

Aby dodatkowo ułatwić sortowanie podobne do SQL, kolejność dowolnego pojedynczego Komparatora na liście można> odwrócić.

Wywołanie metody, która dodaje nowe Komparatory lub zmienia sortowanie wznoszące / opadające po wywołaniu porównania (Obiekt, Obiekt), spowoduje wyjątek UnsupportedOperationException. Uważaj jednak, aby nie zmieniać bazowej listy komparatorów ani zestawu bitów, który definiuje porządek sortowania.

Wystąpienia łańcucha porównawczego nie są synchronizowane. Klasa nie jest bezpieczna dla wątków w czasie budowy, ale jest bezpieczna dla wątków, aby wykonywać wiele porównań po zakończeniu wszystkich operacji instalacji.

Nigel_V_Thomas
źródło
20

Inną opcją, którą zawsze możesz rozważyć, jest Apache Commons. Zapewnia wiele opcji.

import org.apache.commons.lang3.builder.CompareToBuilder;

Dawny:

public int compare(Person a, Person b){

   return new CompareToBuilder()
     .append(a.getName(), b.getName())
     .append(a.getAddress(), b.getAddress())
     .toComparison();
}
Xeroiris
źródło
10
import com.google.common.collect.ComparisonChain;

/**
 * @author radler
 * Class Description ...
 */
public class Attribute implements Comparable<Attribute> {

    private String type;
    private String value;

    public String getType() { return type; }
    public void setType(String type) { this.type = type; }

    public String getValue() { return value; }
    public void setValue(String value) { this.value = value; }

    @Override
    public String toString() {
        return "Attribute [type=" + type + ", value=" + value + "]";
    }

    @Override
    public int compareTo(Attribute that) {
        return ComparisonChain.start()
            .compare(this.type, that.type)
            .compare(this.value, that.value)
            .result();
    }

}
Ran Adler
źródło
1
Bardzo podoba mi się ta strategia. Dzięki!
Pan Polywhirl,
Najbardziej skuteczny sposób! Dzięki
Zakaria Bouazza
8

Dla tych, którzy potrafią korzystać z interfejsu API przesyłania strumieniowego Java 8, istnieje dobrze udokumentowane podejście, które zostało dobrze udokumentowane: lambda i sortowanie

Szukałem odpowiednika C # LINQ:

.ThenBy(...)

Znalazłem mechanizm w Javie 8 na Komparatorze:

.thenComparing(...)

Oto fragment kodu demonstrujący algorytm.

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

Sprawdź powyższy link, aby uzyskać bardziej przejrzysty sposób i wyjaśnienie, w jaki sposób wnioskowanie o typie Javy sprawia, że ​​definiowanie go jest nieco bardziej niezręczne w porównaniu z LINQ.

Oto pełny test jednostkowy w celach informacyjnych:

@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);
    }
}
Łukasz Machowski
źródło
7

ComparatorRęczne pisanie dla takiego przypadku użycia jest okropnym rozwiązaniem IMO. Takie podejścia ad hoc mają wiele wad:

  • Bez ponownego użycia kodu. Narusza SUCHO.
  • Płyta grzewcza.
  • Zwiększona możliwość błędów.

Więc jakie jest rozwiązanie?

Najpierw trochę teorii.

Oznaczmy propozycję „typ Aobsługuje porównanie” przez Ord A. (Z perspektywy programu można myśleć Ord Ao obiekcie zawierającym logikę do porównywania dwóch As. Tak, tak jak Comparator.)

Teraz, jeśli Ord Ai Ord B, to ich kompozyt (A, B)powinien również obsługiwać porównanie. tj Ord (A, B). Jeśli Ord A, Ord Bi Ord Cwtedy Ord (A, B, C).

Możemy rozszerzyć ten argument na dowolne arsenał i powiedzieć:

Ord A, Ord B, Ord C, ..., Ord ZOrd (A, B, C, .., Z)

Nazwijmy to stwierdzenie 1.

Porównanie kompozytów będzie działać tak, jak opisano w pytaniu: pierwsze porównanie zostanie najpierw wypróbowane, następnie następne, następne i tak dalej.

To pierwsza część naszego rozwiązania. Teraz druga część.

Jeśli wiesz, że Ord Ai wiem jak przekształcić Bdo A(wywołanie tej funkcji transformacji f), to można również Ord B. W jaki sposób? Cóż, gdy Bporównamy te dwa wystąpienia, najpierw przekształcamy je w Aużycie, fa następnie stosujemy Ord A.

Tutaj mapujemy transformację B → Ana Ord A → Ord B. Jest to znane jako mapowanie przeciwstawne (lub comapw skrócie).

Ord A, (B → A)comap Ord B

Nazwijmy to stwierdzenie 2.


Teraz zastosujmy to do twojego przykładu.

Masz nazwany typ danych, Personktóry obejmuje trzy pola typu String.

  • Wiemy, że Ord String. Przez oświadczenie 1 Ord (String, String, String).

  • Możemy łatwo napisać funkcję od Persondo (String, String, String). (Zwróć tylko trzy pola.) Ponieważ wiemy Ord (String, String, String)iPerson → (String, String, String) poprzez oświadczenie 2, możemy użyć, comapaby uzyskać Ord Person.

CO BYŁO DO OKAZANIA.


Jak wdrożyć wszystkie te koncepcje?

Dobra wiadomość jest taka, że ​​nie musisz. Istnieje już biblioteka która implementuje wszystkie pomysły opisane w tym poście. (Jeśli jesteś ciekawy, jak są one realizowane, możesz zajrzeć pod maską .)

Tak będzie z tym wyglądał kod:

Ord<Person> personOrd = 
 p3Ord(stringOrd, stringOrd, stringOrd).comap(
   new F<Person, P3<String, String, String>>() {
     public P3<String, String, String> f(Person x) {
       return p(x.getFirstName(), x.getLastname(), x.getAge());
     }
   }
 );

Wyjaśnienie:

  • stringOrd jest obiektem typu Ord<String> . Odpowiada to naszej oryginalnej propozycji „obsługuje porównanie”.
  • p3OrdJest to metoda, która trwa Ord<A>, Ord<B>, Ord<C>i powroty Ord<P3<A, B, C>>. Odpowiada to stwierdzeniu 1. (P3 oznacza produkt z trzema elementami. Produkt jest algebraicznym terminem określającym kompozyty).
  • comapodpowiada dobrze, comap.
  • F<A, B>reprezentuje funkcję transformacji A → B.
  • p to fabryczna metoda tworzenia produktów.
  • Całe wyrażenie odpowiada stwierdzeniu 2.

Mam nadzieję, że to pomaga.

missingfaktor
źródło
5

Zamiast metod porównawczych możesz po prostu zdefiniować kilka typów podklas „Komparator” w klasie Person. W ten sposób możesz przekazać je do standardowych metod sortowania kolekcji.

Marc Nowakowski
źródło
3

Myślę, że byłoby bardziej mylące, gdyby Twój algorytm porównania był „sprytny”. Wybrałbym wiele zaproponowanych metod porównania.

Jedynym wyjątkiem dla mnie byłaby równość. Przy testowaniu jednostkowym przydało mi się przesłonić .Equals (w .net) w celu ustalenia, czy kilka pól jest równych między dwoma obiektami (a nie, że referencje są równe).

Michael Haren
źródło
3

Jeśli istnieje wiele sposobów, w jakie użytkownik może zamówić osobę, możesz także mieć gdzieś wiele ustawień Komparatora jako stałych. Większość operacji sortowania i sortowanych kolekcji przyjmuje parametr jako parametr.

sblundy
źródło
3
//Following is the example in jdk 1.8
package com;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class User {
    private String firstName;
    private String lastName;
    private Integer age;

    public Integer getAge() {
        return age;
    }

    public User setAge(Integer age) {
        this.age = age;
        return this;
    }

    public String getFirstName() {
        return firstName;
    }

    public User setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public String getLastName() {
        return lastName;
    }

    public User setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

}

public class MultiFieldsComparision {

    public static void main(String[] args) {
        List<User> users = new ArrayList<User>();

        User u1 = new User().setFirstName("Pawan").setLastName("Singh").setAge(38);
        User u2 = new User().setFirstName("Pawan").setLastName("Payal").setAge(37);
        User u3 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(60);
        User u4 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(43);
        User u5 = new User().setFirstName("Pawan").setLastName("Chamoli").setAge(44);
        User u6 = new User().setFirstName("Pawan").setLastName("Singh").setAge(5);

        users.add(u1);
        users.add(u2);
        users.add(u3);
        users.add(u4);
        users.add(u5);
        users.add(u6);

        System.out.println("****** Before Sorting ******");

        users.forEach(user -> {
            System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
        });

        System.out.println("****** Aftre Sorting ******");

        users.sort(
                Comparator.comparing(User::getFirstName).thenComparing(User::getLastName).thenComparing(User::getAge));

        users.forEach(user -> {
            System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
        });

    }

}
Pawan
źródło
3

Implementacja tego samego kodu jest tutaj, jeśli musimy posortować obiekt Person na podstawie wielu pól.

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

public class Person {

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

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

public String getLastName() {
    return lastName;
}

public void setLastName(String lastName) {
    this.lastName = lastName;
}

public int getAge() {
    return age;
}

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

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


static class PersonSortingComparator implements Comparator<Person> {

    @Override
    public int compare(Person person1, Person person2) {

        // for first name comparison
        int firstNameCompare = person1.getFirstName().compareTo(person2.getFirstName());

        // for last name comparison
        int lastNameCompare = person1.getLastName().compareTo(person2.getLastName());

        // for last name comparison
        int ageCompare = person1.getAge() - person2.getAge();

        // Now comparing
        if (firstNameCompare == 0) {
            if (lastNameCompare == 0) {
                return ageCompare;
            }
            return lastNameCompare;
        }
        return firstNameCompare;
    }
}

public static void main(String[] args) {
    Person person1 = new Person("Ajay", "Kumar", 27);
    Person person2 = new Person("Ajay","Gupta", 23);
    Person person3 = new Person("Ajay","Kumar", 22);


    ArrayList<Person> persons = new ArrayList<>();
    persons.add(person1);
    persons.add(person2);
    persons.add(person3);


    System.out.println("Before Sorting:\n");
    for (Person person : persons) {
        System.out.println(person.firstName + " " + person.lastName + " " + person.age);
    }

    Collections.sort(persons, new PersonSortingComparator());

    System.out.println("After Sorting:\n");
    for (Person person : persons) {
        System.out.println(person.firstName + " " + person.lastName + " " + person.age);
    }
}

}
Sachchit Bansal
źródło
2
//here threshold,buyRange,targetPercentage are three keys on that i have sorted my arraylist 
final Comparator<BasicDBObject> 

    sortOrder = new Comparator<BasicDBObject>() {
                    public int compare(BasicDBObject e1, BasicDBObject e2) {
                        int threshold = new Double(e1.getDouble("threshold"))
                        .compareTo(new Double(e2.getDouble("threshold")));
                        if (threshold != 0)
                            return threshold;

                        int buyRange = new Double(e1.getDouble("buyRange"))
                        .compareTo(new Double(e2.getDouble("buyRange")));
                        if (buyRange != 0)
                            return buyRange;

                        return (new Double(e1.getDouble("targetPercentage")) < new Double(
                                e2.getDouble("targetPercentage")) ? -1 : (new Double(
                                        e1.getDouble("targetPercentage")) == new Double(
                                                e2.getDouble("targetPercentage")) ? 0 : 1));
                    }
                };
                Collections.sort(objectList, sortOrder);
Pradeep Singh
źródło
Przyszedłem na to pytanie, ponieważ mój kod zaczął polubić twoją odpowiedź;)
Groth
0

Jeśli wdrożysz porównywalny interfejs , będziesz chciał wybrać jedną prostą właściwość do zamówienia. Nazywa się to porządkiem naturalnym. Pomyśl o tym jako o wartości domyślnej. Jest zawsze używany, gdy nie ma określonego komparatora. Zwykle jest to nazwa, ale twój przypadek użycia może wymagać czegoś innego. Możesz swobodnie korzystać z dowolnej liczby innych Komparatorów, które możesz dostarczyć do różnych interfejsów API do kolekcji, aby zastąpić naturalne uporządkowanie.

Zauważ też, że zazwyczaj jeśli a.compareTo (b) == 0, to a.equals (b) == true. Jest ok, jeśli nie, ale są skutki uboczne, o których należy pamiętać. Zobacz doskonałe javadoki w interfejsie porównywalnym, a znajdziesz wiele świetnych informacji na ten temat.

Mark Renouf
źródło
0

Po blogu podano dobry przykład komparatora

http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * This is a chained comparator that is used to sort a list by multiple
 * attributes by chaining a sequence of comparators of individual fields
 * together.
 *
 */
public class EmployeeChainedComparator implements Comparator<Employee> {

    private List<Comparator<Employee>> listComparators;

    @SafeVarargs
    public EmployeeChainedComparator(Comparator<Employee>... comparators) {
        this.listComparators = Arrays.asList(comparators);
    }

    @Override
    public int compare(Employee emp1, Employee emp2) {
        for (Comparator<Employee> comparator : listComparators) {
            int result = comparator.compare(emp1, emp2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }
}

Komponent wywołujący:

Collections.sort(listEmployees, new EmployeeChainedComparator(
                new EmployeeJobTitleComparator(),
                new EmployeeAgeComparator(),
                new EmployeeSalaryComparator())
        );
Vaquar Khan
źródło
0

Począwszy od odpowiedzi Steve'a, operator trójskładnikowy może być użyty:

public int compareTo(Person other) {
    int f = firstName.compareTo(other.firstName);
    int l = lastName.compareTo(other.lastName);
    return f != 0 ? f : l != 0 ? l : Integer.compare(age, other.age);
}
Gerold Broser
źródło
0

Łatwo jest porównać dwa obiekty metodą hashcode w java`

public class Sample{

  String a=null;
  String b=null;

  public Sample(){
      a="s";
      b="a";
  }
  public Sample(String a,String b){
      this.a=a;
      this.b=b;
  }
  public static void main(String args[]){
      Sample f=new Sample("b","12");
      Sample s=new Sample("b","12");
      //will return true
      System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));

      //will return false
      Sample f=new Sample("b","12");
      Sample s=new Sample("b","13");
      System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));

}
Exodus
źródło
Proszę nie rób tego. Hashcodes nie powinny być używane do porównań równości, ale do indeksowania tablicy mieszającej. Zderzenia mieszające mogą spowodować równość dwóch różnych obiektów. Nawet tabele skrótów polegają na prawdziwej równości, jeśli wystąpią kolizje skrótów.
Noel Widmer,
0

Zwykle zastępuję moje compareTo() metodę w ten sposób, gdy muszę przeprowadzić wielopoziomowe sortowanie.

public int compareTo(Song o) {
    // TODO Auto-generated method stub
    int comp1 = 10000000*(movie.compareTo(o.movie))+1000*(artist.compareTo(o.artist))+songLength;
    int comp2 = 10000000*(o.movie.compareTo(movie))+1000*(o.artist.compareTo(artist))+o.songLength;
    return comp1-comp2;
} 

Tutaj pierwszeństwo ma nazwa filmu, następnie wykonawca, a na koniec songLength. Musisz tylko upewnić się, że mnożniki są wystarczająco odległe, aby nie przekraczać wzajemnie swoich granic.

jayanth
źródło