This is an old revision of the document!


Breviar 7

Colecții, iteratori, genericitate

1. Colecții

1.1 Interfața Collection și ierarhia colecțiilor

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:

  • List – colecții ordonate și indexate, care permit duplicate; implementările uzuale sunt ArrayList (acces aleator rapid) și LinkedList (inserări/ștergeri eficiente cu iteratorul). Vector și Stack apar în bibliografie, dar sunt considerate tipuri legacy (fac parte din limbaj încă de la primele versiuni); pentru comportament de stivă/coadă se preferă azi Deque (ex. ArrayDeque).
  • Set – mulțimi fără duplicate (în sens matematic); aici întâlnim HashSet (rapid, neordonat), LinkedHashSet (păstrează ordinea inserării) și TreeSet (menține elementele ordonate). Ordonarea formală este surprinsă de interfața SortedSet, care extinde Set și cere o ordine „naturală” sau dată de un Comparator, TreeSet fiind implementarea clasică.
  • Queue / Deque – structuri de tip coadă (eventual cu două capete). Implementările standard sunt ArrayDeque (coadă/stivă eficientă) și PriorityQueue (ordonare după prioritate, nu după inserare).

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).

În Java modern, colecțiile sunt parametrizate: după numele colecției se declară, între <…>, tipul elementelor. De exemplu, List<String> înseamnă o listă care conține doar șiruri de caractere.

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.

Colecțiile eterogene (elemente de orice fel) apar doar dacă:

  • folosim intenționat un tip general (ex. List<Object>), sau
  • folosim un raw type (ex. List fără parametrul de tip).

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
        }
    }
}

1.2 Liste (List)

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);
}
```
 
} 

* Declarați mereu tipul elementelor: `List<String>`, `List<Integer>`. * Pentru eliminări în timpul parcurgerii folosiți `Iterator.remove()` sau `removeIf(…)`. * Alegeți ArrayList când accentul este pe citire după index și LinkedList când aveți inserări/ștergeri locale cu iteratorul sau operații la ambele capete.

1.3 Mulțimi (Set și SortedSet)

`Set` modelează noțiunea de mulțime în sens matematic:

Nu pot exista două elemente `o1`, `o2` într-un `Set` pentru care `o1.equals(o2)` este `true`.

`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.

Într-un `SortedSet`, pentru orice două obiecte `o1`, `o2` ale colecției:

  • `o1.compareTo(o2)` sau `comparator.compare(o1, o2)` trebuie să fie valid (fără excepții),
  • pentru ordinea naturală, elementele `null` nu sunt permise (`NullPointerException`).

Î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)
}
```
 
} 

1.4 Dicționare (Map și SortedMap)

`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());
    }
}
```
 
} 

2. Iteratori și enumerări

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).

2.1 Enumeration

`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);
    }
}
```
 
} 

2.2 Iterator

`Iterator` oferă metodele:

* `hasNext()` – mai există elemente? * `next()` – returnează elementul următor; * `remove()` – șterge ultimul element returnat de `next()`.

Dacă modificați colecția în timpul parcurgerii, folosiți `iterator.remove()` (sau, mai simplu, `removeIf(…)` pe colecție).

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);
}
```
 
} 

2.3 ListIterator (liste, ambele sensuri)

`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ă parametrizăm colecția/iteratorul (ex. `List<String>`, `Iterator<String>`), metodele `next()`/`previous()` întorc direct tipul elementului și nu mai avem nevoie de cast.

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);
    }
}
```
 
} 

3. Genericitate (generics)

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 

Dacă aveți un caz real în care elementele pot fi de mai multe tipuri, NU reveniți la raw types. Folosiți:

  • `List<Object>` și verificați cu `instanceof` înainte de cast,
  • sau proiectați o ierarhie comună (o interfață, o clasă părinte) și parametrizați lista cu acest supertip.

În toate celelalte situații folosiți colecții parametrizate (`List<Student>`, `Map<String, Integer>`) pentru siguranță și claritate.

4. equals() vs hashCode()

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ă.

* Dacă două obiecte sunt egale prin `equals`, atunci trebuie să aibă același `hashCode()`. * Inversul nu este obligatoriu (două obiecte diferite pot avea același hashCode).

Î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
}
```
 
} 
poo/breviare/breviar-07.1763377226.txt.gz · Last modified: 2025/11/17 13:00 by george.tudor1906
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0