CodeGym /Kursy /JAVA 25 SELF /Nowe kolekcje: SequencedCollection, SequencedSet, Sequenc...

Nowe kolekcje: SequencedCollection, SequencedSet, SequencedMap

JAVA 25 SELF
Poziom 34 , Lekcja 4
Dostępny

1. Problem kolejności w klasycznych kolekcjach

W Javie zawsze istniały kolekcje, które gwarantują kolejność elementów (na przykład ArrayList, LinkedList, LinkedHashSet, LinkedHashMap), oraz takie, gdzie kolejność nie jest gwarantowana (na przykład HashSet, HashMap). Wszystkie te kolekcje miały jednak jedną wspólną wadę: mimo że część z nich przechowuje elementy w określonej kolejności, standardowe interfejsy (List, Set, Map) nie dostarczały uniwersalnych metod dla dostępu do pierwszych i ostatnich elementów ani do odwrócenia kolejności.

Na przykład, jeśli masz List<String>, możesz pobrać pierwszy element przez list.get(0), ale dla Set lub Map taki trik już nie zadziała — trzeba użyć iteratorów albo napisać dodatkowy kod. Zgodzisz się, że to mało wygodne i nie sprzyja czytelności.

To tak, jakbyś miał szafę z szufladami, ale żeby wyciągnąć pierwszą lub ostatnią, musiałbyś za każdym razem ręcznie przeliczać wszystkie szuflady! Fajnie byłoby mieć specjalne uchwyty „pierwszy” i „ostatni”, prawda?

Pojawienie się SequencedCollection i powiązanych interfejsów

W Java 21 pojawiły się nowe interfejsy kolekcji:

  • SequencedCollection<E>
  • SequencedSet<E>
  • SequencedMap<K, V>

Te interfejsy rozszerzają standardowe kolekcje i wprowadzają jednolite podejście do pracy z kolejnością elementów. Teraz można pisać uniwersalny kod dla dowolnych kolekcji, w których kolejność ma znaczenie, bez zastanawiania się nad konkretną implementacją.

Co to takiego?

  • SequencedCollection — to kolekcja, w której elementy mają określoną kolejność i można łatwo pobrać pierwszy, ostatni element oraz odwrócić kolejność.
  • SequencedSet — to samo, ale dla zbiorów (unikalne elementy).
  • SequencedMap — to samo, ale dla odwzorowań (klucz‑wartość).

Które kolekcje je teraz implementują?

W Java 21 nowe interfejsy implementują następujące standardowe kolekcje:

  • ArrayList, LinkedListSequencedCollection
  • LinkedHashSet, TreeSetSequencedSet
  • LinkedHashMap, TreeMapSequencedMap

To oznacza, że jeśli już używasz tych kolekcji, automatycznie zyskujesz nowe możliwości!

2. Podstawowe metody SequencedCollection, SequencedSet, SequencedMap

Metody SequencedCollection

E getFirst();      // Pobierz pierwszy element
E getLast();       // Pobierz ostatni element
SequencedCollection<E> reversed(); // Uzyskaj kolekcję w odwrotnej kolejności

Metody SequencedSet

Te same metody co w SequencedCollection, plus wszystko, co zapewnia Set.

Metody SequencedMap

Map.Entry<K, V> firstEntry();    // Pierwszy wpis (klucz-wartość)
Map.Entry<K, V> lastEntry();     // Ostatni wpis (klucz-wartość)
SequencedMap<K, V> reversed();   // Map w odwrotnej kolejności

3. Przykłady użycia nowych interfejsów

Przykład 1: Pobieranie pierwszego i ostatniego elementu

import java.util.*;

public class SequencedDemo {
    public static void main(String[] args) {
        SequencedCollection<String> sc = new ArrayList<>();
        sc.add("Java");
        sc.add("Python");
        sc.add("Kotlin");

        // Pobierz pierwszy i ostatni element
        String first = sc.getFirst(); // "Java"
        String last = sc.getLast();   // "Kotlin"

        System.out.println("Pierwszy: " + first);
        System.out.println("Ostatni: " + last);
    }
}

Wynik:

Pierwszy: Java
Ostatni: Kotlin

Przykład 2: Odwrócenie kolejności kolekcji

SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");

SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]

Zwróć uwagę: reversed() zwraca widok kolekcji w odwrotnej kolejności. Jeśli zmienisz kolekcję źródłową, zmieni się też widok reversed()!

Przykład 3: Praca z SequencedSet

SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);

System.out.println("Pierwszy element: " + set.getFirst()); // 100
System.out.println("Ostatni element: " + set.getLast()); // 300

SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]

Przykład 4: Praca z SequencedMap

SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);

Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();

System.out.println("Pierwszy: " + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("Ostatni: " + last.getKey() + " = " + last.getValue()); // cherry = 7

SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}

4. Jak to się ma do twojej aplikacji?

