This shows you the differences between two versions of the page.
|
poo:breviare:breviar-07 [2020/11/17 10:58] mihai.nan |
poo:breviare:breviar-07 [2025/11/19 09:25] (current) george.tudor1906 |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== Breviar 7 ====== | ====== Breviar 7 ====== | ||
| + | === Colecții, iteratori, genericitate === | ||
| + | === 1. Colecții === | ||
| - | <HTML> | + | == 1.1 Interfața Collection și ierarhia colecțiilor == |
| - | <iframe src="https://docs.google.com/file/d/1sm4EqZihlBQWomEfEXRwojJ0WBjwrDL2/preview" width="640" height="720"></iframe> | + | |
| - | </HTML> | + | {{:poo:breviare:collection.png?600|}} |
| + | |||
| + | 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). | ||
| + | |||
| + | <note important> | ||
| + | Î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. | ||
| + | </note> | ||
| + | |||
| + | <note warning> | ||
| + | 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). </note> | ||
| + | <code java> | ||
| + | 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 | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | == 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** și ale căror elemente pot fi accesate după **poziție** (index). În practică, cele mai folosite implementări sunt **ArrayList** și **LinkedList**. | ||
| + | |||
| + | ArrayList oferă **acces aleator foarte rapid** la elemente, cu cost mai mare pentru inserări/ștergeri în interiorul listei. LinkedList stochează elementele într-o listă înlănțuită, ceea ce face **inserările și ștergerile** locale mai eficiente (folosind iteratorul), dar accesul la un element "din mijloc" este mai lent. LinkedList implementează și Deque, astfel că poate lucra comod cu elementele de la ambele capete (ex. addFirst, addLast). Pentru stivă/coadă se preferă ArrayDeque, iar pentru liste obișnuite ArrayList/LinkedList. | ||
| + | |||
| + | <code java> | ||
| + | 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); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <note tip> | ||
| + | * 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. | ||
| + | </note> | ||
| + | |||
| + | == 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//. | ||
| + | </note> | ||
| + | |||
| + | 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**. | ||
| + | |||
| + | <note warning> | ||
| + | Î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), iar pentru ordinea naturală, elementele //null// **nu sunt permise**. | ||
| + | </note> | ||
| + | |||
| + | <code java> | ||
| + | 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] | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | |||
| + | == 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, valoareImplicita)//; | ||
| + | * test de apartenență: //containsKey//, //containsValue//; | ||
| + | * eliminare: //remove(k)//; | ||
| + | * parcurgere: //entrySet()//, //keySet()//, //values()//. | ||
| + | |||
| + | În practică, cele mai folosite implementări sunt: | ||
| + | |||
| + | * **HashMap** - rapid, fără ordine de iterare; permite o cheie //null// și valori //null//; | ||
| + | * **LinkedHashMap** - ca //HashMap//, dar păstrează ordinea inserării; | ||
| + | * **TreeMap** - menține cheile **ordonate** (natural sau prin //Comparator//); nu acceptă chei //null//; | ||
| + | * **Hashtable** - tip vechi; se preferă //HashMap// sau //ConcurrentHashMap//. | ||
| + | |||
| + | **SortedMap** este un Map cu chei păstrate în **ordine crescătoare**; implementarea clasică este **TreeMap**. | ||
| + | |||
| + | <code java> | ||
| + | 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) Parcurgere eficientă cu entrySet() | ||
| + | for (Map.Entry<String, Integer> e : sorted.entrySet()) { | ||
| + | System.out.println(e.getKey() + " => " + e.getValue()); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | === 2. Iteratori și enumerări === | ||
| + | |||
| + | Enumerările și iteratorii descriu modalități de **parcurgere secvențială** a unei colecții. În Java, parcurgerea se face în principal cu: | ||
| + | |||
| + | * **Iterator** (și pentru liste, //ListIterator//), | ||
| + | * sintaxa **for-each**: //for (T e : colectie) { ... }//. | ||
| + | |||
| + | == 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//. | ||
| + | |||
| + | <code java> | ||
| + | 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); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | } | ||
| + | </code> | ||
| + | |||
| + | |||
| + | == 2.2 Iterator == | ||
| + | |||
| + | **Iterator** oferă metodele: | ||
| + | * **hasNext()** - mai există elemente?; | ||
| + | * **next()** - returnează elementul următor; | ||
| + | * **remove()** - șterge ultimul element returnat de next(). | ||
| + | |||
| + | <note important> | ||
| + | Dacă modificați colecția în timpul parcurgerii, folosiți **iterator.remove()** (sau, mai simplu, //removeIf(...)// pe colecție). | ||
| + | </note> | ||
| + | |||
| + | <code java> | ||
| + | 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); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | == 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. | ||
| + | |||
| + | <code java> | ||
| + | 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] | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <note warning> | ||
| + | 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. </note> | ||
| + | |||
| + | <code java> | ||
| + | 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); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | === 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. | ||
| + | |||
| + | <code java> | ||
| + | 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 | ||
| + | </code> | ||
| + | |||
| + | <note warning> | ||
| + | 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**. </note> | ||
| + | |||
| + | |||
| + | === 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). | ||
| + | |||
| + | Metoda **hashCode()** returnează un **rezumat numeric** (int) al obiectului. Colecțiile pe bază de dispersie (//HashSet//, //HashMap//) folosesc acest rezumat pentru localizare rapidă. | ||
| + | |||
| + | <note important> | ||
| + | * Dacă două obiecte sunt **egale** prin //equals//, atunci **trebuie** să aibă același //hashCode()//. | ||
| + | </note> | ||
| + | |||
| + | În **HashSet** și **HashMap**, operațiile de apartenență și unicitate funcționează astfel: | ||
| + | |||
| + | - colecția calculează **hashCode()** și alege locul elementului; | ||
| + | - confirmă prezența prin **equals()**. | ||
| + | |||
| + | <code java> | ||
| + | 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 | ||
| + | } | ||
| + | } | ||
| + | </code> | ||