This is an old revision of the document!
—
O colecție este un obiect care grupează mai multe elemente într-o singură unitate. Prin intermediul colecțiilor avem acces la diferite structuri de date: vectori dinamici, liste înlănțuite, stive, mulțimi, tabele de dispersie ș.a.m.d.. Colecțiile sunt folosite atât pentru memorarea și manipularea datelor, cât și pentru transmiterea informațiilor între metode.
Clasele și interfețele pentru lucrul cu colecții se află în pachetul java.util. Ierarhia pornește, pentru colecții propriu-zise, de la interfața Collection, care definește operațiile de bază (adăugare, eliminare, căutare, iterare). Din Collection derivă trei ramuri principale:
Separat de Collection se află ierarhia Map, care gestionează perechi cheie - valoare. Cheile sunt unice, iar fiecare cheie mapează exact o valoare. Implementările cele mai folosite sunt HashMap (rapid, fără ordine), LinkedHashMap (menține ordinea inserării sau a accesului – util, de exemplu, pentru cache LRU - Least Recently Used) și TreeMap (chei ordonate). Interfața SortedMap extinde Map cu operații specifice ordinii - în practică, TreeMap este implementarea reprezentativă. Hashtable este o variantă veche, sincronizată, păstrată pentru compatibilitate, dar în cod modern se preferă HashMap (sau ConcurrentHashMap pentru acces concurent).
Dacă tipul este declarat astfel, compilatorul verifică la compilare să nu introducem alt tip (de ex. un Integer într-o List<String>). Orice încercare greșită este o eroare de compilare, deci problema se oprește înainte de rulare.
Raw types sunt permise, dar nerecomandate: dezactivează verificarea de tip la compilare și pot duce la ClassCastException la rulare. Dacă aveți un caz real în care elementele pot fi de tipuri diferite, folosiți List<Object> sau un tip comun (o interfață, o clasă părinte).
import java.util.*; public class Main { public static void main(String[] args) { // 1) Corect și sigur – generics List<String> nume = new ArrayList<>(); nume.add("Ana"); // nume.add(10); // EROARE de compilare: 10 nu este String String s1 = nume.get(0); // fără cast, sigur // 2) Permis, dar nerecomandat – raw type (fără <T>) List nespecificata = new ArrayList(); // WARNING: unchecked/unsafe nespecificata.add("Ana"); nespecificata.add(10); String s2 = (String) nespecificata.get(1); // EXCEPȚIE la rulare: ClassCastException // 3) Colecție heterogenă (intenționat) List<Object> mix = new ArrayList<>(); mix.add("Ana"); mix.add(10); Object o = mix.get(1); if (o instanceof Integer n) { System.out.println(n + 5); // sigur } } }
—
Interfața List, pe lângă metodele moștenite din `Collection`, definește colecții:
* ordonate și indexate; * care permit duplicate; * ale căror elemente pot fi accesate după poziție (index).
Cele mai folosite implementări:
* ArrayList – vector dinamic; acces aleator rapid, inserări/ștergeri în interiorul listei mai costisitoare; * LinkedList – listă înlănțuită; inserări/ștergeri locale mai eficiente (cu iterator), acces mai lent la elementele din „mijloc”; implementează și `Deque` (operare la ambele capete).
Pentru comportament de stivă/coadă se preferă azi `ArrayDeque`, iar pentru liste obișnuite `ArrayList`/`LinkedList`.
import java.util.*; class Liste { private final List<String> list1 = new ArrayList<>(); // ordonată, acces aleator rapid private final LinkedList<Integer> list2 = new LinkedList<>(); // listă + deque ``` public static void main(String[] args) { Liste obj = new Liste(); // Operații de bază pe ArrayList<String> obj.list1.add("Lab POO"); obj.list1.add("Colectii"); obj.list1.add("Structuri de date"); if (obj.list1.contains("Colectii")) { System.out.println("Lista contine cuvantul"); } // Parcurgere și ștergere în siguranță (fără ConcurrentModificationException) Iterator<String> it = obj.list1.iterator(); while (it.hasNext()) { String s = it.next(); System.out.println(s); it.remove(); // șterge elementul tocmai citit } // LinkedList<Integer> ca listă + deque (ambele capete) obj.list2.addAll(Arrays.asList(1, 10, 20)); obj.list2.addFirst(50); // capătul din stânga obj.list2.addLast(17); // capătul din dreapta // Modificare „pe loc” cu ListIterator (ex.: înmulțește numerele pare cu 10) ListIterator<Integer> li = obj.list2.listIterator(); while (li.hasNext()) { int x = li.next(); if (x % 2 == 0) li.set(x * 10); } // Afișare elemente (for-each) for (Integer i : obj.list2) { System.out.println(i); } // Sortare naturală (echivalent cu Collections.sort(list2)) obj.list2.sort(Comparator.naturalOrder()); System.out.println(obj.list2); } ``` }
—
`Set` modelează noțiunea de mulțime în sens matematic:
`Set` moștenește operațiile de bază din `Collection`, fără a introduce metode proprii. Implementări uzuale:
* HashSet – rapid, *neordonat*; * LinkedHashSet – păstrează ordinea inserării; * TreeSet – menține elementele ordonate.
`SortedSet` reprezintă un `Set` în care elementele sunt păstrate în ordine crescătoare:
* fie după ordinea naturală (`Comparable`), * fie după un `Comparator` furnizat la crearea colecției.
Implementarea standard de `SortedSet` este TreeSet.
Începând cu Java 9 există mulțimi imutabile prin `Set.of(…)` și `Set.copyOf(…)`.
* `Set.of(…)` – creează direct o mulțime nemodificabilă (aruncă excepție dacă are duplicate); * `Set.copyOf(…)` – copiază un set existent într-o versiune nemodificabilă.
import java.util.*; class Example { public static void main(String[] args) { // 1) HashSet – fără ordine, elimină duplicatele pe baza equals()/hashCode() Set<String> hs = new HashSet<>(); Collections.addAll(hs, "Ana", "Ana", "Ion"); System.out.println("HashSet: " + hs); // ex.: [Ana, Ion] ``` // 2) LinkedHashSet – păstrează ordinea inserării Set<Integer> lhs = new LinkedHashSet<>(List.of(3, 1, 2, 1)); System.out.println("LinkedHashSet: " + lhs); // [3, 1, 2] // 3) TreeSet – comparator: lungime, apoi lexicografic SortedSet<String> good = new TreeSet<>( Comparator.comparingInt(String::length) .thenComparing(Comparator.naturalOrder()) ); good.addAll(List.of("aa", "b", "bb")); System.out.println("TreeSet ok: " + good); // [b, aa, bb] // Comparator problematic: compară DOAR lungimea -> unele elemente sunt excluse SortedSet<String> bad = new TreeSet<>(Comparator.comparingInt(String::length)); bad.addAll(List.of("aa", "bb")); // "bb" e ignorat: compare("aa","bb") == 0 System.out.println("TreeSet problematic: " + bad); // [aa] // 4) Set imutabil (Java 9+) Set<String> roles = Set.of("ADMIN", "USER"); // nemodificabil System.out.println("Set.of: " + roles); // roles.add("GUEST"); // -> UnsupportedOperationException // Set.of("A", "A"); // -> IllegalArgumentException (duplicate la creare) } ``` }
—
`Map` descrie structuri care asociază fiecărei chei o valoare:
* cheile sunt unice; * valorile pot fi duplicate.
Ierarhia `Map` este separată de `Collection`. Operații tipice:
* inserare: `put(k, v)`; * citire: `get(k)`, `getOrDefault(k, valoareImplicită)`; * test de apartenență: `containsKey`, `containsValue`; * eliminare: `remove(k)`; * parcurgere: `entrySet()`, `keySet()`, `values()`.
Implementări uzuale:
* HashMap – rapid, fără ordine de iterare; permite o cheie `null` și valori `null`; * LinkedHashMap – ca `HashMap`, dar păstrează ordinea inserării (sau a accesului – util pentru cache LRU); * TreeMap – menține cheile ordonate (natural sau prin `Comparator`); nu acceptă chei `null`; * Hashtable – tip vechi, sincronizat; în cod modern se preferă `HashMap` sau `ConcurrentHashMap`.
`SortedMap` este un `Map` cu chei păstrate în ordine crescătoare; implementarea clasică este `TreeMap`.
În Java 9+, `Map.of(…)` și `Map.copyOf(…)` creează hărți imutabile.
import java.util.*; class MiniMapDemo { public static void main(String[] args) { // 1) HashMap – fără ordine Map<String, Integer> freq = new HashMap<>(); for (String w : List.of("ana", "are", "ana", "mere")) { freq.merge(w, 1, Integer::sum); // new = 1, altfel +1 } System.out.println("HashMap (fara ordine): " + freq); System.out.println("getOrDefault('banane', 0) = " + freq.getOrDefault("banane", 0)); ``` // 2) LinkedHashMap – păstrează ordinea inserării Map<Integer, String> lhm = new LinkedHashMap<>(); lhm.put(2, "B"); lhm.put(1, "A"); lhm.put(3, "C"); System.out.println("LinkedHashMap (ordine inserare): " + lhm.keySet()); // [2, 1, 3] // 3) TreeMap – chei ordonate (natural) Map<String, Integer> sorted = new TreeMap<>(freq); // sortează după cheia String System.out.println("TreeMap (chei ordonate): " + sorted); // 4) Map imutabil (Java 9+) Map<String, Integer> roles = Map.of("ADMIN", 1, "USER", 2); // nemodificabil System.out.println("Map.of: " + roles); // roles.put("GUEST", 3); // -> UnsupportedOperationException // 5) Parcurgere eficientă cu entrySet() for (Map.Entry<String, Integer> e : sorted.entrySet()) { System.out.println(e.getKey() + " => " + e.getValue()); } } ``` }
—
Enumerările și iteratorii descriu modalități de parcurgere secvențială a unei colecții, indiferent dacă este indexată sau nu.
În Java modern, parcurgerea se face în principal cu:
* `Iterator` (și pentru liste, `ListIterator`), * sau cu sintaxa `for-each`: `for (T e : colectie) { … }`.
`Enumeration` și `Vector` sunt considerate tipuri legacy (istorice).
—
`Enumeration` este o interfață veche pentru parcurgere.
O mai întâlnim la `Vector` sau o putem obține din orice colecție prin `Collections.enumeration(…)`. În cod modern, se preferă `Iterator`.
import java.util.*; class DemoEnumeration { public static void main(String[] args) { List<Integer> list = List.of(3, 7, 0, 5); Enumeration<Integer> en = Collections.enumeration(list); // din orice Collection ``` while (en.hasMoreElements()) { int x = en.nextElement(); // Integer, nu Object (datorită generics) System.out.println(x); } } ``` }
—
`Iterator` oferă metodele:
* `hasNext()` – mai există elemente? * `next()` – returnează elementul următor; * `remove()` – șterge ultimul element returnat de `next()`.
Apeluri de tipul `list.remove(e)` direct într-un `for-each` pot produce `ConcurrentModificationException`.
import java.util.*; class DemoIterator { public static void main(String[] args) { List<String> l = new ArrayList<>(List.of("ana", "bad", "ion", "bogdan")); ``` // Variantă modernă: removeIf l.removeIf(s -> s.length() == 4); // șterge elementele cu 4 litere // Echivalent cu iterator.remove() Iterator<String> it = l.iterator(); while (it.hasNext()) { String s = it.next(); if (s.startsWith("b")) it.remove(); // sigur } System.out.println(l); } ``` }
—
`ListIterator` extinde `Iterator` și oferă în plus:
* navigare înapoi: `previous()`; * poziții: `nextIndex()`, `previousIndex()`; * inserare: `add(…)`; * înlocuire: `set(…)`.
Este util când trebuie să modificați lista „pe loc” sau să o parcurgeți bidirecțional.
import java.util.*; class DemoListIterator { public static void main(String[] args) { List<Integer> l = new ArrayList<>(List.of(0, 1, 2, 0, 3)); ``` // 1) Înlocuire „pe loc”: 0 -> 10 ListIterator<Integer> it = l.listIterator(); while (it.hasNext()) { if (it.next() == 0) it.set(10); } // 2) Inserare după 1 ListIterator<Integer> it2 = l.listIterator(); while (it2.hasNext()) { if (it2.next() == 1) { // elementul curent 1; cursorul este după 1 (între 1 și 2) it2.add(99); // inserează între 1 și 2 break; } } System.out.println(l); // [10, 1, 99, 2, 10, 3] } ``` }
Dacă folosim raw types (ex. `List`, `Iterator`), `next()`/`previous()` întorc `Object` și conversia devine responsabilitatea programatorului – cu risc de `ClassCastException` la rulare.
import java.util.*; class ObservatieIterator { public static void main(String[] args) { // 1) Parametrizat (recomandat): fără cast, sigur List<String> l = new ArrayList<>(List.of("ana", "ion")); Iterator<String> it = l.iterator(); String s1 = it.next(); // String, nu Object ListIterator<String> li = l.listIterator(l.size()); String last = li.previous(); // tot String System.out.println("OK (generic): " + s1 + ", " + last); ``` // 2) Neparametrizat (raw type): next()/previous() -> Object, necesită cast List raw = new ArrayList(); // WARNING: unchecked/raw type raw.add("text"); // compilează raw.add(10); // compilează (amestec de tipuri!) Iterator itr = raw.iterator(); // WARNING: raw Object o1 = itr.next(); // "text" ca Object Object o2 = itr.next(); // 10 ca Object // Castul e responsabilitatea ta; poate eșua la rulare: try { String s2 = (String) o2; // ClassCastException (Integer -> String) System.out.println(s2); } catch (ClassCastException ex) { System.out.println("Eroare la rulare (raw type): " + ex); } } ``` }
—
Fără generics, o colecție raw acceptă obiecte de orice fel, iar la citire trebuie să facem conversii (`cast`). Codul devine greu de urmărit, iar amestecul de tipuri poate produce ușor `ClassCastException` la rulare.
Genericitatea rezolvă exact aceste probleme:
* declarăm de la început tipul elementelor; * compilatorul verifică și interzice inserarea altor tipuri.
List<Integer> list = new ArrayList<>(); // parametrizare: lista conține DOAR Integer list.add(5); list.add(7); int x = list.iterator().next(); // fără cast; auto-unboxing (Integer -> int) int y = list.get(1); // tot fără cast // list.add("Text"); // eroare de compilare: tip incompatibil
În toate celelalte situații folosiți colecții parametrizate (`List<Student>`, `Map<String, Integer>`) pentru siguranță și claritate.
—
Metoda `equals(Object)` stabilește egalitatea logică dintre două instanțe. Definiția egalității aparține modelului de date (ex.: doi studenți pot fi considerați egali prin CNP, sau prin combinație de câmpuri).
Compararea unei instanțe non-null cu referința `null` produce întotdeauna rezultat de inegalitate. Pentru tipuri numerice în virgulă mobilă se recomandă `Double.compare` / `Float.compare`.
Metoda `hashCode()` returnează un rezumat numeric (`int`) al obiectului. Colecțiile pe bază de dispersie (`HashSet`, `HashMap`) folosesc acest rezumat pentru localizare rapidă.
În `HashSet` și `HashMap`, operațiile de apartenență și unicitate funcționează astfel:
1. colecția calculează `hashCode()` și alege „bucket-ul”; 2. confirmă prezența prin `equals()`.
import java.util.*; class EqualsHashDemo { ``` // Varianta corectă: equals și hashCode folosesc ACELEAȘI câmpuri static final class GoodStudent { private final String id; GoodStudent(String id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof GoodStudent g)) return false; return Objects.equals(id, g.id); } @Override public int hashCode() { return Objects.hash(id); } } // Varianta greșită: equals suprascris, hashCode NU static final class BadStudent { private final String id; BadStudent(String id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof BadStudent b)) return false; return Objects.equals(id, b.id); } // (fără hashCode) -> folosește Object.hashCode(), diferit pentru instanțe diferite } public static void main(String[] args) { // Caz corect GoodStudent a1 = new GoodStudent("42"); GoodStudent a2 = new GoodStudent("42"); System.out.println("a1.equals(a2) = " + a1.equals(a2)); // true System.out.println("a1.hashCode = " + a1.hashCode() + " | a2.hashCode = " + a2.hashCode()); // egale Set<GoodStudent> good = new HashSet<>(); good.add(a1); good.add(a2); System.out.println("HashSet<GoodStudent>.size = " + good.size()); // 1 // Caz greșit BadStudent b1 = new BadStudent("42"); BadStudent b2 = new BadStudent("42"); System.out.println("b1.equals(b2) = " + b1.equals(b2)); // true System.out.println("b1.hashCode = " + b1.hashCode() + " | b2.hashCode = " + b2.hashCode()); // diferite Set<BadStudent> bad = new HashSet<>(); bad.add(b1); bad.add(b2); System.out.println("HashSet<BadStudent>.size = " + bad.size()); // 2 (duplicate) System.out.println("bad.contains(new BadStudent(\"42\")) = " + bad.contains(new BadStudent("42"))); // false } ``` }