Szukaj…


Składnia

  • klasa publiczna MyClass implementuje porównywalne <MyClass >
  • klasa publiczna MyComparator implementuje Komparator <SomeOtherClass >
  • public int compareTo (MyClass other)
  • public int porównaj (SomeOtherClass o1, SomeOtherClass o2)

Uwagi

Wdrażając metodę compareTo(..) , która zależy od double , nie wykonuj następujących czynności:

public int comareTo(MyClass other) {
    return (int)(doubleField - other.doubleField); //THIS IS BAD
}

Obcięcie spowodowane rzutowaniem (int) spowoduje, że metoda czasami nieprawidłowo zwraca 0 zamiast liczby dodatniej lub ujemnej, a zatem może prowadzić do błędów w porównywaniu i sortowaniu.

Zamiast tego najprostszą poprawną implementacją jest użycie Double.compare jako takiego:

public int comareTo(MyClass other) {
    return Double.compare(doubleField,other.doubleField); //THIS IS GOOD
} 

Od wersji Java 1.2 istnieje ogólna wersja Comparable<T> , po prostu Comparable . Poza interfejsem ze starszym kodem zawsze lepiej jest zaimplementować ogólną wersję Comparable<T> , ponieważ nie wymaga rzutowania po porównaniu.


Jest bardzo standardowe, aby klasa była porównywalna do siebie, jak w:

public class A implements Comparable<A>

Chociaż można oderwać się od tego paradygmatu, zachowaj ostrożność.


Comparator<T> może być nadal używany w instancjach klasy, jeśli klasa ta implementuje Comparable<T> . W takim przypadku zastosowana zostanie logika Comparator ; naturalne uporządkowanie określone w implementacji Comparable zostanie zignorowane.

Sortowanie listy za pomocą porównywalnego lub komparator

Powiedzmy, że pracujemy nad klasą reprezentującą Osobę po imieniu i nazwisku. Stworzyliśmy do tego podstawową klasę i zaimplementowaliśmy odpowiednie metody equals i hashCode .

public class Person {

    private final String lastName; //invariant - nonnull
    private final String firstName; //invariant - nonnull

    public Person(String firstName, String lastName){
        this.firstName = firstName != null ? firstName : "";
        this.lastName = lastName != null ? lastName : "";
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String toString() {
        return lastName + ", " + firstName;
    }

    @Override
    public boolean equals(Object o) {
        if (! (o instanceof Person)) return false;
        Person p = (Person)o;
        return firstName.equals(p.firstName) && lastName.equals(p.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName);
    }
}

Teraz chcielibyśmy posortować listę obiektów Person według ich nazw, na przykład w następującym scenariuszu:

public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //This currently won't work.
}

Niestety, jak zaznaczono, powyższe obecnie się nie kompiluje. Collections.sort(..) wie, jak posortować listę tylko wtedy, gdy elementy na tej liście są porównywalne lub podano niestandardową metodę porównania.

Gdybyś został poproszony o posortowanie następującej listy: 1,3,5,4,2 , nie miałbyś problemu z odpowiedzią na pytanie 1,2,3,4,5 . Wynika to z faktu, że liczby całkowite (zarówno w Javie, jak i matematycznie) mają naturalne uporządkowanie , standardowe, domyślne uporządkowanie bazowe porównania. Aby nadać naszej klasie Person naturalne uporządkowanie, wdrażamy Comparable<Person> , co wymaga implementacji metody compareTo(Person p):

public class Person implements Comparable<Person> {

    private final String lastName; //invariant - nonnull
    private final String firstName; //invariant - nonnull

