După ce am studiat colecțiile de tip List, unde elementele sunt ordonate și pot apărea duplicate, este important să analizăm și o altă categorie esențială din cadrul colecțiilor Java — Set.
Spre deosebire de liste, structurile de tip Set au o regulă foarte clară: nu permit elemente duplicate. Încercarea de a adăuga un element deja prezent în Set nu va avea efect. Această caracteristică le face extrem de utile în situațiile în care dorim să lucrăm cu date unice, cum ar fi eliminarea valorilor repetate sau verificarea rapidă a existenței unui element.
Un alt aspect important este că, în funcție de implementare, seturile pot:
- să nu păstreze ordinea elementelor - clasa HashSet
- să păstreze ordinea de inserare - clasa LinkedHashSet
- sau să sorteze automat elementele - clasa TreeSet
În continuare, vom examina fiecare clasă separat, evidențiind modul de funcționare și situațiile în care este recomandată utilizarea acesteia.
Interfața Set pune la dispoziție un set de metode care permit gestionarea eficientă a colecțiilor fără elemente duplicate. Aceste metode oferă posibilitatea de a adăuga, elimina, verifica și parcurge elementele, indiferent de tipul concret de implementare utilizat. Cunoașterea acestor metode este extrem de importantă deoarece ele reprezintă baza lucrului cu toate tipurile de seturi (HashSet, LinkedHashSet, TreeSet). Astfel, odată înțelese, ele pot fi aplicate ușor în orice context, fără a fi necesară învățarea separată pentru fiecare clasă. Deoarece noi cunoaștem cu toate clasele care implementează o interfață conțin implementare pentru metodele interfeței.
Deci, interfața Set are următoarele metode principale:
- boolean add(E element) - adaugă un element în set. Returnează true dacă setul nu conține deja elementul adăugat.
- boolean contains(Object o)) - verifică dacă setul conține un anumit element.
- boolean remove(Object o) - elimină un element din set. Returnează true dacă setul conținea elementul șters.
- int size() - returnează numărul de elemente din set.
- boolean isEmpty() - verifică dacă setul este gol (nu conține elemente).
- void clear() - elimină toate elementele din set.
- Iterator<E> iterator() - returnează un iterator peste elementele din set.
- Object[] toArray() - returnează un tablou care conține toate elementele din set.
- <T> T[] toArray(T[] a) - returnează un tablou care conține toate elementele din set, într-un tablou specificat.
- boolean containsAll(Collection<?> c) - verifică dacă setul conține toate elementele dintr-o altă colecție.
- boolean addAll(Collection<? extends E> c) - adaugă toate elementele dintr-o altă colecție în set.
- boolean removeAll(Collection<?> c) - elimină toate elementele din set care sunt prezente și într-o altă colecție.
- boolean equals(Object o) - verifică dacă setul este egal cu un alt obiect.
- int hashCode() - returnează codul hash pentru set.
- boolean removeIf(Predicate<? super E> filter) - elimină elementele care respectă o condiție. Vedeți expresiile Lambda aici https://musteatadidactic.blogspot.com/2026/02/expresiile-lambda.html
- void forEach(Consumer<? super E> action)- parcurge fiecare element și aplică o acțiune. Acceptă ca parametru o expresie lambda.
Interfața Set nu poate fi utilizată direct, deoarece este doar un model. Pentru a lucra efectiv cu date, avem nevoie de o clasă concretă care să implementeze această interfață.
Una dintre cele mai frecvent utilizate implementări ale interfeței Set este HashSet. Aceasta permite stocarea elementelor într-un mod rapid și eficient, utilizând mecanismul de hashing, și asigură în același timp unicitatea acestora.
În continuare, vom analiza clasa HashSet, modul de funcționare și principalele operații disponibile.
Clasa HashSet reprezintă o colecție în care elementele sunt organizate prin hashing, ceea ce permite un acces foarte rapid. Operațiile de adăugare, eliminare și căutare au, în medie, o complexitate de timp O(1), ceea ce le face potrivite pentru lucrul cu volume mari de date. Această clasă nu permite elemente duplicate și, în mod obișnuit, nu acceptă valori nule.
Constructori importanți:
- HashSet() – creează un set gol, utilizând valorile implicite
- HashSet(Collection<? extends E> c) – creează un set care conține elementele unei colecții existente
Exemplul 1 Creează o listă de tip HashSet ce va conține discipline, adaugă câteva elemente (fără duplicate), și le afișează folosind trei metode diferite de parcurgere, afișează și numărul total de elemente in listă.
import java.util.HashSet;
import java.util.Set;
public class Main{
public static void main(String[] args) {
Set<String> discipline = new HashSet<>();
discipline.add("Matematica");
discipline.add("Informatica");
discipline.add("Fizica");
discipline.add("Matematica");
System.out.println("\u001B[4m\033[1mAfișare directă\033[0m\u001B[0m");
System.out.println(discipline);
System.out.println("\u001B[4m\033[1m\nParcurgere cu for îmbunătățit\033[0m\u001B[0m");
for (String d : discipline) {
System.out.println(d);
}
System.out.println("\u001B[4m\033[1m\nParcurgere cu expresie lambda\033[0m\u001B[0m");
discipline.forEach(x -> System.out.println(x));
System.out.println("\u001B[4m\033[1m\nNumar elemente:\033[0m\u001B[0m " + discipline.size());
}}
Atenție la elementele care au fost adăugate în listă și la cele care au fost afișate! 👀 Ce observați ? Evident, elementele duplicat nu sunt adăugate în listă 👌
Exemplul 2 Vom modifica exemplul de mai sus prin crearea unei liste de tip ArrayList care permite elemente duplicat. Observați diferențele prin compararea rezultatelor.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> discipline = new ArrayList<>();
discipline.add("Matematica");
discipline.add("Informatica");
discipline.add("Fizica");
discipline.add("Matematica");
System.out.println("\u001B[4m\033[1m\nParcurgere cu expresie lambda\033[0m\u001B[0m");
discipline.forEach(x -> System.out.println(x));
System.out.println("\u001B[4m\033[1m\nNumar elemente:\033[0m\u001B[0m " + discipline.size());
}}
Clasa LinkedHashSet implementează interfeța Set care combină unicitatea elementelor cu menținerea ordinii de inserare. Practic, ea păstrează elementele exact în ordinea în care au fost adăugate, spre deosebire de HashSet, care nu garantează nicio ordine.
Această clasă folosește un hashing pentru a asigura unicitatea elementelor, adică nu permite duplicate și folosește o listă dublu înlănțuită internă pentru a păstra ordinea de inserare. Oferă operații rapide de adăugare, eliminare și căutare, aproape de O(1), însă are un consum mai mare de memorie comparativ cu HashSet, din cauza listei dublu înlănțuite, adică este puțin mai lent decât HashSet la operații simple.Constructori importanți:
- LinkedHashSet() - creează un set gol fără parametri. Menține ordinea de inserare.
- LinkedHashSet(Collection<? extends E> c) - creează un set care conține toate elementele unei colecții existente. Elimină duplicatele automat.
Exemplul 3 Vom crea o listă de tip LinkedHashSet în care vom stoca elemente de tip Elev.
import java.util.LinkedHashSet;
import java.util.Set;
class Elev {
public Elev(String nume, int nota) {
public String toString() {
return nume + " - Nota: " + nota;
public static void main(String[] args) {
Set<Elev> elevi = new LinkedHashSet<>();
elevi.add(new Elev("Ana", 9));
elevi.add(new Elev("Ion", 7));
elevi.add(new Elev("Maria", 10));
elevi.add(new Elev("Bogdan", 8));
elevi.add(new Elev("Ana", 9));
System.out.println("Lista elevilor:");
elevi.forEach(x -> System.out.println(x));
}
După cum observați, necătând la faptul că LinkedHashSet nu premite elemente duplicat, exemplul nostru are un alt rezultat și avem două persoane cu aceleași valori, este din motiv că operatorul new() crează un nou obiect în sistem și acesta va avea codul hash diferit de obiectul anterior, iar limbajul Java compară implicit doar acest cod, nu și conținutul obiectelor. În acest caz este necesară supradefinirea metodelor equals() și hashCode().Exemplul 4 Vom crea o listă de tip LinkedHashSet în care vom stoca elemente de tip Elev. Vom supradefini metodele equals() și hashCode() pentru a nu permite două obiecte cu același conținut.
import java.util.LinkedHashSet;
import java.util.Set;
class Elev {
public Elev(String nume, int nota) {
public String toString() {
return nume + " - Nota: " + nota;
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Elev)) return false;
return nota == elev.nota && nume.equals(elev.nume);
return nume.hashCode() + nota;
}
public class Main {
public static void main(String[] args) {
Set<Elev> elevi = new LinkedHashSet<>();
elevi.add(new Elev("Ana", 9));
elevi.add(new Elev("Ion", 7));
elevi.add(new Elev("Maria", 10));
elevi.add(new Elev("Bogdan", 8));
elevi.add(new Elev("Ana", 9));
System.out.println("Lista elevilor:");
elevi.forEach(x -> System.out.println(x));
}}În proiecte reale pentru a asigura unicitatea fiecărui elev în colecție, vom lua în considerare un element care îl identifică în mod unic, cum ar fi un ID sau un cod personal (IDNP), astfel încât să nu putem avea doi elevi cu același identificator, chiar dacă au aceeași notă sau același nume. Clasa TreeSet este o implementare a interfeței NavigableSet, care stochează elementele sortate automat, deci și aici elemente duplicate nu sunt permise și nici valorile null. Are la bază arbore binar de căutare. Operațiile de adăugare, ștergere și căutare au complexitate O(log n).
Constructori importanți:
- TreeSet() – creează un set gol cu ordonare naturală.
- TreeSet(Collection<? extends E> c) – creează un set cu elementele dintr-o colecție existentă și le sortează.
- TreeSet(Comparator<? super E> comparator) – creează un set gol care folosește un comparator personalizat pentru sortare.
Exemplul 5 Vom crea o listă de tip TreeSet care va conține elemente întregi.
import java.util.*;
public class Main {
public static void main(String[] args) {
Set<Integer> numere = new TreeSet<>();
System.out.println("Numere sortate automat:");
for (Integer n : numere) {
}}}
După cum observați elementele au fost automat sortate în ordine crescătoare, iar duplicatele au fost omise. Exemplul 6 Vom crea o listă de tip TreeSet care va conține elemente întregi, dar vor fi sortate automat în ordine descrescătoare.
import java.util.*;
public class Main {
public static void main(String[] args) {
Set<Integer> numere = new TreeSet<>(Comparator.reverseOrder());
System.out.println("Numere sortate descrescător:");
for (Integer n : numere) {
}}}
Exemplul 7 Vom crea o listă de tip TreeSet care va conține elemente de tip Elev sortate descrescător după notă. Fără elemente duplicat.
import java.util.*;
class Elev {
public Elev(String nume, int nota) {
public String getNume() {
public String toString() {
return nume + " - Nota: " + nota;
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Elev)) return false;
return nota == elev.nota && nume.equals(elev.nume);
return nume.hashCode() + nota;
}}
public class Main {
public static void main(String[] args) {
Set<Elev> elevi = new TreeSet<>(Comparator.comparingInt(Elev::getNota).reversed());
elevi.add(new Elev("Ana", 9));
elevi.add(new Elev("Ion", 7));
elevi.add(new Elev("Maria", 10));
elevi.add(new Elev("Bogdan", 8));
elevi.add(new Elev("Ana", 9));
System.out.println("Elevi sortati descrescător după nota:");
elevi.forEach(x -> System.out.println(x));
}
}
Pentru a efectua o sortare alfabetică după nume este suficient să folosim constructorul cu următorul parametru personalizat de sortare:
Set<Elev> elevi = new TreeSet<>(Comparator.comparing(Elev::getNume));
Pentru o sortare pe două nivele putem folosi constructor de forma celui ce urmează care va efectua o sortare alfabetică după nume și descrescătoare după notă: Comparator<Elev> comparator = Comparator.comparing(Elev::getNume.thenComparing (Comparator.comparingInt(Elev::getNota).reversed());
Pentru o sortare pe trei nivele amputea utiliza constructorul personalizat:
Comparator<Elev> comparator = Comparator.comparing(Elev::getNume.thenComparing(Elev::getPrenume.thenComparing (Comparator.comparingInt(Elev::getNota).reversed());
Pentru a evidenția diferențele și avantajele fiecărei implementări a interfeței Set, voi atașa cestei teme tabelul comparativ între HashSet, LinkedHashSet și TreeSet, care este concentrat pe ordonare, performanță și modul de gestionare a elementelor duplicat
| Caracteristică |
HashSet |
LinkedHashSet |
TreeSet |
| Interfață implementată |
Set |
Set |
NavigableSet (extinde SortedSet și Set) |
| Ordinea elementelor |
Nu este garantată |
Păstrează ordinea de inserare |
Elementele sunt sortate natural (alfabetic/numeric) sau după Comparator |
| Permite duplicate |
Nu |
Nu |
Nu |
| Permite elemente null |
Da (doar un singur null) |
Da (doar un singur null) |
Nu (aruncă NullPointerException) |
| Complexitate adăugare/ștergere/căutare |
O(1) în medie |
O(1) în medie |
O(log n) |
| Structură internă |
Tabel hash |
Tabel hash + listă dublu înlănțuită |
Arbore binar de căutare |
| Avantaje |
Foarte rapid |
Rapid + ordinea elementelor păstrată |
Elemente sortate, navigare facilă |
| Dezavantaje |
Ordinea nu este păstrată |
Consum mai mare de memorie decât HashSet |
Operații mai lente decât HashSet/LinkedHashSet |
| Când se folosește |
Când ordinea nu contează și avem nevoie de viteză |
Când vrem ordinea de inserare și unicitate |
Când avem nevoie de elemente sortate automat |
În concluzie, alegerea corectă a unei implementări a interfeței Set depinde de cerințele aplicației: dacă este nevoie de viteză, se va utiliza HashSet, dacă se dorește păstrarea ordinii de inserare se va utiliza LinkedHashSet, iar dacă este necesară sortarea automată a elementelor – TreeSet. Toate aceste colecții asigură unicitatea elementelor, însă diferă prin modul de organizare internă și performanță.
Vă încurajez să testați fiecare tip de set prin exemple proprii și să observați diferențele în comportament – aceasta este cea mai bună metodă de învățare în programare, testați, modificați, ajustați, corectați erori de compilare și execuție.
Vă doresc o zi deosebit de frumoasă!
❤️