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, LinkedList → SequencedCollection
- LinkedHashSet, TreeSet → SequencedSet
- LinkedHashMap, TreeMap → SequencedMap
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!
GO TO FULL VERSION