This is an old revision of the document!
Pe parcursul laboratoarelor și temelor ați folosit structuri de date oferite de API-ul Java. În cadrul acestui laborator le vom aprofunda.
În Java există o separare fundamentală între:
int, double, boolean, char etc.);java.lang.Object.
Obiectele de tip wrapper oferă funcționalități suplimentare sub forma unor metode (ex. rotateLeft(), toHex() etc.), însă, spre deosebire de tipurile primitive, acestea sunt gestionate de Garbage Collector, ceea ce poate face execuția mai lentă.
Din aceste motive, Java a ales să păstreze tipurile primitive pentru a evita costurile suplimentare ale obiectelor, mai ales în calculele numerice intensive.
numNodes == -1) puteți folosi clasele de tip wrapper care permit și starea null (ex. numNodes == null).
Pentru a permite utilizarea valorilor primitive în contexte ce necesită obiecte (de exemplu, în colecții despre care vom vorbi în următoarele laboratore), Java oferă clase wrapper dedicate fiecărui tip primitiv.
| Tip primitiv | Clasă wrapper corespunzătoare |
|---|---|
void | java.lang.Void |
boolean | java.lang.Boolean |
char | java.lang.Character |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
Un obiect wrapper poate fi construit:
Float pi = new Float(3.14); Float pi2 = new Float("3.14");
Dacă șirul nu poate fi convertit într-o valoare numerică validă, se afișează o eroare la run-time de tip NumberFormatException.
Toate clasele numerice (Integer, Double, Float etc.) au la dispoziție metode pentru conversia valorii interne în alte forme primitive:
Double size = new Double(32.76); double d = size.doubleValue(); // 32.76 float f = size.floatValue(); // 32.76f long l = size.longValue(); // 32L int i = size.intValue(); // 32
Aceste metode sunt echivalente cu operațiile de conversie explicită (cast) între tipurile primitive.
Începând cu Java 5, conversia între tipurile primitive și wrapper se face automat:
int primitiveValue = 42; Integer objectValue = primitiveValue; // autoboxing automat de la int → Integer Integer anotherObject = new Integer(99); int anotherPrimitive = anotherObject; // unboxing automat de la Integer → int Integer a = 10; Integer b = 20; int sum = a + b; // a și b sunt unboxed automat, apoi rezultatul e autoboxed dacă e atribuit unui Integer
Compilatorul inserează conversiile în mod implicit, făcând codul mai concis și mai lizibil.
Colecțiile sunt structuri de date esențiale care permit gruparea, organizarea și manipularea eficientă a obiectelor. În Java, colecțiile oferă o modalitate standardizată de a lucra cu seturi, liste, cozi și mapări, reprezentând una dintre cele mai puternice părți ale limbajului.
La nivel de bază, Java oferă tablouri (arrays), despre care am învățat în laboratoarele trecute că sunt structuri fixe, rapide, dar inflexibile. Ele au o dimensiune fixă și nu pot crește sau micșora dinamic, ceea ce devine o limitare serioasă în aplicațiile reale.
Colecțiile au fost introduse pentru a răspunde acestor limitări, oferind structuri dinamice capabile să se redimensioneze automat și să ofere operații avansate de căutare, filtrare și sortare.
| Etapă | Soluție oferită | Limitări |
|---|---|---|
| Java 1.0 | Vector și Hashtable | Lipsă de tipizare, design inconsistent |
| Java 1.2 | Collections Framework | Introduce interfețele Collection și Map |
| Java 5+ | Generics, autoboxing, unboxing | Colecții tipizate și mai sigure |
Colecțiile moderne permit:
Frameworkul de colecții se bazează pe două ierarhii paralele, definite în pachetul java.util:
| Ierarhie | Interfață rădăcină | Ce reprezintă | Exemple |
|---|---|---|---|
| Colecții de elemente | Collection | Grup de obiecte independente | List, Set, Queue |
| Mapări cheie–valoare | Map | Asocieri între chei unice și valori | HashMap, TreeMap |
Map face parte din framework, nu extinde Collection deoarece logica sa (chei și valori separate) diferă conceptual de cea a colecțiilor simple.
Fiecare colecție are un scop diferit:
Prin folosirea ierarhiilor de mai sus, avem următoarele beneficii:
Interfața Collection<E> definește un comportament comun pentru toate colecțiile care gestionează elemente individuale. Ea nu dictează cum sunt stocate datele, ci ce operații sunt posibile.
| Metodă | Descriere | Observații |
|---|---|---|
boolean add(E e) | Adaugă un element | Returnează false dacă e duplicat (în cazul Set) |
boolean remove(E e) | Elimină un element existent | Poate arunca UnsupportedOperationException |
boolean contains(E e) | Verifică dacă elementul există | Compară prin equals() |
int size() | Numărul elementelor | |
boolean isEmpty() | Verifică dacă e goală | |
Iterator<E> iterator() | Permite parcurgerea elementelor | Folosit pentru bucle controlate |
ConcurrentModificationException.
| Metodă | Descriere |
|---|---|
addAll(Collection c) | adaugă toate elementele unei alte colecții |
removeAll(Collection c) | elimină toate elementele dintr-o altă colecție |
containsAll(Collection c) | verifică dacă toate elementele există |
Un iterator este un obiect care permite traversarea unei colecţii şi modificarea acesteia (ex: ştergere de elemente) în mod selectiv. Puteţi obţine un iterator pentru o colecţie, apelând metoda sa iterator(). Interfaţa Iterator este următoarea:
public interface Iterator<E> { boolean hasNext(); E next(); void remove(); // optional }
Exemplu de folosire a unui iterator:
Collection<Double> col = new ArrayList<Double>(); Iterator<Double> it = col.iterator(); while (it.hasNext()) { Double backup = it.next(); // apelul it.next() trebuie realizat înainte de apelul it.remove() if (backup < 5.0) { it.remove(); } }
Apelul metodei remove() a unui iterator face posibilă eliminarea elementului din colecţie care a fost întors la ultimul apel al metodei next() din acelaşi iterator.
În exemplul anterior, toate elementele din colecţie mai mici decât 5 for fi şterse la ieşirea din bucla while.
Această construcţie permite (într-o manieră expeditivă) traversarea unei colecţii.
Collection collection = new ArrayList(); for (Object o : collection) System.out.println(o);
Construcţia for-each se bazează, în spate, pe un iterator, pe care îl ascunde. Prin urmare nu putem şterge elemente în timpul iterării.
În această manieră pot fi parcurşi şi vectori oarecare. De exemplu, collection ar fi putut fi definit ca Object[].
import java.util.*; public class CollectionExample { public static void main(String[] args) { // Folosim Collection cu ArrayList ca implementare Collection<String> fruits = new ArrayList<>(); // Diamond operator <> // Adăugăm elemente fruits.add("Apple"); fruits.add("Banana"); fruits.add("Cherry"); // Verificăm dimensiunea și dacă este goală System.out.println("Dimensiune: " + fruits.size()); System.out.println("Este goală? " + fruits.isEmpty()); // Verificăm dacă un element există System.out.println("Conține 'Banana'? " + fruits.contains("Banana")); // Iterăm prin elemente folosind Iterator System.out.println("Elemente în colecție:"); Iterator<String> it = fruits.iterator(); while (it.hasNext()) { System.out.println(it.next()); } // Eliminăm un element fruits.remove("Apple"); System.out.println("Colecție după eliminare: " + fruits); } }
<>:
Collection<String> fruits = new ArrayList<>(); // corect, specificat doar în stânga Collection<String> fruits = new ArrayList<String>(); // corect, dar nepreferat
Vom învăța mai multe despre acesta în laboratorul despre genericitate.
Un Set este o colecție fără duplicate. Elementele sunt comparate folosind metodele equals() și hashCode().
Caracteristici:
HashSet)null (în unele implementări)HashSet – rapid, dar neordonatLinkedHashSet – păstrează ordinea de inserareTreeSet – menține ordinea naturală sau definită de un Comparator| Implementare | Structură internă | Ordine elemente | Permite duplicate? | Complexitate adăugare / căutare / ștergere | Avantaje | Dezavantaje | Când se folosește |
|---|---|---|---|---|---|---|---|
| HashSet | Bazat pe HashMap intern (folosește hashing pentru distribuție rapidă a elementelor) | ❌ Ordine imprevizibilă | ❌ Nu | Aproximativ O(1) (constantă amortizată) | Foarte rapid pentru operații de bază (add, remove, contains) | Nu păstrează ordinea inserării; performanța depinde de funcția hashCode() | Când performanța este prioritară și ordinea nu contează (ex: filtrarea duplicatelor, verificări rapide de apartenență) |
| LinkedHashSet | Extinde HashSet dar menține o listă dublu înlănțuită a elementelor pentru ordinea inserării | ✅ Ordinea inserării | ❌ Nu | Aproximativ O(1) | Păstrează ordinea de inserare, performanță foarte bună | Ușor mai lent decât HashSet (suplimentar pentru menținerea listei) | Când e nevoie de viteză dar și de o ordine previzibilă (ex: cache, istorice, afisări ordonate) |
| TreeSet | Implementat prin TreeMap intern (arbore roșu-negru) | ✅ Ordine naturală (sau definită de un Comparator) | ❌ Nu | O(log n) | Menține elementele sortate, permite căutări bazate pe ordine (headSet, tailSet, etc.) | Mai lent decât HashSet și LinkedHashSet | Când ai nevoie ca elementele să fie sortate sau să poți naviga în intervale (ex: rapoarte, topuri, ordonări lexicografice) |
hashCode() sau equals() după inserarea elementului, comportamentul colecției devine imprevizibil. Vom vorbi despre aceste pe larg în secțiunile de mai jos.
| Subinterfață | Descriere | Metode specifice | Implementări principale |
|---|---|---|---|
SortedSet | Elemente sortate | subSet(), headSet(), tailSet() | TreeSet, ConcurrentSkipListSet |
NavigableSet | Căutare a elementelor apropiate | higher(), lower(), ceiling(), floor() | TreeSet, ConcurrentSkipListSet |
ConcurrentSkipListSet oferă o variantă thread-safe, bazată pe o skip list, potrivită pentru medii concurente, dar care depășește scopul acestui laborator.
import java.util.*; public class HashSetExample { public static void main(String[] args) { // HashSet Set<String> cities = new HashSet<>(); cities.add("Bucharest"); cities.add("Cluj"); cities.add("Bucharest"); // ignorat (duplicat) System.out.println(cities); // ordinea nu este garantată System.out.println("Conține Cluj? " + cities.contains("Cluj")); // LinkedHashSet Set<Integer> numbers = new LinkedHashSet<>(); numbers.add(3); numbers.add(1); numbers.add(2); System.out.println(numbers); // păstrează ordinea de inserare → [3, 1, 2] // TreeSet Set<String> names = new TreeSet<>(); names.add("Ana"); names.add("Ion"); names.add("Maria"); System.out.println(names); // sortat natural → [Ana, Ion, Maria] } }
List este o colecție ordonată ce permite elemente duplicate și acces prin index.
Caracteristici:
ArrayList – acces rapid, inserări lente în mijlocLinkedList – inserări rapide, acces lent la elementeVector – versiune veche, sincronizată (de obicei evitată)| Metodă | Descriere | Exemple |
|---|---|---|
void add(int index, E e) | Inserează la poziția indicată | list.add(2, “Hello”) |
E get(int index) | Returnează elementul | list.get(0) |
E set(int index, E e) | Înlocuiește un element | |
void remove(int index) | Elimină elementul la index |
Iterator. Evitați modificarea listei în timpul iterării, deoarece poate genera ConcurrentModificationException.
| Implementare | Structură internă | Ordine elemente | Permite duplicate? | Acces prin index | Complexitate (add / get / remove) | Avantaje | Dezavantaje | Când se folosește |
|---|---|---|---|---|---|---|---|---|
| ArrayList | Bazat pe array dinamic (redimensionat automat) | ✅ Ordinea inserării | ✅ Da | ✅ Da | add → amortizat O(1) get → O(1) remove → O(n) | Acces foarte rapid prin index, eficient la citire | Inserările/ștergerile în mijloc sunt lente; cost mare la redimensionare | Când ai multe citiri și parcurgeri, dar puține inserări în mijloc |
| LinkedList | listă dublu înlănțuită | ✅ Ordinea inserării | ✅ Da | ⚠️ Lent (traversare secvențială) | add/remove → O(1) la capete get → O(n) | Inserare/ștergere rapidă oriunde în listă | Acces lent la elemente; overhead de memorie mai mare | Când ai multe inserări/ștergeri, dar nu acces frecvent prin index |
Vector nu a fost inclusă, deoarece nu mai este folosită în proiectele moderne.
import java.util.*; public class ArrayListExample { public static void main(String[] args) { // ArrayList List<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Apple"); // duplicatele sunt permise System.out.println(fruits); // [Apple, Banana, Apple] System.out.println("Primul element: " + fruits.get(0)); // LinkedList List<String> tasks = new LinkedList<>(); tasks.add("Wake up"); tasks.add("Make coffee"); tasks.addFirst("Stretch"); System.out.println(tasks); // [Stretch, Wake up, Make coffee] tasks.removeLast(); System.out.println(tasks); // [Stretch, Wake up] } }
Queue modelează o coadă de așteptare. Elementele sunt procesate în ordinea în care sunt adăugate (FIFO).
Caracteristici:
PriorityQueue - permite inserarea într-o coadă păstrând o ordine specificăArrayDeque - permite inserarea și ștergerea din ambele capete
ArrayDeque.LinkedList implementează interfața Deque care extinde Queue, deoarece o listă înlănțuită permite inserarea și ștergerea de la ambele capete, similar cu un ArrayDeque.
| Metodă | Descriere | Comportament la gol |
|---|---|---|
boolean offer(E e) | Încearcă să adauge element | Returnează `false` dacă nu e loc |
E poll() | Scoate și returnează elementul din față | null dacă e goală |
E peek() | Returnează elementul din față fără a-l elimina | null dacă e goală |
| Implementare | Structură internă | Ordine elemente | Permite duplicate? | Acceptă null? | Complexitate (add / poll / peek) | Avantaje | Dezavantaje | Când se folosește |
|---|---|---|---|---|---|---|---|---|
| LinkedList | Listă dublu înlănțuită | ✅ Ordinea inserării (FIFO) sau LIFO (prin Deque) | ✅ Da | ✅ Da | add → O(1) poll → O(1) peek → O(1) | Flexibilă — poate fi folosită și ca List, Queue sau Stack | Acces aleator lent (O(n)); overhead de memorie mai mare | Când ai nevoie de o coadă simplă, ușor de convertit în listă sau stivă |
| ArrayDeque | Buffer circular dinamic | ✅ Ordinea inserării (FIFO) sau LIFO | ✅ Da | ❌ Nu | add → amortizat O(1) poll → O(1) peek → O(1) | Mai rapidă decât LinkedList; foarte eficientă pentru stive și cozi | Nu este thread-safe; nu acceptă null | Când vrei o coadă sau stivă performantă într-un singur fir de execuție |
| PriorityQueue | Heap binar (min-heap) | ⚠️ Ordine bazată pe prioritate, nu pe inserare | ✅ Da | ❌ Nu | add → O(log n) poll → O(log n) peek → O(1) | Menține automat ordinea elementelor prin comparator | Nu păstrează ordinea de inserare; nu este thread-safe | Când vrei procesarea elementelor în funcție de prioritate |
Queue cum ar fi: ArrayBlockingQueue, LinkedBlockingQueue, ConcurrentLinkedQueue, DelayQueue, SynchronousQueue, LinkedTransferQueue, doar că cele mai folosite implementări sunt cele din tabelul de mai sus.
import java.util.*; public class Main { public static void main(String[] args) { // PriorityQueue Queue<Integer> pq = new PriorityQueue<>(); pq.add(5); pq.add(1); pq.add(3); while (!pq.isEmpty()) { System.out.println(pq.poll()); // scoate în ordine: 1, 3, 5 } // ArrayDeque (Stack) Deque<String> stack = new ArrayDeque<>(); stack.push("A"); stack.push("B"); stack.push("C"); System.out.println(stack.pop()); // C (comportament de stivă) System.out.println(stack); // [B, A] } }
Map<K, V> asociază chei unice cu valori. Este baza pentru implementarea tabelelor de asociere, cache-urilor și bazelor de date simple în memorie.
| Metodă | Descriere |
|---|---|
V put(K key, V value) | Adaugă o pereche cheie–valoare |
V get(K key) | Returnează valoarea asociată cheii |
V remove(K key) | Elimină o pereche din map |
int size() | Numărul perechilor |
put() o suprascrie și returnează vechea valoare.HashSet, implementările de tip hash (ex: HashMap, LinkedHashMap) necesită ca obiectele să implementeze metodele equals() și hashCode(). Alte implementări, cum ar fi TreeMap, folosesc ordinea naturală sau un Comparator și nu se bazează pe hashCode().
| Metodă | Returnează | Conținut |
|---|---|---|
keySet() | Set<K> | Toate cheile unice |
values() | Collection<V> | Toate valorile (pot fi duplicate) |
entrySet() | Set<Map.Entry<K,V» | Perechi cheie–valoare |
| Implementare | Structură internă | Ordine elemente | Permite duplicate? | Acceptă null? | Complexitate (put / get / remove) | Avantaje | Dezavantaje | Când se folosește |
|---|---|---|---|---|---|---|---|---|
| HashMap | Table hash cu liste înlănțuite/bucket-uri sau red-black tree după coliziuni | ❌ Nicio ordine garantată | ❌ Nu (cheile sunt unice) | ✅ Chei și valori | put → O(1) amortizat get → O(1) remove → O(1) | Foarte rapidă; implementare standard | Nu păstrează ordinea de inserare | Când ai nevoie de un map rapid, fără grijă pentru ordine |
| LinkedHashMap | HashMap + listă dublu înlănțuită pentru ordinea inserării | ✅ Ordinea inserării sau LRU dacă este activată | ❌ Nu | ✅ Chei și valori | put → O(1) amortizat get → O(1) remove → O(1) | Păstrează ordinea inserării sau accesului; utilă pentru cache LRU | Ușor mai lentă și mai multă memorie decât HashMap | Când ai nevoie de map rapid, dar ordonat după inserare sau acces |
| TreeMap | Red-Black Tree (arbore echilibrat) | ✅ Ordine naturală sau Comparator | ❌ Nu | ❌ Chei ✅ Valori | put → O(log n) get → O(log n) remove → O(log n) | Ordine sortată; suportă subseturi și navigare | Mai lentă decât HashMap; nu permite chei null | Când ai nevoie de un map sortat sau de intervale de chei |
TreeMap implementează interfețele SortedMap și NavigableMap, ceea ce îi permite să mențină cheile într-o ordine sortată și să ofere metode pentru navigarea în arbore (de exemplu: subMap(), headMap(), tailMap(), higherKey(), lowerKey()).
import java.util.*; public class MapExample { public static void main(String[] args) { // HashMap Map<String, Integer> map = new HashMap<>(); map.put("Apple", 10); map.put("Banana", 20); map.put("Cherry", 30); map.put("Apple", 40); // suprascrie valoarea pentru cheie "Apple" System.out.println("HashMap: " + map); // Iterare for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } // Output HashMap (aleator pe baza metodei hashCode(): // Banana -> 20 // Apple -> 40 // Cherry -> 30 // LinkedHashMap LinkedHashMap<String, Integer> linkedMap = new LinkedHashMap<>(); linkedMap.put("Apple", 10); linkedMap.put("Banana", 20); linkedMap.put("Cherry", 30); System.out.println("LinkedHashMap: " + linkedMap); // Iterare for (Map.Entry<String, Integer> entry : linkedMap.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } // Output LinkedHashMap (ordonat pe baza inserării): // Apple -> 10 // Banana -> 20 // Cherry -> 30 // TreeMap TreeMap<String, Integer> treeMap = new TreeMap<>(); treeMap.put("Apple", 10); treeMap.put("Banana", 20); treeMap.put("Cherry", 30); System.out.println("TreeMap: " + treeMap); // Iterare for (Map.Entry<String, Integer> entry : treeMap.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } // Output TreeMap (ordonat lexicografic dupa cheia String): // Apple -> 10, // Banana -> 20 // Cherry -> 30 // SubMap System.out.println("SubMap (A-C): " + treeMap.subMap("Apple", "Cherry")); // Output SubMap: SubMap (A-C): {Apple=10, Banana=20} } }
subMap(“Apple”, “Cherry”) returnează toate cheile între “Apple” (inclusiv) și “Cherry” (exclusiv).
List.of()) sunt imutabile și aruncă UnsupportedOperationException la add() sau remove().
// Listă imutabilă List<String> immutableList = List.of("Apple", "Banana", "Cherry"); immutableList.add("Orange"); // eroare // Listă mutabilă List<String> mutableList = new ArrayList<>(List.of("Apple", "Banana", "Cherry")); mutableList.add("Orange"); // funcționează
Sortarea elementelor simple (ex. String, Integer, Float etc.) într-un ArrayList este directă, dar pentru obiecte complexe, cum ar fi o listă ArrayList<Student> sortată după media anilor, avem nevoie de Comparable sau Comparator.
| Comparable | Comparator | |
|---|---|---|
| Logica de sortare | În clasa obiectului (sortare naturală) | Într-o clasă separată, permițând mai multe strategii |
| Implementare | Clasa implementează Comparable și suprascrie compareTo() | O altă clasă (sau internă) implementează Comparator și metoda compare() |
| Metoda de comparare | int compareTo(T o) – returnează negativ, zero sau pozitiv | int compare(T o1, T o2) – returnează negativ, zero sau pozitiv |
| Metoda de sortare | Collections.sort(List) | Collections.sort(List, Comparator) sau List.sort(Comparator) |
| Pachet | java.lang.Comparable | java.util.Comparator |
class Student implements Comparable<Student> { private String name; private String surname; public Student(String name, String surname) { this.name = name; this.surname = surname; } // Ordonare naturală @Override public int compareTo(Student o) { return surname.equals(o.surname) ? name.compareTo(o.name) : surname.compareTo(o.surname); } } public class Main { public static void main(String[] args) { ArrayList<Student> students = new ArrayList<>(); // Sortare folosind metoda sort() din Collections Collections.sort(students); } }
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(5, 1, 3623, 13, 7)); // Sortare descrescătoare folosind Comparator anonim Collections.sort(numbers, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); System.out.println(numbers); // [3623, 13, 7, 5, 1] // Alternativ, folosind metoda sort() din List numbers.sort(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } });
sort() din Collections sau metoda sort() din List pentru a ordona un tip de colecție.sort() din List apelează metoda de sortare din Collections care folosește un algoritm foarte eficient numit TimSort.
| Aspect | Comparable | Comparator |
|---|---|---|
| Când se folosește | Când clasa are o ordine „naturală” clară și vrem o singură metodă de sortare implicită | Când vrem sortări multiple sau dinamice sau clasa nu poate implementa Comparable |
| Exemplu | Sortarea alfabetică a unui `Student` după `surname` și `name` | Sortarea unei liste de `Student` după media anilor, apoi vârstă, apoi nume |
| Avantaje | Simplu, nu necesită clase externe | Flexibil, permite definirea mai multor strategii de sortare |
| Limitări | Poate exista o singură metodă de sortare „standard” per clasă | Necesită clasă separată sau comparator anonim |
Comparable.Comparator.
În Java, metodele hashCode() și equals() sunt fundamentale pentru colecțiile care folosesc hashing, cum ar fi HashSet, HashMap sau LinkedHashMap. Înțelegerea lor corectă este esențială pentru a evita comportamente neașteptate.
Metoda hashCode() returnează un număr întreg care reprezintă obiectul. Acest număr este folosit de colecțiile bazate pe hash pentru a determina bucket-ul în care va fi plasat obiectul. Astfel, accesul la elemente devine rapid, aproape constant în timp.
Dacă două obiecte sunt considerate egale prin equals(), hashCode-ul lor trebuie să fie identic. Dacă hashCode-ul este diferit, obiectele sunt sigur inegale.
hashCode() trebuie să fie consistentă: dacă obiectul nu se modifică, valoarea returnată trebuie să fie aceeași.hashCode() după ce obiectul a fost adăugat într-o colecție poate cauza pierdere de elemente sau comportament neașteptat.
Definirea metodelor pentru hash:
public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student other = (Student) obj; return age == other.age && name.equals(other.name); } @Override public int hashCode() { return Objects.hash(name, age); } }
Adăugarea în HashSet:
Set<Student> students = new HashSet<>(); students.add(new Student("Alice", 20)); students.add(new Student("Alice", 20)); // Nu se adaugă, equals() returnează true System.out.println(students.size()); // Afișează 1
click dreapta pe cod → Generate… → equals() and hashCode() de unde selectăm toate câmpurile care ne interesează.hashCode() cu un algoritm diferit față de cel din Objects.hash() recomandat de IntelliJ, însă alegeți algoritmul cu grijă, astfel încât să nu penalizați performanța aplicației pentru use-case-ul aplicației voastre.
Un iterator este un obiect care permite traversarea unei colecţii şi modificarea acesteia (ex: ştergere de elemente) în mod selectiv. Puteţi obţine un iterator pentru o colecţie, apelând metoda sa iterator(). Interfaţa Iterator este următoarea:
public interface Iterator<E> { boolean hasNext(); E next(); void remove(); // optional }
Exemplu de folosire a unui iterator:
Collection<Double> col = new ArrayList<Double>(); Iterator<Double> it = col.iterator(); while (it.hasNext()) { Double backup = it.next(); // apelul it.next() trebuie realizat înainte de apelul it.remove() if (backup < 5.0) { it.remove(); } }
Apelul metodei remove() a unui iterator face posibilă eliminarea elementului din colecţie care a fost întors la ultimul apel al metodei next() din acelaşi iterator.
În exemplul anterior, toate elementele din colecţie mai mici decât 5 for fi şterse la ieşirea din bucla while.
Map sunt HashMap (neordonat, nesortat), TreeMap (map sortat) și LinkedHashMap (map ordonat)1. În cadrul acestui exercițiu, veți implementa o clasă numită Student, care are patru membri:
Clasa Student va implementa interfața Comparable<Student>, folosită la sortări, prin implementarea metodei compareTo. În metoda compareTo, studenții vor fi comparați mai întâi după medie, apoi după numele de familie, apoi după prenume. După implementarea clasei, sortați elementele listei “students” din metoda main folosind metoda Collections.sort().
2. Adăugați lista “copyStudents” într-un PriorityQueue (cu ajutorul metodei Collection.addAll), care folosește un Comparator (utilizați constructorul PriorityQueue) sau o funcție anonimă. Elementele vor fi sortate crescător după id.
3. Suprascrieți metodele equals și hashCode în clasa Student (puteți folosi generatorul de cod din IntelliJ). După aceasta, adăugați în lista asociată studentilor din “studentMap” patru materii aleatorii. Pentru a obține materiile aleatorii, urmăriți indicațiile din codul din funcția main.
4. Extindeți clasa LinkedHashSet<Integer> cu o clasă în care se vor putea adăuga doar numere pare. Metoda add va fi suprascrisă astfel încât să nu permită adăugarea de numere impare în colecție. Efectuați aceeași operațiune și pentru clasele TreeSet și HashSet. Observați diferențele privind ordinea de inserare a elementelor între cele trei clase menționate.
Scheletul il puteți gasi pe github. Soluția trebuie încărcată pe devmind.