CodeGym /Blog Java /Random-PL /Interfejs komparatora Java
Autor
Artem Divertitto
Senior Android Developer at United Tech

Interfejs komparatora Java

Opublikowano w grupie Random-PL
Nie tylko leniwi piszą o komparatorach i porównaniach w Javie. Nie jestem leniwy, więc proszę kochajcie i narzekajcie na jeszcze jedno wyjaśnienie. Mam nadzieję, że nie będzie to zbyteczne. I tak, ten artykuł jest odpowiedzią na pytanie: „ Czy potrafisz napisać komparator z pamięci? ” Mam nadzieję, że po przeczytaniu tego artykułu każdy będzie mógł napisać komparator z pamięci. Interfejs Javas Comparator - 1

Wstęp

Jak wiesz, Java jest językiem zorientowanym obiektowo. W rezultacie zwyczajowo manipuluje się obiektami w Javie. Ale prędzej czy później staniesz przed zadaniem porównania obiektów na podstawie jakiejś cechy. Na przykład : Załóżmy, że mamy jakąś wiadomość opisaną przez Messageklasę:

public static class Message {
    private String message;
    private int id;
        
    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
Umieść tę klasę w kompilatorze Java Tutorialspoint . Nie zapomnij również dodać instrukcji importu:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
W mainmetodzie utwórz kilka komunikatów:

public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
Zastanówmy się, co byśmy zrobili, gdybyśmy chcieli je porównać? Na przykład chcemy posortować według identyfikatora. A żeby stworzyć porządek, trzeba jakoś porównać obiekty, żeby zrozumieć, który obiekt powinien być pierwszy (czyli ten mniejszy), a który następny (czyli większy). Zacznijmy od klasy takiej jak java.lang.Object . Wiemy, że wszystkie klasy niejawnie dziedziczą Objectklasę. Ma to sens, ponieważ odzwierciedla koncepcję, że „wszystko jest obiektem” i zapewnia wspólne zachowanie dla wszystkich klas. Ta klasa wymaga, aby każda klasa miała dwie metody: → hashCode Metoda hashCodezwraca pewną liczbę (int) reprezentacja obiektu. Co to znaczy? Oznacza to, że jeśli utworzysz dwie różne instancje klasy, powinny one mieć różne hashCodes. Opis metody mówi tyle samo: „O ile jest to rozsądnie praktyczne, metoda hashCode zdefiniowana przez klasę Object zwraca różne liczby całkowite dla różnych obiektów”. Innymi słowy, dla dwóch różnych instances powinny istnieć różne hashCodes. Oznacza to, że ta metoda nie nadaje się do naszego porównania. → equals. Metoda equalsodpowiada na pytanie „czy te obiekty są sobie równe?” i zwraca boolean.” Domyślnie ta metoda ma następujący kod:

public boolean equals(Object obj) {
    return (this == obj);
}
Oznacza to, że jeśli ta metoda nie zostanie przesłonięta, zasadniczo mówi, czy odniesienia do obiektów pasują, czy nie. Nie tego chcemy dla naszych wiadomości, ponieważ interesują nas identyfikatory wiadomości, a nie odwołania do obiektów. I nawet jeśli przesłonimy tę equalsmetodę, możemy najwyżej liczyć na to, że dowiemy się, czy są one równe. A to nam nie wystarcza do ustalenia kolejności. Czego zatem potrzebujemy? Potrzebujemy czegoś, co można porównać. Ten, kto porównuje, jest Comparator. Otwórz API Java i znajdź Comparator . Rzeczywiście, istnieje java.util.Comparatorinterfejs java.util.Comparator and java.util.Comparable Jak widać, taki interfejs istnieje. Klasa, która ją implementuje, mówi: „Implementuję metodę, która porównuje obiekty”. Jedyną rzeczą, o której naprawdę musisz pamiętać, jest umowa porównawcza, która jest wyrażona w następujący sposób:

Comparator returns an int according to the following rules: 
  • It returns a negative int if the first object is smaller
  • It returns a positive int if the first object is larger
  • It returns zero if the objects are equal
Teraz napiszmy komparator. Będziemy musieli importować java.util.Comparator. Po instrukcji import dodaj do metody następującą treść main: Comparator<Message> comparator = new Comparator<Message>(); Oczywiście to nie zadziała, ponieważ Comparatorjest interfejsem. Więc dodajemy nawiasy klamrowe {}po nawiasach. Wpisz następującą metodę wewnątrz nawiasów klamrowych:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Nie musisz nawet pamiętać pisowni. Komparator to ten, który dokonuje porównania, to znaczy porównuje. Aby wskazać względną kolejność obiektów, zwracamy int. To w zasadzie wszystko. Ładne i łatwe. Jak widać na przykładzie oprócz Comparatora istnieje jeszcze jeden interfejs — java.lang.Comparable, który wymaga od nas zaimplementowania compareTometody. Ten interfejs mówi: „klasa, która mnie implementuje, umożliwia porównywanie instancji klasy”. Na przykład Integerimplementacja compareTo jest następująca:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 wprowadziła kilka fajnych zmian. Jeśli przyjrzysz się bliżej interfejsowi Comparator, zobaczysz @FunctionalInterfacenad nim adnotację. Ta adnotacja ma charakter informacyjny i informuje nas, że ten interfejs działa. Oznacza to, że ten interfejs ma tylko 1 metodę abstrakcyjną, która jest metodą bez implementacji. Co to nam daje? Teraz możemy napisać kod komparatora w następujący sposób:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Nazywamy zmienne w nawiasach. Java to zobaczy, ponieważ istnieje tylko jedna metoda, więc wymagana liczba i typy parametrów wejściowych są jasne. Następnie używamy operatora strzałki, aby przekazać je do tej części kodu. Co więcej, dzięki Javie 8 mamy teraz domyślne metody w interfejsach. Te metody pojawiają się domyślnie, gdy implementujemy interfejs. Interfejs Comparatorma kilka. Na przykład:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Jest jeszcze jedna metoda, która sprawi, że Twój kod będzie czystszy. Spójrz na powyższy przykład, w którym zdefiniowaliśmy nasz komparator. Co to robi? To dość prymitywne. Po prostu bierze obiekt i wyodrębnia pewną „porównywalną” wartość. Na przykład Integerimplements comparable, więc możemy wykonać operację porównania z wartościami pól id komunikatu. Ta prosta funkcja komparatora może być zapisana w następujący sposób:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Innymi słowy, mamy funkcję Comparator, która porównuje w następujący sposób: pobiera obiekty, używa getId()metody, aby uzyskać Comparablez nich a, a następnie używa compareTodo porównania. I nie ma bardziej okropnych konstrukcji. I na koniec chcę zwrócić uwagę na jeszcze jedną cechę. Komparatory można łączyć w łańcuchy. Na przykład:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Aplikacja

Zadeklarowanie komparatora okazuje się całkiem logiczne, nie sądzisz? Teraz musimy zobaczyć, jak i gdzie go używać. → Collections.sort(java.util.Collections) Możemy oczywiście w ten sposób sortować kolekcje. Ale nie każda kolekcja, tylko listy. Nie ma w tym nic niezwykłego, ponieważ listy to rodzaj kolekcji, w których dostęp do elementów uzyskuje się za pomocą ich indeksu. Pozwala to na zamianę drugiego elementu na trzeci element. Dlatego poniższa metoda sortowania dotyczy tylko list:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Tablice są również łatwe do sortowania. Ponownie, z tego samego powodu — dostęp do ich elementów uzyskuje się za pomocą indeksu. → Descendants of java.util.SortedSet and java.util.SortedMap Zapamiętasz to Seti Mapnie gwarantujesz kolejności przechowywania elementów. ALE mamy specjalne wdrożenia, które gwarantują zamówienie. A jeśli elementy kolekcji nie implementują java.util.Comparable, możemy przekazać a Comparatordo jego konstruktora:

Set<Message> msgSet = new TreeSet(comparator);
Stream API W Stream API, które pojawiło się w Javie 8, komparatory pozwalają uprościć pracę z elementami strumieniowymi. Załóżmy na przykład, że potrzebujemy sekwencji liczb losowych od 0 do 999 włącznie:

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Na tym moglibyśmy się zatrzymać, ale są jeszcze ciekawsze problemy. Załóżmy na przykład, że musisz przygotować plik Map, gdzie kluczem jest identyfikator wiadomości. Dodatkowo chcemy posortować te klucze, więc zaczniemy od następującego kodu:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Właściwie dostajemy HashMaptutaj. A jak wiemy nie gwarantuje to żadnego zamówienia. W rezultacie nasze elementy, które zostały posortowane według id, po prostu tracą kolejność. Niedobrze. Będziemy musieli nieco zmienić nasz kolektor:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
Kod zaczął wyglądać trochę bardziej przerażająco, ale teraz problem został rozwiązany poprawnie. Przeczytaj więcej o różnych grupach tutaj: Możesz stworzyć własnego kolekcjonera. Przeczytaj więcej tutaj: „Tworzenie niestandardowego kolektora w Javie 8” . Skorzystasz na tym, czytając dyskusję tutaj: „Java 8 list to map with stream” .

Pułapka na upadek

Comparatori Comparablesą dobre. Ale jest jeden niuans, o którym powinieneś pamiętać. Kiedy klasa wykonuje sortowanie, oczekuje, że twoja klasa może zostać przekonwertowana na plik Comparable. Jeśli tak nie jest, w czasie wykonywania zostanie wyświetlony komunikat o błędzie. Spójrzmy na przykład:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Wydaje się, że nic tu nie jest nie tak. Ale w rzeczywistości w naszym przykładzie zakończy się niepowodzeniem z błędem: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable A wszystko dlatego, że próbował posortować elementy ( SortedSetw końcu jest to ) ... ale nie mógł. Nie zapomnij o tym podczas pracy z SortedMapi SortedSet.

Więcej czytania:

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION