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.

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ą przezMessage
klasę:
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 main
metodzie 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ą Object
klasę. 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 hashCode
zwraca pewną liczbę (int
) reprezentacja obiektu. Co to znaczy? Oznacza to, że jeśli utworzysz dwie różne instancje klasy, powinny one mieć różne hashCode
s. 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 instance
s powinny istnieć różne hashCode
s. Oznacza to, że ta metoda nie nadaje się do naszego porównania. → equals
. Metoda equals
odpowiada 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ę equals
metodę, 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.Comparator
interfejs 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ż Comparator
jest 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 compareTo
metody. Ten interfejs mówi: „klasa, która mnie implementuje, umożliwia porównywanie instancji klasy”. Na przykład Integer
implementacja compare
To 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 @FunctionalInterface
nad 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 Comparator
ma 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 Integer
implements 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ć Comparable
z nich a, a następnie używa compareTo
do 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 Set
i Map
nie 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 Comparator
do 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 HashMap
tutaj. 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
Comparator
i Comparable
są 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 ( SortedSet
w końcu jest to ) ... ale nie mógł. Nie zapomnij o tym podczas pracy z SortedMap
i SortedSet
.
Więcej czytania: |
---|
GO TO FULL VERSION