Przejdź do treści

Sortowanie obiektów przez Comparator i Collator

  • Kolekcje i strumienie
  • poziom: średni
  • aktualizacja: 16 sierpnia 2026

Comparator pozwala opisać porządek bez wpisywania Comparable na stałe do klasy danych — i dobrze, bo ten sam produkt raz układasz po nazwie, raz po cenie malejąco, a raz po dostępności. Komparatory łączysz przez thenComparing, odwracasz przez reversed i wpinasz zarówno w List.sort, jak i w strumień. Dla polskich napisów zwykłe String.compareTo odpada, bo porównuje kody Unicode. Collator zna zasady polskiego alfabetu, więc „Łódź” postawi między „Lublinem” a „Mławą”, a nie za literą Z — zobaczysz to w dwóch ostatnich linijkach wydruku.

JAVA SortowanieObiektow.java 91 linii Pobierz .java
import java.math.BigDecimal;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

/**
 * Sortowanie obiektów przez Comparator i Collator — skryptyJAVA.pl
 * Zapisz jako: SortowanieObiektow.java
 */
public class SortowanieObiektow {

    /** Prosty typ danych; rekord nie narzuca jednego, globalnego sposobu sortowania. */
    public record Produkt(String nazwa, String kategoria, BigDecimal cena, int stan) {
        public Produkt {
            if (stan < 0) { throw new IllegalArgumentException("Stan nie może być ujemny."); }
        }
    }

    /**
     * Buduje komparator zgodny z polskim alfabetem.
     * SECONDARY rozróżnia polskie znaki, ale ignoruje wielkość liter.
     */
    public static Comparator<String> polskiTekst() {
        Collator collator = Collator.getInstance(Locale.forLanguageTag("pl-PL"));
        collator.setStrength(Collator.SECONDARY);
        return collator::compare;
    }

    /** Sortuje kopię listy i wypisuje ją, nie zmieniając danych wejściowych. */
    private static void wypisz(String tytul, List<Produkt> produkty,
                               Comparator<Produkt> comparator) {
        System.out.println("\n--- " + tytul + " ---");
        produkty.stream().sorted(comparator).forEach(produkt ->
                System.out.printf(Locale.forLanguageTag("pl-PL"),
                        "%-13s | %-11s | %8.2f zł | %3d szt.%n",
                        produkt.nazwa(), produkt.kategoria(), produkt.cena(), produkt.stan()));
    }

    // ── PRZYKŁADY ───────────────────────────────────────────
    public static void main(String[] args) {
        List<Produkt> produkty = List.of(
                new Produkt("Żelazko", "AGD", new BigDecimal("189.90"), 8),
                new Produkt("Ładowarka", "Elektronika", new BigDecimal("89.00"), 21),
                new Produkt("Ćwiek", "Narzędzia", new BigDecimal("0.45"), 900),
                new Produkt("Zegar", "Elektronika", new BigDecimal("129.00"), 12),
                new Produkt("Ącki notes", "Papiernicze", new BigDecimal("14.50"), 35),
                new Produkt("Śrubokręt", "Narzędzia", new BigDecimal("24.90"), 0),
                new Produkt("Młotek", "Narzędzia", new BigDecimal("54.90"), 16),
                new Produkt("Żarówka", "Elektronika", new BigDecimal("18.00"), 70));

        Comparator<String> poPolsku = polskiTekst();
        Comparator<Produkt> poNazwie = Comparator.comparing(Produkt::nazwa, poPolsku);
        wypisz("Nazwa według polskiego alfabetu", produkty, poNazwie);

        Comparator<Produkt> poKategoriiINazwie =
                Comparator.comparing(Produkt::kategoria, poPolsku)
                        .thenComparing(Produkt::nazwa, poPolsku);
        wypisz("Kategoria, potem nazwa", produkty, poKategoriiINazwie);

        Comparator<Produkt> poCenieMalejaco =
                Comparator.comparing(Produkt::cena, Comparator.reverseOrder())
                        .thenComparing(Produkt::nazwa, poPolsku);
        wypisz("Cena malejąco, remisy po nazwie", produkty, poCenieMalejaco);

        Comparator<Produkt> dostepneNajpierw =
                Comparator.comparing((Produkt produkt) -> produkt.stan() == 0)
                        .thenComparingInt(Produkt::stan)
                        .thenComparing(Produkt::nazwa, poPolsku);
        wypisz("Dostępne najpierw, potem stan", produkty, dostepneNajpierw);

        // List.sort zmienia listę, dlatego tworzymy modyfikowalną kopię.
        List<Produkt> kopia = new ArrayList<>(produkty);
        kopia.sort(poNazwie.reversed());
        System.out.println("\nNazwy od końca: "
                + kopia.stream().map(Produkt::nazwa).toList());

        // Rekord przepuszcza null w nazwie, więc przed NPE musi bronić komparator.
        List<Produkt> zBrakami = new ArrayList<>(produkty);
        zBrakami.add(new Produkt(null, "Nieznana", new BigDecimal("9.90"), 3));
        Comparator<Produkt> nazwaNullNaKoniec =
                Comparator.comparing(Produkt::nazwa, Comparator.nullsLast(poPolsku));
        System.out.println("\nNazwy, brak na końcu: " + zBrakami.stream()
                .sorted(nazwaNullNaKoniec).map(Produkt::nazwa).toList());

        List<String> miasta = List.of("Żory", "Zabrze", "Łódź", "Lublin", "Mława");
        System.out.println("Naturalnie: " + miasta.stream().sorted().toList());
        System.out.println("Po polsku:   " + miasta.stream().sorted(poPolsku).toList());
    }
}