    public Person(String firstName, String lastName) {
        this.firstName = firstName != null ? firstName : "";
        this.lastName = lastName != null ? lastName : "";
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String toString() {
        return lastName + ", " + firstName;
    }

    @Override
    public boolean equals(Object o) {
        if (! (o instanceof Person)) return false;
        Person p = (Person)o;
        return firstName.equals(p.firstName) && lastName.equals(p.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName);
    }

    @Override
    public int compareTo(Person other) {
        // If this' lastName and other's lastName are not comparably equivalent,
        // Compare this to other by comparing their last names.
        // Otherwise, compare this to other by comparing their first names
        int lastNameCompare = lastName.compareTo(other.lastName);
        if (lastNameCompare != 0) {
            return lastNameCompare;
        } else {
            return firstName.compareTo(other.firstName);
        }
    }
}

Teraz podana główna metoda będzie działać poprawnie

public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //Now functions correctly

    //people is now sorted by last name, then first name:
    // --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}

Jeśli jednak nie chcesz lub nie możesz zmodyfikować klasy Person , możesz podać niestandardowy Comparator<T> który obsługuje porównanie dowolnych dwóch obiektów Person . Jeśli zostaniesz poproszony o posortowanie następującej listy: circle, square, rectangle, triangle, hexagon , nie możesz, ale jeśli zostaniesz poproszony o posortowanie tej listy na podstawie liczby rogów , możesz. Właśnie dlatego zapewnienie komparatora instruuje Javę, jak porównać dwa normalnie nieporównywalne obiekty.

public class PersonComparator implements Comparator<Person> {

    public int compare(Person p1, Person p2) {
        // If p1's lastName and p2's lastName are not comparably equivalent,
        // Compare p1 to p2 by comparing their last names.
        // Otherwise, compare p1 to p2 by comparing their first names
        if (p1.getLastName().compareTo(p2.getLastName()) != 0) {
            return p1.getLastName().compareTo(p2.getLastName());
        } else {
            return p1.getFirstName().compareTo(p2.getFirstName());
        }
    }
}

//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //Illegal, Person doesn't implement Comparable.
    Collections.sort(people, new PersonComparator()); //Legal

    //people is now sorted by last name, then first name:
    // --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}

Komparatory mogą być również tworzone / używane jako anonimowa klasa wewnętrzna

//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //Illegal, Person doesn't implement Comparable.

    Collections.sort(people, new PersonComparator()); //Legal

    //people is now sorted by last name, then first name:
    // --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald

    //Anonymous Class
    Collections.sort(people, new Comparator<Person>() { //Legal
        public int compare(Person p1, Person p2) {
            //Method code...
        }
    });
}
Java SE 8

Komparatory oparte na wyrażeniu lambda

Od wersji Java 8 komparatory mogą być również wyrażane jako wyrażenia lambda

    //Lambda
    Collections.sort(people, (p1, p2) -> { //Legal
        //Method code....
    });

Domyślne metody komparatora

Ponadto istnieją ciekawe domyślne metody interfejsu komparatora do budowania komparatorów: poniżej buduje się komparator porównując według lastName a następnie firstName .

Collections.sort(people, Comparator.comparing(Person::getLastName)
                                .thenComparing(Person::getFirstName));

Odwracanie kolejności komparatora

Dowolny komparator można również łatwo odwrócić za pomocą reversedMethod który zmieni kolejność rosnącą na malejącą.

Metody porównywania i porównywania

Comparable<T> interfejs Comparable<T> wymaga jednej metody:

public interface Comparable<T> {

    public int compareTo(T other);

}

Interfejs Comparator<T> wymaga jednej metody:

public interface Comparator<T> {

    public int compare(T t1, T t2);

}

Te dwie metody robią w zasadzie to samo, z jedną niewielką różnicą: compareTo porównuje this z other , podczas gdy compare porównuje t1 do t2 , nie przejmując się this .

