= Colecții =
* Responsabil: Cristina Coman * Data publicării: 16.11.2013 * Data ultimei modificări: 20.11.2013
În pachetul java.util (pachet standard din JRE) existǎ o serie de clase pe care le veti găsi folositoare. Collections Framework este o arhitectură unificată pentru reprezentarea şi manipularea colecţiilor. Ea conţine: * interfeţe: permit colecţiilor sǎ fie folosite independent de implementǎrile lor * implementǎri * algoritmi metode de prelucrare (căutare, sortare) pe colecţii de obiecte oarecare. Algoritmii sunt polimorfici: un astfel de algoritm poate fi folosit pe implementări diferite de colecţii, deoarece le abordeazǎ la nivel de interfaţǎ. Colecţiile oferǎ implementǎri pentru urmǎtoarele tipuri: * mulţime (ordinea elementelor este neimportantǎ) * listǎ (ordinea elementelor conteazǎ) * tabel asociativ (perechi cheie-valoare)
Existǎ o interfaţǎ, numitǎ Collection, pe care o implementeazǎ majoritatea claselor ce desemneazǎ colecţii din java.util. Explicaţii suplimentare gǎsiţi pe Java Tutorials - Collection
Exemplul de mai jos construieşte o listǎ populatǎ cu nume de studenţi:
Collection names = new ArrayList(); names.add("Andrei"); names.add("Matei");
Colecţiile pot fi parcurse (element cu element) folosind: * iteratori * o construcţie for specialǎ (cunoscutǎ sub numele de for-each)
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 c = new ArrayList(); Iterator it = c.iterator(); while (it.hasNext()) { //verificari asupra elementului curent: it.next(); it.remove(); }
Aceastǎ construcţie permite (într-o manierǎ expeditivǎ) traversarea unei colecţii. for-each este foarte similar cu for
. Urmǎtorul exemplu parcurge elementele unei colecţii şi le afişeazǎ.
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[]
.
Fie urmǎtoarea porţiune de cod:
c.add("Test"); Iterator it = c.iterator(); while (it.hasNext()) { String s = it.next(); // EROARE: next intoarce Object si este nevoie de cast explicit la String String s = (String)it.next(); // OK }
Am definit o colecţie c
, de tipul ArrayList
(pe care îl vom examina într-o secţiune urmǎtoare). Apoi, am adǎugat în colecţie un element de tipul String
. Am realizat o parcurgere folosind un iterator, şi am încercat obţinerea elementului nostru folosind apelul: String s = it.next()
;. Funcţia next
însǎ întoarce un obiect de tip Object
. Prin urmare apelul va eşua. Varianta corectǎ este String s = (String)it.next()
;.
Am fi putut preciza, din start, ce tipuri de date dorim într-o colecţie:
Collection<String> c = new ArrayList<String>(); c.add("Test"); c.add(2); // EROARE! Iterator<String> it = c.iterator(); while (it.hasNext()) { String s = it.next(); }
O listǎ este o colecţie ordonatǎ. Listele pot conţine elemente duplicate. Pe langǎ operaţiile moştenite de la Collection
, interfaţa List defineşte urmǎtoarele operaţii:
* T get(int index)
- întoarce elementul de la poziţia index
* T set(int index, T element)
- modificǎ elementul de la poziţia index
* void add(int index, T element)
- adaugǎ un element la poziţia index
* T remove(int index)
- şterge elementul de la poziţia index
Alǎturi de List
, este definitǎ interfaţa ListIterator, ce extinde interfaţa Iterator
cu metode de parcurgere în ordine inversǎ.
List
posedǎ douǎ implementǎri standard:
* ArrayList
- implementare sub formǎ de vector. Accesul la elemente se face în timp constant: O(1)
* LinkedList
- implementare sub formǎ de listǎ dublu înlǎnţuitǎ. Prin urmare, accesul la un element nu se face în timp constant, fiind necesarǎ o parcurgere a listei: O(n)
.
Algoritmi implementaţi:
* sort
- realizeazǎ sortarea unei liste
* binarySearch
- realizaeazǎ o cǎutare binarǎ a unei valori într-o listǎ
În general, algoritmii pe colecţii sunt implementaţi ca metode statice în clasa Collections.
Atenţie: Nu confundaţi interfaţa Collection
cu clasa Collections
. Spre deosebire de prima, a doua este o clasǎ ce conţine exclusiv metode statice. Aici sunt implementate diverse operaţii asupra colecţiilor.
Iatǎ un exemplu de folosire a sortǎrii:
List<Integer> l = new ArrayList<Integer>(); l.add(5); l.add(7); l.add(9); l.add(2); l.add(4); Collections.sort(l); System.out.println(l);
Mai multe detalii despre algoritmi pe colecţii gǎsiţi pe Java Tutorials - Algoritmi pe liste
Rularea exemplului de sortare ilustrat mai sus aratǎ cǎ elementele din ArrayList
se sorteazǎ crescator. Ce se întâmplǎ când dorim sǎ realizǎm o sortare particularǎ pentru un tip de date complex? Spre exemplu, dorim sǎ sortǎm o listǎ ArrayList<Student>
dupǎ media anilor. Sǎ presupunem cǎ Student
este o clasǎ ce conţine printre membrii sǎi o variabilǎ ce reţine media anilor.
Acest lucru poate fi realizat folosind interfeţele:
* Comparable
* Comparator
Un Set (mulţime) este o colecţie ce nu poate conţine elemente duplicate. Interfaţa Set
conţine doar metodele moştenite din Collection
, la care adaugǎ restricţii astfel încât elementele duplicate sǎ nu poatǎ fi adǎugate.
Avem trei implementǎri utile pentru Set:
* HashSet: memoreazǎ elementele sale într-o tabelǎ de dispersie (hash table)
; este implementarea cea mai performantǎ, însǎ nu avem garanţii asupra ordinii de parcurgere. Doi iteratori diferiţi pot parcurge elementele mulţimii în ordine diferitǎ.
* TreeSet: memoreazǎ elementele sale sub formǎ de arbore roşu-negru; elementele sunt ordonate pe baza valorilor sale. Implementarea este mai lentǎ decat HashSet
.
* LinkedHashSet: este implementat ca o tabelǎ de dispersie. Diferenţa faţǎ de HashSet
este cǎ LinkedHashSet
menţine o listǎ dublu-înlǎnţuitǎ peste toate elementele sale. Prin urmare (şi spre deosebire de HashSet
), elementele rǎmân în ordinea în care au fost inserate. O parcurgere a LinkedHashSet
va gǎsi elementele mereu în aceastǎ ordine.
Atenţie : Implementarea HashSet, care se bazeazǎ pe o tabelǎ de dispersie, calculeazǎ codul de dispersie al elementelor pe baza metodei hashCode, definitǎ în clasa Object
. De aceea, douǎ obiecte egale, conform funcţiei equals
, trebuie sǎ întoarcǎ acelaşi rezultat din hashCode
.
Explicaţii suplimentare gǎsiti pe Java Tutorials - Set.
Un Map este un obiect care mapeazǎ chei pe valori. Într-o astfel de structurǎ nu pot exista chei duplicate. Fiecare cheie este mapatǎ la exact o valoare. Map
reprezintǎ o modelare a conceptului de funcţie: primeşte
o entitate ca parametru (cheia), şi întoarce o altǎ entitate (valoarea).
Cele trei implementǎri pentru Map
sunt:
* HashMap
* TreeMap
* LinkedHashMap
Particularitǎţile de implementare corespund celor de la Set
.
Exemplu de folosire:
class Student { String name; // numele studentului float avg; // media public Student(String name, float avg) { this.name = name; this.avg = avg; } public String toString() { return "[" + name + ", " + avg + "]"; } } public class Test { public static void main(String[] args) { Map<String,Student> students = new HashMap<String,Student>(); students.put("Matei", new Student("Matei", 4.90F)); students.put("Andrei", new Student("Andrei", 6.80F)); students.put("Mihai", new Student("Mihai", 9.90F)); System.out.println(students.get("Mihai")); // elementul avand cheia "Andrei" //adaugam un element cu o cheie existenta System.out.println(students.put("Andrei", new Student("", 0.0F))); //put intoarce vechiul element, //si apoi il suprascrie System.out.println(students.get("Andrei")); //remove intoarce elementul sters System.out.println(students.remove("Matei")); //afisare a structurii System.out.println(students); } }
Interfaţa Map.Entry desemneazǎ o pereche (cheie, valoare) din map. Metodele caracteristice sunt: * getKey: întoarce cheia * getValue: întoarce valoarea * setValue: permite stabilirea valorii asociatǎ cu aceastǎ cheie O iterare obişnuitǎ pe un map se va face în felul urmǎtor:
for (Map.Entry<String, Student> entry : students.entrySet()) System.out.println("Media studentului " + entry.getKey() + " este " + entry.getValue().getAverage());
În bucla for-each
de mai sus se ascunde, de fapt, iteratorul mulţimii de perechi, întoarse de entrySet
.
Explicaţii suplimentare gǎsiţi pe Java Tutorials - Map.
Queue defineşte operaţii specifice pentru cozi:
* inserţia unui element
* ştergerea unui element
* operaţii de inspecţie
a cozii
Implementǎri utilizate frecvente pentru Queue
:
* LinkedList
: pe lângǎ List
, LinkedList
implementeazǎ şi Queue
* PriorityQueue
;
Explicaţii suplimentare gǎsiţi pe Java Tutorials - Queue
String
-uri şi va fi parametrizatǎ.Student
.nume
(de tip String
) şi medie (de tip float
)toString
.Student
. Adǎugaţi elemente duplicate, instanţiindu-le, de fiecare datǎ, cu new
. Ce observaţi?equals
a clasei Student
şi încercaţi din nou. De ce situaţia rǎmâne neschimbatǎ?hashCode
şi încercaţi din nou.Student
, metoda equals
, cu o variantǎ care primeşte un parametru Student
, şi care întoarce, întotdeauna, false
element.equals(element)
şi ((Object)element).equals(element)
. Cum explicaţi comportamentul observat? Iteratorul va fi, şi el, parametrizat.Map
pentru reţinerea studenţilor dupǎ medie.List
) care va reţine toţi studenţii cu media rotunjitǎ egalǎ cu cheia. Considerǎm cǎ un student are media rotunjitǎ 8 dacǎ media sa este în intervalul [7.50, 8.49].Map
-ul vostru va menţine cheile (mediile) ordonate descrescǎtor. Extindeţi o implementare potrivitǎ a interfeţei Map
, care sǎ permitǎ acest lucru, şi folosiţi un Comparator pentru stabilirea ordinii cheilor (ca în exercițiul 1 din laboratorul 6)add(Student)
, ce va adǎuga un student în lista corespunzǎtoare mediei lui. Dacǎ, în prealabil, nu mai existǎ niciun student cu media respectivǎ (rotunjitǎ), atunci lista va fi creatǎ la cerere.Collections.sort
, dar spre deosebire de laboratorul trecut, nu va primi un Comparator, ci colecția va implementa o interfață prin care se specifică cum se compară obiectele din ea.Student
va implementa interfaţa ComparableStudent
va conține implementarea metodei compareTo.HashSet<Integer>
.add
şi addAll
. Pentru adǎugarea efectivǎ a elementelor, folosiţi implementǎrile din clasa pǎrinte (HashSet
).add
cât şi addAll
. Ce observaţi? Corectaţi dacǎ este cazul.LinkedList<Integer>
. Ce observaţi? Ce concluzii trageţi?* PDF laborator * Soluții