Jak użyć tego skryptu

  1. Zapisz kod jako SortowanieObiektow.java (pod inną nazwą kompilator się nie zgodzi — plik nosi nazwę klasy publicznej) i uruchom przez java SortowanieObiektow.java. Porównaj zwłaszcza dwie ostatnie linie: porządek naturalny napisu i porządek polskiego Collator.
  2. Zastąp rekord Produkt własnym typem, a odwołania takie jak Produkt::nazwa odpowiednimi metodami dostępowymi. Komparator nie wymaga zmian w samej klasie danych.
  3. Łącz kolejne kryteria przez thenComparing, thenComparingInt lub thenComparingDouble. Kryterium po prawej jest używane tylko wtedy, gdy wcześniejsze porównanie dało remis.
  4. Użyj lista.sort(comparator), gdy chcesz zmienić istniejącą modyfikowalną listę, albo lista.stream().sorted(comparator).toList(), gdy potrzebujesz nowego, niemodyfikowalnego wyniku.

Warto wiedzieć

  • Do tekstu, który zobaczy użytkownik, nie używaj String.compareTo — porządek kodów Unicode nie ma nic wspólnego z polskim alfabetem. Właściwe reguły daje Collator utworzony dla języka pl-PL. Pamiętaj przy tym, że Collator jest modyfikowalny i nie jest bezpieczny wątkowo: twórz osobny egzemplarz na operację lub wątek, zamiast trzymać jeden współdzielony w statycznym polu.
  • reversed() odwraca cały komparator zbudowany przed tym wywołaniem, a nie ostatni dołączony człon — na tym najłatwiej się przejechać. Jeśli malejąco ma iść tylko jedno pole, odwróć komparator tego pola przy dołączaniu: Comparator.comparing(Produkt::cena, Comparator.reverseOrder()).thenComparing(Produkt::nazwa, poPolsku) da cenę malejąco przy nazwach nadal rosnąco — dokładnie tak działa poCenieMalejaco w przykładzie.
  • Rekord waliduje tylko stan, więc new Produkt("X", "Y", null, 1) przejdzie bez mrugnięcia okiem, a wywróci się dopiero przy sortowaniu komparatorem poCenieMalejaco: użyty tam Comparator.reverseOrder() nie obsługuje null. Jeśli brak ceny jest dopuszczalny, użyj Comparator.nullsLast(Comparator.reverseOrder()); jeśli nie, dopisz Objects.requireNonNull(cena, "cena") do konstruktora rekordu. Przykład nazwaNullNaKoniec pokazuje analogiczną obsługę braku nazwy przez Comparator.nullsLast(poPolsku).
  • Komparator musi być przechodni i najlepiej spójny z equals. Nie buduj go przez odejmowanie, np. a.stan() - b.stan() — przy dużych wartościach przepełnienie int odwróci wynik; użyj comparingInt albo Integer.compare. Miej też z tyłu głowy, że polskiTekst() z siłą SECONDARY spójny z equals nie jest: new TreeSet<>(polskiTekst()) po dodaniu „Zegar”, „ZEGAR” i „zegar” zawiera jeden element, dwa znikają po cichu. Do zbiorów i map z kluczem tekstowym bierz HashSet/HashMap, a Collator zostaw do sortowania.
#sortowanie #collator #comparator #polskie znaki #record #lambda

Podobne skrypty