Przejdź do treści

Sortowanie polskich napisów (Collator)

  • Teksty i String
  • poziom: średni
  • aktualizacja: 16 sierpnia 2026

String.compareTo porównuje kody Unicode, a nie litery polskiego alfabetu — dlatego „Łódź” i „Ćmielów” lądują za „Zebrą”, choć polski alfabet umieszcza je odpowiednio między L i M oraz C i D. Użytkownik nie pomyśli wtedy „ciekawa kolejność”, tylko „ta strona jest zepsuta”. Collator zna reguły wybranego języka: kolejność a, ą, b, c, ć, potrafi ignorować wielkość liter i uznaje za równoważne różne zapisy Unicode tego samego znaku — złożone ż i z ze znakiem kropki nad literą. Dostajesz zwykły Comparator<String>, więc wepniesz go wszędzie tam, gdzie działa sort, także przy rekordach.

JAVA PolskieSortowanie.java 82 linii Pobierz .java
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

/**
 * Sortowanie i porównywanie napisów według polskiego alfabetu — skryptyJAVA.pl
 * Zapisz jako: PolskieSortowanie.java
 */
public final class PolskieSortowanie {

    private static final Locale POLSKI = Locale.forLanguageTag("pl-PL");

    /** Przykładowy obiekt sortowany najpierw po nazwisku, potem po imieniu. */
    public record Osoba(String imie, String nazwisko) {}

    private PolskieSortowanie() {
        // Klasa zawiera wyłącznie metody statyczne.
    }

    /**
     * Tworzy komparator zgodny z polskimi regułami. Wartość null trafia na koniec.
     * SECONDARY ignoruje wielkość liter, a TERTIARY bierze ją pod uwagę.
     */
    public static Comparator<String> komparator(boolean rozrozniajWielkoscLiter) {
        Collator collator = Collator.getInstance(POLSKI);
        collator.setStrength(rozrozniajWielkoscLiter ? Collator.TERTIARY : Collator.SECONDARY);

        Comparator<String> porownanie = collator::compare;
        return Comparator.nullsLast(porownanie);
    }

    /** Zwraca nową, posortowaną listę i nie zmienia kolekcji przekazanej przez wywołującego. */
    public static List<String> sortuj(List<String> napisy) {
        List<String> wynik = new ArrayList<>(napisy);
        wynik.sort(komparator(false));
        return wynik;
    }

    /**
     * Porównuje napisy po polsku, ignorując wielkość liter; poprawnie obsługuje też null.
     * Każde wywołanie buduje nowy Collator — w pętli sięgnij raczej po gotowy komparator.
     */
    public static boolean takieSame(String pierwszy, String drugi) {
        if (pierwszy == null || drugi == null) {
            return pierwszy == drugi;
        }
        return komparator(false).compare(pierwszy, drugi) == 0;
    }

    /** Sortuje osoby według nazwiska i imienia, w obu polach według polskiego alfabetu. */
    public static List<Osoba> sortujOsoby(List<Osoba> osoby) {
        Comparator<String> teksty = komparator(false);
        Comparator<Osoba> poNazwisku = Comparator.comparing(Osoba::nazwisko, teksty)
                .thenComparing(Osoba::imie, teksty);
        List<Osoba> wynik = new ArrayList<>(osoby);
        wynik.sort(poNazwisku);
        return wynik;
    }

    // ── PRZYKŁADY ───────────────────────────────────────────
    public static void main(String[] args) {
        List<String> miasta = List.of(
                "Zabrze", "Łódź", "Lublin", "Śrem", "Sopot", "Ćmielów", "Cieszyn", "Żory");

        List<String> unicode = new ArrayList<>(miasta);
        unicode.sort(Comparator.naturalOrder());
        System.out.println("Unicode: " + unicode);
        System.out.println("Po polsku: " + sortuj(miasta));

        System.out.println("ŁÓDŹ = łódź: " + takieSame("ŁÓDŹ", "łódź"));
        System.out.println("Żaba = Z + kropka: " + takieSame("Żaba", "Z\u0307aba"));

        List<Osoba> osoby = List.of(
                new Osoba("Anna", "Żak"),
                new Osoba("Łukasz", "Lemański"),
                new Osoba("Jan", "Łuczak"),
                new Osoba("Celina", "Ćwik"));
        sortujOsoby(osoby).forEach(System.out::println);
    }
}

Jak użyć tego skryptu

  1. Zapisz przykład jako PolskieSortowanie.java — plik z klasą publiczną nosi jej nazwę — i uruchom poleceniem java PolskieSortowanie.java. Pierwsze dwie listy pokażą różnicę między porządkiem Unicode a polskim alfabetem.
  2. Najprościej wywołaj PolskieSortowanie.sortuj(lista). Dostaniesz nową listę, a kolejność elementów w oryginalnej liście pozostanie bez zmian.
  3. Do własnego sortowania pobierz Comparator<String> przez komparator(false) — utwórz go raz i przekazuj dalej, zamiast wołać w pętli. Przekaż true, jeżeli wielkie i małe litery mają wpływać na kolejność.
  4. Przy obiektach użyj komparatora jako drugiego argumentu Comparator.comparing, a kolejne pola dołącz przez thenComparing, tak jak w metodzie sortujOsoby.

Warto wiedzieć

  • String.compareTo, CASE_INSENSITIVE_ORDER i gołe sorted() nie znają polskiego alfabetu — układają znaki według kodów Unicode. Dla identyfikatorów technicznych to w porządku, ale lista nazwisk, miast czy produktów pokazana użytkownikowi wygląda wtedy po prostu na zepsutą.
  • Siła SECONDARY ignoruje wielkość liter, ale zachowuje istotne różnice językowe, np. między l i ł. TERTIARY dodatkowo rozróżnia wielką i małą literę. PRIMARY spłaszcza jeszcze więcej i nadaje się wyłącznie do wyszukiwania „mniej więcej”, nie do porównań.
  • Pułapka, o której łatwo zapomnieć: ten komparator nie jest spójny z equals przy żadnej sile. Wrzuć „Kowalski”, „KOWALSKI” i „kowalski” do new TreeSet<>(komparator(false)), a zostanie jeden element — dwa przepadną bez słowa ostrzeżenia. Tak samo zachowa się TreeMap i Collections.binarySearch z tym komparatorem. Do sprawdzania dokładnej równości używaj equals; takieSame odpowiada na pytanie „czy to ten sam tekst językowo”, a nie „czy to identyczne napisy”.
  • Collator jest mutowalny i nie gwarantuje bezpieczeństwa wątkowego. Komparator z przykładu służy do sortowania sekwencyjnego; nie współdziel go między wątkami ani nie używaj w parallelStream lub Arrays.parallelSort — do pracy równoległej twórz osobny komparator dla każdego wątku. W obrębie jednego wątku rób odwrotnie: zbuduj komparator raz i przekazuj dalej, bo każde wywołanie komparator(...) tworzy nowy Collator. W pętli kosztuje to około dwa razy więcej czasu niż porównania gotowym komparatorem.
#sortowanie #collator #comparator #locale #polskie znaki #unicode

Podobne skrypty