Czy już wiecie jak możemy posortować dowolną kolekcję w języku Java, która przyjmuje obiekty typu klasowego? Mamy dwie drogi: interfejs "Comparator" lub interfejs "Comparable" (drugi będzie przedstawiony później). Dzięki nim, jesteśmy w stanie ustalić kryterium w jaki sposób mają być sortowane obiekty o którym pisałem w pierwszej części.

INTERFEJS "COMPARATOR". SPOSOBY ZAPISÓW

Różnica między nimi polega na miejscu osadzenia naszego kryterium. W pierwszym możemy je ustalić w formie wyrażenia lambda lub klasy wewnętrznej na zewnątrz klasy, której instancje będziemy chcieli na swój własny sposób sortować, a drugie nadaje się do przydzielenia konkretnego filtru wewnątrz klasy. Kolejną różnicą jest później korzystanie z odpowiedniego nagłówka metody sortującej, ale do tego przejdziemy w odpowiednim czasie.

Muszę uświadomić Wam jednak, że ten temat prezentuje pewien "cosik", który spokojnie potraktowałbym jako temat na poziomie mocno zaawansowanym. Klasy kolekcji korzystają z "typów generycznych" ("generic types" albo "generics"), nazywanych również "typami ogólnymi". Aby rozpoznać je w kodzie, należy dostrzec parę nawiasów kątowych (<>). W środku nich umieszcza się dużą literę według powszechnie przyjętej konwencji nazewniczej (najczęściej to T lub E). Typy ogólne wymagają całej serii odrębnych artykułów, aby dostatecznie zobrazować Wam powagę sytuacji, a zarazem możliwości jakie one dają piszącemu. Tu znajdziecie pierwszą część przedstawiającą typy generyczne. Na tę chwilę wystarczy Wam wiedzieć, że jak widzicie literę T w nawiasach kątowych, to jest to typ generyczny.

Teraz zrobimy sobie przykład kolekcji, która pobiera instancje typu "Book". Książka będzie klasą posiadająca kilka składowych wymuszającą przypadek kiedy nie ma jak "domyślnie" je posortować w żaden sposób. "Comparator" możemy zapisać na kilka sposobów. Albo tak:

Comparator<T> comparator = new Comparator<T>()
{
	@Override
	public int compare(T ta, T tb)
	{
		return [liczba lub wyrażenie liczbowe];
	}
};

albo tak:

Comparator<T> comparator = (ta, tb) ->
{
	return [liczba lub wyrażenie liczbowe];
};

a jeszcze krócej można zapisać w taki sposób:

Comparator<T> comparator = (ta, tb) -> [liczba lub wyrażenie liczbowe];

Trzeba zwrócić uwagę, że "Comparator" zdradza w tym momencie parę istotnych szczegółów:

  • przyjmuje jedną metodę "compare" zawierającą dwa parametry formalne typu generycznego
  • metoda zwraca liczbę całkowitą "int"

To już sugeruje mniej więcej w jaki sposób będzie wyglądać sortowanie, aczkolwiek trzeba podać więcej informacji. Sortowanie polega na zbadaniu wartości zwracanej przez metodę "compare". Te dwa parametry są po to, aby porównywać do siebie sąsiadujące ze sobą argumenty kolekcji celem podjęcia decyzji który z nich jest "lepszy" (tylko w sensie ustalenia pierwszeństwa). Robi się to przeważnie przy pomocy getterów pobierając konkretne dane składowe jakie mają wpływać na wynik i to staje się odpowiedzią w jaki sposób można utworzyć klasyfikowanie. Są trzy możliwości:

  1. jeśli wartość jest mniejsza bądź równa -1, to obiekt jest "niższego rzędu"
  2. jeśli wartość jest równa 0, to obiekty są sobie równe (ex aequo)
  3. jeśli wartość jest większa bądź równa 1, to obiekt jest "wyższego rzędu"

Wynika z tego, że "Comparator" sortuje na podstawie porównania określanego przedziałem liczbowym.

Comparator. Sortowanie poprzez przedział liczbowy

Interfejs "Comparator" sortuje według podanego kryterium pobieranego z metody "compare".

Na sam koniec skwitujemy to sobie prostym przykładowym kodem. Skompilujcie to sobie zwracając uwagę na zapis porównania w kodzie:

  • KLASA "Main"
public class Main
{
	public static void main(String[] args)
	{
		new Launcher();
	}
}
  • KLASA "Book"
public class Book
{
	private final String title, author, publisher;
	private final int year;

	public Book(String author, String title, String publisher, int year)
	{
		this.author = author;
		this.title = title;
		this.publisher = publisher;
		this.year = year;
	}

	public int getYear()
	{
		return year;
	}

	@Override
	public String toString()
	{
		return author + ". " + title + ". " + publisher + ", " + year;
	}
}
  • KLASA "Launcher"
import java. util.ArrayList;
import java. util.Comparator;
import java. util.Collections;

public class Launcher
{
	private ArrayList<Book> books;

	public Launcher()
	{
		createInstances();
		addBooks();
		printBooks();
		sortBooks();
		printBooks();
	}

	private void createInstances()
	{
		books = new ArrayList<>();
	}

	private void addBooks()
	{
		books.add(new Book("Adam Mickiewicz", "Pan Tadeusz", "Aleksander Jełowicki", 1834));
		books.add(new Book("Gustaw Herling-Grudziński", "Inny świat", "Roy", 1951));
		books.add(new Book("Aleksander Głowacki", "Lalka", "Gebethner i Wolff", 1890));
	}

	private void printBooks()
	{
		System.out.println("\nWYPISYWANIE LISTY:");
		books.forEach(System.out::println);
	}

	private void sortBooks()
	{
		Comparator<Book> comparator = (b1, b2) -> b1.getYear() - b2.getYear();

		Collections.sort(books, comparator);
	}
}

Koniecznie przyjrzyjcie się metodzie "sortBooks". Mamy w nim nasz "Comparator" i zaraz po operatorze wyrażenia lambda, ustalamy kryterium. Za przykład ustaliłem kryterium dla sortowania na podstawie roku wydania książki. W ten sposób, książki zostaną posortowane według roku od najstarszego do najmłodszego. Gdy będziemy chcieli sortować w drugą stronę, zmieniamy miejscami nazwy odwołań tak, żeby "b2" było odjemną, a "b1", odjemnikiem albo odwracamy całą kolekcję za pomocą "reverseOrder".

Możecie również zastąpić lambdę poniższą funkcją:

Comparator.comparingInt(Book::getYear);

Tak mi zasugerował "IntelliJ".


Ciekawy temat? Być może, ale zrozumienie i wiedza na ten temat na pewno przy którymś projekcie się przyda.

PODOBNE ARTYKUŁY