Załóżmy, że w twojej aplikacji edukacyjnej przechowujesz listę użytkowników, którzy zalogowali się do systemu, i chcesz szybko pobrać pierwszego i ostatniego użytkownika (na przykład, aby pokazać „kto wszedł pierwszy” i „kto ostatni”). Wcześniej trzeba było pisać coś w tym stylu:

List<String> users = new ArrayList<>();
// ... dodajemy użytkowników
String first = users.get(0);
String last = users.get(users.size() - 1);

Ale jeśli kolekcja to nie lista, a na przykład LinkedHashSet (gdzie elementy są unikalne i kolejność jest zachowana), taki trik już nie zadziała:

Set<String> users = new LinkedHashSet<>();
// ... dodajemy użytkowników
// Jak pobrać pierwszego? Tylko przez iterator:
String first = users.iterator().next();
// A ostatniego? Tylko przeiterować wszystkie elementy!

Teraz jest prościej i bardziej uniwersalnie:

SequencedSet<String> users = new LinkedHashSet<>();
// ... dodajemy użytkowników
String first = users.getFirst();
String last = users.getLast();

To nie tylko zmniejsza ilość kodu, ale też czyni go bardziej czytelnym i bezpiecznym.

5. Schemat nowych interfejsów

classDiagram
    Collection <|-- SequencedCollection
    List <|-- SequencedCollection
    Set <|-- SequencedSet
    Map <|-- SequencedMap
    SequencedCollection <|-- SequencedSet
    SequencedSet <|-- LinkedHashSet
    SequencedSet <|-- TreeSet
    SequencedCollection <|-- ArrayList
    SequencedCollection <|-- LinkedList
    SequencedMap <|-- LinkedHashMap
    SequencedMap <|-- TreeMap

6. Przydatne szczegóły

Praktyczne zalety SequencedCollection

  • Jednolity interfejs do pracy z kolejnością: Nie musisz już pamiętać, gdzie jest get(0), gdzie potrzebny jest iterator, a gdzie w ogóle nie da się pobrać pierwszego elementu.
  • Wygoda pracy z kolejkami i stosami: Łatwo pobrać pierwszy (head) i ostatni (tail) element.
  • Bezpieczeństwo i czytelność: Mniej błędów związanych z nieprawidłowym użyciem kolekcji; kod staje się samodokumentujący.
  • Szybkie odwracanie kolejności kolekcji: Metoda reversed() pozwala łatwo uzyskać odwrotną kolejność bez ręcznych manipulacji.
  • Łatwość utrzymania i rozbudowy kodu: Jeśli w przyszłości zechcesz zamienić na przykład ArrayList na LinkedHashSet, kod używający SequencedCollection nie będzie wymagał przepisania.

Cechy implementacji

  • Nie wszystkie kolekcje implementują SequencedCollection: Na przykład HashSet i HashMap nie gwarantują kolejności, więc nie implementują nowych interfejsów.
  • Metody mogą zgłaszać wyjątki: Jeśli kolekcja jest pusta, wywołanie getFirst() lub getLast() spowoduje NoSuchElementException. Nie zapomnij sprawdzić, czy kolekcja nie jest pusta!
  • reversed() — to widok, a nie kopia: Zmiany kolekcji źródłowej odzwierciedlają się w widoku odwróconym i odwrotnie.
  • Zgodność: Nowe interfejsy są dostępne dopiero od Java 21. Jeśli używasz starszej wersji Javy, te możliwości nie są jeszcze dostępne (ale to świetny powód, by zaktualizować!).
  • Generyki: Wszystkie nowe interfejsy w pełni wspierają generyki, więc można pracować z dowolnymi typami danych.

7. Typowe błędy podczas pracy z SequencedCollection

Błąd nr 1: Oczekiwanie zachowania kolejności od kolekcji, które jej nie gwarantują. Jeśli spróbujesz rzutować HashSet na SequencedSet, dostaniesz błąd kompilacji — HashSet nie ma ustalonej kolejności i nie implementuje tego interfejsu.

Błąd nr 2: Ignorowanie pustych kolekcji. Wywołanie getFirst() lub getLast() na pustej kolekcji zgłosi wyjątek. Zanim wywołasz te metody, sprawdź, czy kolekcja nie jest pusta:

if (!sc.isEmpty()) {
    String first = sc.getFirst();
}

Błąd nr 3: Nieporozumienia z reversed(). Metoda reversed() zwraca widok, a nie kopię. Jeśli zmienisz widok reversed, zmieni się też kolekcja źródłowa (i odwrotnie). To może prowadzić do nieoczekiwanych rezultatów, jeśli nie spodziewasz się takiego zachowania.

Błąd nr 4: Używanie nowych interfejsów na starszych wersjach Javy. Jeśli twój projekt jest kompilowany z wersją poniżej Java 21, kompilator nie znajdzie tych interfejsów. Sprawdź wersję JDK w ustawieniach projektu!

1
Ankieta/quiz
Nowoczesne kolekcje, poziom 34, lekcja 4
Niedostępny
Nowoczesne kolekcje
Nowoczesne kolekcje i niezmienność
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION