Kolejną kolekcją w Javie są Sety, inaczej mówiąc zbiory. Dostarczane są one za pośrednictwem interfejsu
Set z pakietu
java.util.
Sety wyglądają z wierzchu trochę podobnie do list, jednak ich implementacja i podejście do danych jest zupełnie inne. Przyjrzyjmy się teraz najważniejszym cechom zbiorów:
- Nie są dozwolone duplikaty elementów.
- Do elementów w secie nie odwołujemy się po pozycji, gdyż nie wiadomo na jakiej pozycji znajduje się dany element.
-
Mogą być sortowalne lub nie - zależy od konkretnej implementacji interfejsu.
Po co mi sety skoro mam listy?
Każdy na początku zadaje sobie pytanie dotyczące użyteczności setów, których konstrukcja wydaje się być bardziej zagmatwana od list i
do tego ma swoje ograniczenia. Mimo tego sety są bardzo często używane i nie można ich pominąć w trakcie nauki. Dzieje się tak ze względu na ich specjalną
właściwość:
Unikalność elementów
W secie nie mogą istnieć dokładnie dwa takie same obiekty. Zawsze będzie przechowywany tylko jeden. Zatem jeśli mamy ciąg liczb:
7, 2, 9, 1, 5, 7, 6, 3, 9
i taki ciąg wpiszemy do seta, to odczytując wszystkie elementy zbioru otrzymamy tylko pojedyncze wartości:
7, 2, 9, 1, 5, 6, 3
W ten sposób można w łatwy sposób odfiltrować duplikaty. Wystarczy wprowadzić je do seta. Wyciągniemy z niego jedynie niezduplikowane elementy.
Weryfikacja unikalności - kontrakt equals/hashCode
To czy dany element znajduje się w zbiorze jest weryfikowne
przy pomocy metod
hashCode i
equals. Metody te (występujące w każdym obiekcie) wiąże istotny kontrakt (opisujący relację między nimi).
Polega on na tym, że jeśli dwa obiekty są równe według metody
equals to oznacza, że muszą mieć te same "haszkody" (metoda
hashCode zwraca dla nich tę samą wartość).
Natomiast jeśli obiekty mają równe "haszkody" niekoniecznie muszą być równe według porówanania metodą
equals.
Popularne implementacje zbiorów
Najpopularniejszymi zbiorami są implementacje dostarczane przez klasy:
-
HashSet z pakietu java.util
Bardzo często stosowana impelmentacja. Elementy są nieposortowane. Ich kolejność nie odpowiada również kolejności wkładania do zbioru. Może przechowywać null-e.
-
LinkedHashSet z pakietu java.util
Implementacja przechowująca elementy w kolejności ich dodawania. Zatem może być przydatna jeśli zależy nam zarówno na unikalności jak i
na tworzeniu historii unikalnych wpisów. Może przechowywać null-e.
-
TreeSet z pakietu java.util
Przechowuje zbiór pod postacią drzewa. Elementy są poukładane w sposób posortowany (rosnąco). Przydaje się gdy chcemy zapewnić unikalność elementów oraz podstawowe sortowanie.
Nie może przechowywać null-i.
Tworzenie zbioru
Dobrą praktyką jest zadeklarowanie instancji zbioru z
parametrem typu, na przykład:
Podobnie jak to miało miejsce w przypadku list, tak samo tutaj możemy określić jakiego typu obiekty mogą być przechowywane w danym zbiorze.
Dzięki temu już na etapie kompilacji jesteśmy w stanie się zorientować, czy nie popełniliśmy błędu
- na przykład przekazując obiekt klasy
String do zbioru, w którym możemy przechowywać tylko obiekty klasy
Integer.
W takiej sytuacji dowiemy się o tym od razu, gdyż po prostu otrzymamy błąd kompilacji.
Java 7
Od Javy 7 możemy usunąć parametr typu po prawej stronie, co upraszcza zapis do następującej postaci:
Java 9
Od Javy 9 możemy utworzyć zbiór z ustalonego zestawu elementów:
Java 10
Od Javy 10 można jeszcze bardziej skrócić tworzenie kolekcji używając słowa kluczowego
var:
Niemniej to rozwiązanie ma pewne ograniczenia, a także wady, dlatego warto jest wstrzymać się z jego masowym użyciem
(będziemy jeszcze o tym pisać w przyszłości).
Podstawowe operacje na zbiorach
Podstawowymi operacjami jakie możemy wykonywać na zbiorach jest dodawanie oraz usuwanie elementów. Operacje te są realizowane przez następujące metody:
- add(<obiekt>)
Umożliwia ona dodanie elementu do seta. Co ważne - wymagane jest dodanie elementów tego samego typu (lub podtypu) co parametr typu zadeklarowanego przez seta.
Dodawanie elementów podtypów zadeklarowanego typu:
Pamiętajmy, że w przypadku setów nie możemy mówić o indeksie elementu. W przypadku LinkedHashSet mamy jedynie zapewnioną kolejność, czyli podczas przeglądania zbioru otrzymamy elementy zgodne
z kolejnością ich dodawania.
- remove(<obiekt>)
Metoda usuwa element ze zbioru.
Wymieniliśmy tutaj zaledwie kilka podstawowych metod, które umożliwiają pracę ze zbiorami. Z biegiem czasu należy zapoznać się również z innymi metodami.
Uruchamiając mechanizm podpowiedzi w IDE - na przykład w IntelliJ - można łatwo poznać jakie metody są dostępne (obrazek zawiera podpowiedź dla seta
linkedNumbers):
Po tym wstępie z łatwością zrozumiecie jak działają najważniejsze z metod. Natomiast do trudniejszych będziemy jeszcze wracać w naszym kursie.
Przeglądanie zawartości seta
Zbiory implementują interfejs
Iterable, co umożliwia przeglądanie ich
element po elemencie. W tym celu wykorzystujemy interfejs
Iterator. Jego działanie polega na przeglądaniu kolekcji
dopóki po danym elemencie występuje kolejny element. Pobranie bieżącego elementu i przejście do następnego
wykonywane jest za pomocą metody
next.
Przekazanie zadania iteracji zewnętrznemu mechanizmowi (obiektowi klasy
Iterator) powoduje, że nie zależymy od implementacji
naszej struktury danych. Trzeba bowiem pamiętać, że nie tylko sety możemy iterować w Javie (na przykład w poprzednim rozdziale iterowaliśmy po liście).
Natomiast bardzo często stosowanymi rozwiązaniami do przeglądania zbiorów są udoskonalone pętle
for (
for-each):
albo też zupełna nowość od Javy w wersji 8, a więc strumienie. To jednak jest już zupełnie inne, obszerne zagadnienie, które poruszymy omawiając
kompleksowo nowości wprowadzone w Javie 8.
Jak modyfikować obiekty w setach?
Omawiając listy wspomnieliśmy o metodzie
set, która pozwalała nam na aktualizację obiektu na danej pozycji.
W przypadku setów nie mamy indeksów i nie mamy metody
set. Co nam zatem pozostaje? Otóż możemy iterować po kolejnych obiektach seta
i porównywać je po wybranym polu w celu znalezienia właściwego obiektu, a gdy już to zrobimy możemy wtedy zaktualizować jego dane.
Inną opcją jest usunięcie obiektu z seta metodą
remove i dodanie go na nowo w zaktualizowanej wersji za pomocą metody
add.
Autor: Jarek Klimas
Data: 03 stycznia 2024
Labele: Backend, Podstawowy, Java
Czy informacje, które otrzymałeś, były pomocne?
Jeśli tak, zapraszam Cię do podarowania mi kawy.
Masz pytanie dotyczące prezentowanego materiału?
Coś jest dla Ciebie niejasne i Twoje wątpliwości przeszkadzają Ci w pełnym zrozumieniu treści?
Napisz do nas maila, a my chętnie znajdziemy odpowiednie rozwiązanie.
Najciekawsze pytania wraz z odpowiedziami będziemy publikować pod rozdziałem.
Nie czekaj. Naucz się programować jeszcze lepiej.