TreeSet, HashSet – Set: Wprowadzenie do Kolekcji w Javie

Java to jeden z najpopularniejszych języków programowania, znany ze swojej wszechstronności i bogatej biblioteki standardowej. Jednym z jej kluczowych elementów jest framework kolekcji, który umożliwia efektywne przechowywanie, przetwarzanie i manipulowanie danymi. W tym artykule skupimy się na interfejsie Set oraz dwóch jego najczęściej używanych implementacjach: HashSet i TreeSet. Obydwa te typy kolekcji mają swoje unikalne cechy, które sprawiają, że nadają się do różnych zastosowań.


Czym jest interfejs Set?

Set to interfejs w ramach kolekcji Java, który reprezentuje zbiór unikalnych elementów. W odróżnieniu od list, w których elementy mogą się powtarzać, każdy element w zbiorze musi być unikalny. Interfejs Set jest częścią pakietu java.util i rozszerza interfejs Collection. Kluczowe cechy Set to:

  • Brak powtórzeń elementów.
  • Kolejność przechowywania elementów zależy od konkretnej implementacji.
  • Wysoka wydajność operacji takich jak dodawanie, usuwanie czy sprawdzanie obecności elementów (również zależy od implementacji).

Główne implementacje interfejsu Set to:

  • HashSet
  • TreeSet
  • LinkedHashSet (nie jest omawiany w tym artykule)

Każda z tych klas ma swoje zalety i ograniczenia. Zrozumienie różnic między nimi pozwala na świadome dobranie właściwej klasy do konkretnych potrzeb.


HashSet: Szybkość i Prostota

HashSet to najczęściej używana implementacja interfejsu Set. Wewnętrznie opiera się na mechanizmie tablicy haszującej, co zapewnia wysoką wydajność operacji takich jak dodawanie, usuwanie czy wyszukiwanie elementów. Kluczowe cechy HashSet to:

  • Unikalność elementów: Duplikaty są automatycznie ignorowane.
  • Brak uporządkowania: Elementy nie są przechowywane w określonej kolejności.
  • Wysoka wydajność: Operacje takie jak dodawanie czy wyszukiwanie mają średnią złożoność .

Jak działa HashSet?

Pod maską HashSet wykorzystuje tablicę haszującą (HashMap). Każdy element, który dodajemy, jest mapowany na indeks w tablicy za pomocą funkcji hashCode(). Dzięki temu możliwe jest szybkie wyszukiwanie elementów.

Przykładowy kod:

import java.util.HashSet;

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();

        set.add("Java");
        set.add("Python");
        set.add("JavaScript");
        set.add("Java"); // Duplikat

        System.out.println("Zawartość HashSet: " + set);
    }
}

Wyjście:

Zawartość HashSet: [Java, Python, JavaScript]

Jak widać, duplikaty są automatycznie ignorowane.

Zastosowania HashSet

  • Gdy potrzebujemy szybkiego dostępu do danych bez duplikatów.
  • Do przechowywania dużych zbiorów danych, gdzie kolejność elementów nie ma znaczenia.
  • Do filtrowania duplikatów w danych wejściowych.

TreeSet: Porządek i Funkcjonalność

TreeSet to implementacja interfejsu Set oparta na strukturze drzewa (specjalnie drzewa czerwono-czarnego). W przeciwieństwie do HashSet, TreeSet przechowuje elementy w uporządkowany sposób, co umożliwia wykonywanie operacji związanych z porządkiem.

Kluczowe cechy TreeSet

  • Uporządkowanie: Elementy są automatycznie sortowane według naturalnego porządku (np. dla liczb od najmniejszej do największej).
  • Brak duplikatów: Podobnie jak w HashSet, duplikaty są ignorowane.
  • Wydajność: Operacje takie jak dodawanie, usuwanie czy wyszukiwanie mają złożoność .

Przykład TreeSet

import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();

        set.add(5);
        set.add(3);
        set.add(8);
        set.add(1);

        System.out.println("Zawartość TreeSet: " + set);

        // Operacje dodatkowe
        System.out.println("Najmniejszy element: " + set.first());
        System.out.println("Największy element: " + set.last());
    }
}

Wyjście:

Zawartość TreeSet: [1, 3, 5, 8]
Najmniejszy element: 1
Największy element: 8

Zastosowania TreeSet

  • Gdy potrzebujemy zbioru elementów w uporządkowanej kolejności.
  • Do implementacji funkcjonalności takich jak przedziały, operacje na zbiorach (np. podzbiory).
  • W sytuacjach, gdy konieczne jest częste wyszukiwanie najmniejszego lub największego elementu.

Dodatkowe możliwości

TreeSet obsługuje metody takie jak:

  • headSet(E toElement) – zwraca podzbiór elementów mniejszych niż podany element.
  • tailSet(E fromElement) – zwraca podzbiór elementów większych lub równych podanemu elementowi.
  • subSet(E fromElement, E toElement) – zwraca podzbiór elementów mieszczących się w określonym zakresie.

Różnice między HashSet a TreeSet

Cecha HashSet TreeSet
Podstawowa struktura Tablica haszująca Drzewo czerwono-czarne
Kolejność elementów Brak (kolejność przypadkowa) Sortowanie naturalne
Wydajność operacji
Obsługa zakresów Nie Tak
Przechowywanie null Tak (tylko jeden null) Nie (null powoduje błąd)

Kiedy wybrać HashSet, a kiedy TreeSet?

  • HashSet jest lepszym wyborem, gdy zależy nam na maksymalnej wydajności i brak uporządkowania elementów nie stanowi problemu.
  • TreeSet sprawdza się, gdy konieczne jest przechowywanie elementów w uporządkowany sposób lub wykonywanie operacji zakresowych.

Kompatybilność i uwagi

Warto zauważyć, że obie implementacje wymagają odpowiednio zaimplementowanych metod equals() oraz hashCode() (w przypadku HashSet) oraz interfejsu Comparable lub komparatora (w przypadku TreeSet). Niedopatrzenia w tej kwestii mogą prowadzić do błędnego działania kolekcji.


Interfejs Set oraz jego implementacje, takie jak HashSet i TreeSet, stanowią fundament nowoczesnych aplikacji. Ich wybór i odpowiednie zastosowanie umożliwiają optymalizację wydajności oraz uproszczenie kodu, co czyni je niezastąpionymi narzędziami w codziennej pracy programisty.