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:
LinkedHashSetzachowuje kolejność wstawiania, czyli kolejność dodawania elementów.TreeSetutrzymuje elementy posortowane według porządku naturalnego (albo podanego przez ciebieComparator).
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.