Menu

HashSet w Javie: unikalne wartości, przynależność i działania na zbiorach

Jak używać HashSet w Javie do kolekcji unikalnych wartości: add, contains, remove, usuwanie duplikatów z listy oraz suma, iloczyn i różnica zbiorów.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Zbiór przechowuje unikalne wartości

HashSet (z java.util) to kolekcja, która przechowuje każdą wartość najwyżej raz. Nie ma tu kluczy i wartości połączonych w pary jak w HashMap: to po prostu worek różnych elementów. Jego całe zadanie to szybka odpowiedź na jedno pytanie: „czy to tu jest?”

Jest jeden parametr typu, <ElementType>. Podobnie jak przy ArrayList i HashMap zmienną zwykle deklaruje się jako interfejs Set, a tworzy HashSet.

add zwraca informację, czy wartość była nowa

add nie tylko zapisuje wartość, ale też zwraca boolean, który mówi, czy zbiór faktycznie się zmienił. Dodanie wartości, która już jest w zbiorze, zwraca false i niczego nie zmienia.

Ta zwracana wartość jest naprawdę przydatna: if (!seen.add(x)) { /* x is a repeat */ } pozwala wykrywać duplikaty w jednym wierszu na bieżąco.

Usuwanie duplikatów z listy

Zbiór odrzuca powtórzenia, więc najszybszy sposób na usunięcie duplikatów z kolekcji to przerzucenie jej do zbioru. Konstruktor HashSet przyjmuje dowolną inną kolekcję:

To najczęstszy powód, dla którego początkujący sięgają po zbiór. Pamiętaj tylko, że po drodze tracisz pierwotną kolejność; jeśli kolejność ma znaczenie, użyj LinkedHashSet (omówiony niżej).

contains, remove i size

Codzienne operacje wyglądają tak samo jak w innych kolekcjach:

Wielka przewaga nad ArrayList to contains. Lista musi przejść po każdym elemencie, żeby odpowiedzieć (O(n)), a HashSet trafia niemal od razu do odpowiedzi (mniej więcej O(1)). Gdy łapiesz się na wywoływaniu list.contains(...) wewnątrz pętli, to zwykle sygnał, że pora przejść na zbiór.

Działania na zbiorach: suma, iloczyn, różnica

Zbiory błyszczą, gdy je łączysz. Gdy wiesz, która metoda co robi, czyta się je jak zwykły tekst:

Najważniejsza pułapka: addAll, retainAll i removeAll zmieniają zbiór, na którym je wywołano. Dlatego każdy przykład najpierw kopiuje a do nowego HashSet, inaczej zniszczyłbyś oryginał. Dla każdego wyniku buduj nowy zbiór.

HashSet nie zachowuje kolejności

Podobnie jak HashMap, HashSet nie gwarantuje kolejności iteracji, a ta może się różnić między uruchomieniami. Jeśli potrzebujesz przewidywalności:

  • LinkedHashSet zachowuje kolejność wstawiania, czyli kolejność dodawania elementów.
  • TreeSet utrzymuje elementy posortowane według porządku naturalnego (albo podanego przez ciebie Comparator).

Wszystkie trzy implementują interfejs Set, więc zamiana jednego na drugi to zmiana jednego wiersza z konstruktorem.

Elementy muszą być haszowalne

Pod spodem HashSet opiera się na HashMap, więc obowiązuje ta sama zasada: odnajduje elementy przez haszowanie, co oznacza, że hashCode() i equals() elementu muszą być ze sobą zgodne. Typy wbudowane, takie jak String i Integer, robią to poprawnie, i dlatego powtórzone napisy "java" powyżej poprawnie się scalają. Jeśli przechowujesz instancje własnej klasy, nadpisz zarówno equals, jak i hashCode. Inaczej dwa obiekty „równe” znaczeniowo zostaną uznane za różne, a contains i usuwanie duplikatów po cichu przestaną działać.

Dalej: iterowanie po kolekcjach

Znasz już trzy najważniejsze kolekcje: ArrayList, HashMap i HashSet. Po każdej przechodzi się trochę inaczej i czyhają przy tym subtelne pułapki (na przykład modyfikowanie kolekcji w trakcie pętli). Dalej zbierzemy to wszystko razem i omówimy czyste iterowanie po kolekcjach za pomocą pętli for-each, iteratorów i forEach.

Najczęściej zadawane pytania

Jak utworzyć HashSet w Javie?

Zadeklaruj go z jednym parametrem typu, czyli typem elementów, i wywołaj konstruktor: Set<String> tags = new HashSet<>();. Dodawaj wartości przez tags.add("java");, a przynależność sprawdzaj przez tags.contains("java");. Zaimportuj java.util.HashSet i java.util.Set.

Czym różni się HashSet od ArrayList w Javie?

ArrayList przechowuje każdy dodany element (także duplikaty) w kolejności wstawiania i jest indeksowana pozycjami. HashSet przechowuje tylko unikalne wartości, nie gwarantuje kolejności, nie ma indeksu, a sprawdzenie contains działa w mniej więcej stałym czasie zamiast przeszukiwać całą listę. Sięgaj po HashSet, gdy liczy się unikalność albo szybkie sprawdzanie przynależności, a nie pozycja.

Jak usunąć duplikaty z listy w Javie?

Przekaż listę do konstruktora HashSet: Set<String> unique = new HashSet<>(list);. Zbiór automatycznie odrzuca powtórzone wartości. Jeśli potrzebujesz z powrotem listy (i nie przeszkadza ci utrata kolejności), opakuj go ponownie: new ArrayList<>(unique). Jeśli chcesz zachować pierwotną kolejność, użyj LinkedHashSet.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