Oprócz tej różnicy obie metody mają podobne wymagania. W szczególności (dla CompareTo) porównuje ten obiekt z określonym obiektem w celu zamówienia. Zwraca ujemną liczbę całkowitą, zero lub dodatnią liczbę całkowitą, ponieważ ten obiekt jest mniejszy, równy lub większy niż określony obiekt. Tak więc, dla porównania i a b :

  • Jeśli a < b , a.compareTo(b) i compare(a,b) powinny zwrócić ujemną liczbę całkowitą, a b.compareTo(a) i compare(b,a) powinny zwrócić dodatnią liczbę całkowitą
  • Jeśli a > b , a.compareTo(b) i compare(a,b) powinny zwrócić dodatnią liczbę całkowitą, a b.compareTo(a) i compare(b,a) powinny zwrócić ujemną liczbę całkowitą
  • Jeśli równa a b dla porównania, wszystkie porównania powinien powrócić 0 .

Naturalne (porównywalne) vs jawne (porównawcze) sortowanie

Istnieją dwie metody Collections.sort() :

  • Taki, który przyjmuje List<T> jako parametr, w którym T musi implementować Porównywalny i przesłonić metodę compareTo() , która określa kolejność sortowania.
  • Taki, który jako argument przyjmuje List i Komparator, przy czym Komparator określa kolejność sortowania.

Po pierwsze, oto klasa Person, która implementuje porównywalne:

public class Person implements Comparable<Person> {         
    private String name;  
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }         

    @Override
    public int compareTo(Person o) {
        return this.getAge() - o.getAge();
    }
    @Override
    public String toString() {
        return this.getAge()+"-"+this.getName();
    }

}

Oto, w jaki sposób użyjesz powyższej klasy do sortowania listy w naturalnym porządku jej elementów, zdefiniowanej przez przesłonięcie metody compareTo() :

//-- usage
List<Person> pList = new ArrayList<Person>();
            Person p = new Person();
            p.setName("A");
            p.setAge(10);
            pList.add(p);
            p = new Person();
            p.setName("Z");
            p.setAge(20);
            pList.add(p);
            p = new Person();
            p.setName("D");
            p.setAge(30);
            pList.add(p);
            
            //-- natural sorting i.e comes with object implementation, by age
            Collections.sort(pList);

            System.out.println(pList);

Oto jak użyłbyś anonimowego wbudowanego Komparatora do sortowania Listy, która nie implementuje Porównywalnego, lub w tym przypadku do sortowania Listy w kolejności innej niż naturalna kolejność:

            //-- explicit sorting, define sort on another property here goes with name
            Collections.sort(pList, new Comparator<Person>() {

                @Override
                public int compare(Person o1, Person o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });            
            System.out.println(pList);

Sortowanie wpisów mapy

Począwszy od Java 8, w interfejsie Map.Entry istnieją domyślne metody umożliwiające sortowanie iteracji map.

Java SE 8
Map<String, Integer> numberOfEmployees = new HashMap<>();
numberOfEmployees.put("executives", 10);
numberOfEmployees.put("human ressources", 32);
numberOfEmployees.put("accounting", 12);
numberOfEmployees.put("IT", 100);

// Output the smallest departement in terms of number of employees
numberOfEmployees.entrySet().stream()
    .sorted(Map.Entry.comparingByValue())
    .limit(1)
    .forEach(System.out::println);   // outputs : executives=10

Oczywiście można ich również używać poza interfejsem API strumienia:

Java SE 8
List<Map.Entry<String, Integer>> entries = new ArrayList<>(numberOfEmployees.entrySet());
Collections.sort(entries, Map.Entry.comparingByValue());

Tworzenie komparatora przy użyciu metody porównywania

Comparator.comparing(Person::getName)

Spowoduje to utworzenie komparatora dla klasy Person która używa tej nazwy osoby jako źródła porównania. Możliwe jest również użycie wersji metody do porównania długich, całkowitych i podwójnych. Na przykład:

Comparator.comparingInt(Person::getAge)

Odwrócone zamówienie

Aby utworzyć komparator narzucający odwrotną kolejność, użyj metody reverse reversed() :

Comparator.comparing(Person::getName).reversed()

Łańcuch komparatorów

Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName)

Spowoduje to utworzenie komparatora, który najpierw porównuje z nazwiskiem, a następnie porównuje z imieniem. Możesz połączyć tyle komparatorów, ile chcesz.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow