Sunteți pe pagina 1din 10

Lucrarea 5.

Colectii Java
1. Scopul lucrrii
Aceasta lucrare prezinta colectiile Java si sistemul de Intrare-Iesire. Se va prezenta modul de lucru cu sirurile clasice precum si containerele JAVA, delimitare in functie de tipul de obiecte stocate de acestea. Referitor la sistemul de intrare-iesire, se va insista pe utilizarea Readers / Writers. Se va prezenta conceptul de serializare

2. Mod de desfurare a lucrrii


Studenii sunt rugai s revad noiunile similare asimilate la disciplinele Medii vizuale si programare n .NET. Studenii vor parcurge cu atenie exemplele din laborator precum i bibliografia aferent acestui laborator.

3. Colectii in Java 3.1 Siruri (arrays)


Sirurile sunt cel mai eficient mod de a memora obiecte, care sa fie regasite cu access random. Sirurile sunt organizate ca si secvente liniare de obiecte. La crearea unui sir marimea sa (adica numarul de obiecte din sir) este fixata. Marimea unui sir nu poate fi modificata pe parcursul utilizarii sirului. Pe langa sirurile clasice (operatorul []), Java furnizeaza siruri dinamice, care vor fi de asemenea studiate in acest laborator. La crearea unui sir, identificatorul acestuia este o referinta catre un obiect care este creat in heap. Acest obiect memoreaza referinte catre alte obiecte (obiectele din sir), care pot fi create la initializare sau pe parcursul utilizarii sirului. Tot parte a obiectului sir este si campul lenght (lungimea sirului) care ne indica numarul de elemente care pot fi stocate intr-un sir. Exemplul urmator ne arata modul de intializare a sirurilor de obiecte.
//: c11:ArraySize.java // Initializarea sirurilor. class Weeble {} public class ArraySize { private static Test monitor = new Test(); public static void main(String[] args) { // siruri de obiecte: Weeble[] a; // a este variabila sir neinitializata Weeble[] b = new Weeble[5]; // var sir cu 5 referinte neinitializ Weeble[] c = new Weeble[4]; for(int i = 0; i < c.length; i++) if(c[i] == null) // Can test for null reference c[i] = new Weeble(); // initializare agregata la crearea sirului: Weeble[] d = {

new Weeble(), new Weeble(), new Weeble() }; // initializare agregata dinamica: a = new Weeble[] { new Weeble(), new Weeble() }; System.out.println("a.length=" + a.length); System.out.println("b.length = " + b.length); // The references inside the array are // automatically initialized to null: for(int i = 0; i < b.length; i++) System.out.println("b[" + i + "]=" + b[i]); System.out.println("c.length = " + c.length); System.out.println("d.length = " + d.length); a = d; System.out.println("a.length = " + a.length); // siruri de date primitive: int[] e; // referinte null int[] f = new int[5]; int[] g = new int[4]; for(int i = 0; i < g.length; i++) g[i] = i*i; int[] h = { 11, 47, 93 }; // Compile error: variable e not initialized: //!System.out.println("e.length=" + e.length); System.out.println("f.length = " + f.length); // The primitives inside the array are // automatically initialized to zero: for(int i = 0; i < f.length; i++) System.out.println("f[" + i + "]=" + f[i]); System.out.println("g.length = " + g.length); System.out.println("h.length = " + h.length); e = h; System.out.println("e.length = " + e.length); e = new int[] { 1, 2 }; System.out.println("e.length = " + e.length); } } ///:~

Sirurile pot fi utilizate ca si valori de return din metode:


public static String[] flavorSet(int n) { }

Clasa Array Java contine o clasa speciala numita Array care furnizeaza o serie de metode statice cu functii utilitare pentru arrayuri. Metode importante furnizate de clasa Array: - equals(): compara doua siruri - fill(): umple un sir cu o valoare - sort(): sorteaza un sir - binarySearch(): realizeaza cautare binara a unui element intr-un sir Metoda asList() returneaza containerul de tip List asociat unui sir obisnuit.

Metoda statica System.arraycopy() permite o copiere mult mai eficienta a elementelor unui sir sursa intr-un sir destinatie, fara a mai fi nevoie sa se utilizeze o bucla for. La realizarea sortarii, o problema care apare este modul de comparare a 2 elemente din sir, pentru a determina ordinea lor. Java permite sortarea sirurilor care contin obiecte instante din clase care implementeaza interfata Comparable. Interfata Comparable contine o singura metoda (compareTo) care produce o valoare negativa daca primul obiect este mai mic sau egal cu al doilea.
//: c11:CompType.java // Implementing Comparable in a class. import java.util.*; import utilities.array.*; public class CompType implements Comparable { int i; int j; public CompType(int n1, int n2) { i = n1; j = n2; } public String toString() { return "[i = " + i + ", j = " + j + "]"; } public int compareTo(Object rv) { int rvi = ((CompType)rv).i; return (i < rvi ? -1 : (i == rvi ? 0 : 1)); } private static Random r = new Random(); public static Generator generator() { return new Generator() { public Object next() { return new CompType(r.nextInt(100),r.nextInt(100)); } }; } public static void main(String[] args) { CompType[] a = new CompType[10]; Arrays2.fill(a, generator()); System.out.println("before sorting, a = " + Arrays.asList(a)); Arrays.sort(a); System.out.println("after sorting, a = " + Arrays.asList(a)); } } ///:~

Atentie: se va vedea package-ul utilities.array din resurse care implementeaza tipul Array2 pentru a facilita realizarea metodei fill !!! In aceasta metoda, sort realizeaza sortarea a 2 obiecte care implementeaza interfata comparable. Insa, de multe ori lucram cu obiecte care nu implementeaza aceasta metoda. Astfel ca pentru realizarea sortarii, sort va avea nevoie de functia de comparare. Aceasta poate fi furnizata printr-un obiect care implementeaza interfata Comparator. Interfata Comparator are 2 metode: compare (similara cu compareTo) si equals. Metoda compare este cea folosita pentru realizarea compararii in acest caz:
import utilities.array.*;

import java.util.*; public class Reverse { public static void main(String[] args) { CompType[] a = new CompType[10]; Arrays2.fill(a, CompType.generator()); System.out.println("before sorting, a = " + Arrays.asList(a)); Arrays.sort(a, Collections.reverseOrder()); System.out.println("after sorting, a = " + Arrays.asList(a)); } } ///:~

In exemplul de mai sus, metoda reverseOrder() din Collections furnizeaza un comparator care inverseaza ordinea naturala de comparare (pentru numere). In cazul in care sortam obiecte de tipul String, Java furnizeaza comparare lexicografica (pe sirul caracterlor ASCII). Pentru regasire rapida a elementelor, se poate utiliza metoda binarySearch(), dar numai pe un sir sortat !

3.2 Containerele
Containerele reprezinta una din cele mai puternice instrumente de programare. Java 2 furnizeaza un design complet al acestora, orientat catre eficienta. In Java, containerele se grupeaza in jurul a 2 concepte: Collection: reprezinta un grup de obiecte impreuna cu reguli aplicate acestora. Avem astfel colectii de tipul List (lista de obiecte) si Set (se elimina duplicatele) Map: grup de obiecte de tip cheie-valoare. Obiectele de tip Map sunt foarte utilizate pentru implementarea dictionarelor.

//: c11:PrintingContainers.java import java.util.*; public class PrintingContainers { static Collection fill(Collection c) { c.add("dog"); // se adauga un String la colectia c c.add("dog"); c.add("cat"); return c; } static Map fill(Map m) { m.put("dog", "Bosco"); // se adauga o pereche in map m.put("dog", "Spot"); m.put("cat", "Rags"); return m; } public static void main(String[] args) { System.out.println(fill(new ArrayList())); System.out.println(fill(new HashSet())); System.out.println(fill(new HashMap())); } } ///:~

Exemplul de mai sus ne arata modul in care se adauga elemente la colectii si la Mapuri. (metodele add si put). De asemenea, observam faptul ca containerele Java au implementare metoda toString si astfel, afisarea acestora este facila. Dezavantajul major al containerelor este faptul ca ele sunt implementare pe tipul general (Object), astfel ca: - la inserarea elementelor in container, se pierde (implicit) tipul obiectelor stocate. Containerele salveaza doar referinte catre obiectele stocate - tipul obiectelor este regasit prin RTTI. Daca dorim sa utilizam in mod gresit un obiect stocat intr-un container (prin conversie gresita), Java ne atentioneaza prin generarea unei exceptii de tip RuntimeExpection. La utilizarea obiectelor, trebuie realizata operatia explicata de downcast. (se va vedea exemplul Cats & Dogs din resurse). Aplicarea regulii de mai sus (cu conversia explicita) este relaxata pentru tipul String. Astfel, ori de cate ori JVM intalneste un obiect de alt tip in locul unui obiect de tipul String, va incerca sa genereze obiectul de tip String prin apelul metodei toString a obiectului gasit. Cum putem face containerele sa fie particularizate pe tipurile noastre? Se creaza o noua clasa care agrega un obiect de tip Container (collection sau map). Clasa noua va rescrie toate metodele specifice de lucru cu containerele (de exemplu add si put), astfel incat sa lucreze cu tipul dorit:
public class MouseList { private List list = new ArrayList(); public void add(Mouse m) { list.add(m); } public Mouse get(int index) { return (Mouse)list.get(index); } public int size() { return list.size(); }

} ///:~
public class MouseListTest { public static void main(String[] args) { MouseList mice = new MouseList(); for(int i = 0; i < 3; i++) mice.add(new Mouse(i)); for(int i = 0; i < mice.size(); i++) MouseTrap.caughtYa(mice.get(i)); }

} ///:~ Iteratori Atunci cand lucram cu o colectie (de exemplu un ArrayList), trebuie sa avem o metoda prin care sa salvam elemente in colectie (add) si una prin care sa regasim elemente (get). Un dezavantaj apare si datorita faptului ca colectiile sunt generice (nu cunoastem tipul elementelor salvate in colectie). Sa presupunem ca utilizam la inceput tipul ArrayList pentru implementarea unei colectii, iar apoi, pe parcurs, ne razgandim si dorim sa utilizam tipul Set. Apare necesitatea scrierii de cod specific celor 2 implementari pentru regasirea si utilizarea elementelor din colectii. Pentru rezolvarea acestei probleme (sa nu mai fim nevoiti sa scriem cod specific ori de care ori schimbam implementarea utilizata pentru colectie) apare conceptul de Iterator.

Iteratorul este un obiect care ne permite sa iteram elementele unei colectii si sa regasim pe rand aceste elemente, fara ca programatorul client sa stie modalitatea specifica de implementare a colectiei. Din aceasta cauza, un iterator este denumit un obiect de tip light usor de creat, orice colectie ne furnizeaza un asemenea obiect. Asfel, codul client poate ramane neschimbat la schimbarea implementarii. Modul de utilizare a iteratorilor: - cerem containerului sa ne furnizeze un obiect de tip Iterator: prin metoda iterator() - obtinem urmatorul element din secventa prin next() - vedem daca mai sunt elemente neiterate in secventa prin hasNext() - putem sterge ultimul element returnat de iterator prin remove()
public class CatsAndDogs2 { public static void main(String[] args) { List cats = new ArrayList(); for(int i = 0; i < 7; i++) cats.add(new Cat(i)); Iterator e = cats.iterator(); while(e.hasNext()) ((Cat)e.next()).id(); }

} ///:~ Figura de mai jos prezinta ierarhia claselor de tip Container in Java. In aceasta figura cu linie punctata sunt pictate interfetele, cu linie obisnuita clasele abstracte iar cu linie ingrosata clasele concrete (cele care contin implementari concrete de tipuri de date).

Ierarhia claselor de tip Container in Java In cele ce urmeaza prezentam principalele caracteristici ale acestor clase: List: Cea mai importanta proprietare a acestui tip este ordinea elemenetelor.

ArrayList: Lista implementata printr-un sir. Foarte eficienta la acces random, in schimb foarte inceata la inserari si stergeri de elemente din interiorul sirului. Iteratorii trebuie utilizati pentru parcurgere si nu pentru inserari/stergeri LinkedList Furnizeaza access secvential optim, inserari si stergeri facile. Este inceata la accesul random. Set: Orice element din Set trebuie sa fie unic. HashSet Un set cu deosebirea ca este folosit la regasirea rapida a elementelor TreeSet Setul este implementat cu un arbore. Astfel, putem sa regasim elementele din Set intro anumita ordine. LinkedHashSet Este un hash set cu elementele stocate intr-o lista inlantuita. La iterarea elementelor din LinkedHashSet acestea vor aparea in ordinea inserarii. SortedSet Se garanteaza ordinea sortata a elementelor. Map Pastreaza asociatii de tip cheie-valoare. Putem face cautarea rapida a valorilor daca se cunosc cheile. HashMap Implementarea map-ului este realizata pe iun Hashtable. Se furnizeaza performanta similara la inserare si regasire. LinkedHashMap: La iterare, elementele vor fi regasite fie in ordinea de inserare fie dupa LRU (leastreecently-used). De obicei mai inceata decat un HashMap, fiind mai rapida doar la iterare. TreeMap Implementarea map-ului cu arbori rosu si negru. Cheile sunt pastrate in ordine sortata. IdentityHashMap Foloseste == pentru comparare, in loc de equals Aceste implementari specifice se folosesc in functie de cerintele specifice de performanta din programele noastre. Exercitiu:

Se vor rula programele din resurse care prezinta testul de eficienta pentru diversele tipuri de colectii si care prezinta modul de utilizare a acestora. Hashing si coduri hash Conceptul de hashing este utilizat la lucrul cu colectiile de tip HashSet si HashMap. Aceste colectii utilizeaza un cod de hashing pentru identificarea unica a obiectelor. Fie exemplul de mai jos:
//: c11:Statistics.java // demonstratie pentru HashMap. import java.util.*; class Counter { int i = 1; public String toString() { return Integer.toString(i); } } public class Statistics { private static Random rand = new Random(); public static void main(String[] args) { Map hm = new HashMap(); for(int i = 0; i < 10000; i++) { // Produce a number between 0 and 20: Integer r = new Integer(rand.nextInt(20)); if(hm.containsKey(r)) ((Counter)hm.get(r)).i++; else hm.put(r, new Counter()); } System.out.println(hm); } } ///:~

In acest exemplu, se observa ca colectia HashMap memoreaza perechi de tipul (Integer, Counter), unde cheia este de tipul Integer si ia valori de la 0 la 20. In acest caz, HashMap functioneaza corect pt ca utilizam o cheie dintr-un tip predefinit. Mai precis, Java stie sa genereze corect codul hash pentru obiectele de tip cheie, care apoi sa fie utilizate la stocarea colectiei. Fie exemplul de mai jos, in care incercam sa utilizam propriile clase ca si chei in colectii de tip Hash:
//: c11:Groundhog.java // Clasa plauzibila pentru a fi cheie, dar nu va functiona cu HashMap public class Groundhog { protected int number; public Groundhog(int n) { number = n; } public String toString() { return "Groundhog #" + number; }

} ///:~
//: c11:Prediction.java public class Prediction { private boolean shadow = Math.random() > 0.5; public String toString() { if(shadow) return "Six more weeks of Winter!";

else return "Early Spring!"; } } ///:~ //: c11:SpringDetector.java import utilities.simpletest.*; import java.util.*; import java.lang.reflect.*; public class SpringDetector { private static Test monitor = new Test(); // Uses a Groundhog or class derived from Groundhog: public static void detectSpring(Class groundHogClass) throws Exception { Constructor ghog = groundHogClass.getConstructor( new Class[] {int.class}); Map map = new HashMap(); for(int i = 0; i < 10; i++) map.put(ghog.newInstance( new Object[]{ new Integer(i) }), new Prediction()); System.out.println("map = " + map + "\n"); Groundhog gh = (Groundhog) ghog.newInstance(new Object[]{ new Integer(3) }); System.out.println("Looking up prediction for " + gh); if(map.containsKey(gh)) System.out.println((Prediction)map.get(gh)); else System.out.println("Key not found: " + gh); } public static void main(String[] args) throws Exception { detectSpring(Groundhog.class); monitor.expect(new String[] { "%% map = \\{(Groundhog #\\d=" + "(Early Spring!|Six more weeks of Winter!)" + "(, )?){10}\\}", "", "Looking up prediction for Groundhog #3", "Key not found: Groundhog #3" }); } } ///:~

Se observa ca eroarea apare la regasire (containsKey), unde nu se identifica corect obiectul cheie prezent in container. Groundhog, la fel ca si orice alta clasa mosteneste din clasa Object. Clasa Object are o metoda numita hashCode() care este utilizata pentru a genera codul hash aferent unei instante. Aceasta metoda, (implicit) utilizeaza pentru generarea hashCode-ului adresa fizica din memorie unde este salvat obiectul. Astfel ca pentru 2 instante (chiar egale) din aceasi clasa, hash codul care este dependent de adresa de memorare a instantelor va fi diferit. Astfel ca in clasele pe care noi le scriem si dorim sa le utilizam ca si chei in colectii de tip Hash trebuie sa implementam 2 lucruri: - metoda hashCode care sa genereze un hash corect pt instanta. Se recomanda pastrarea hashCode-ului generat de Object, pt ca acesta are proprietatea de a fi generat unic pe instanta - metoda equals() (operatorul de egalitate) care testeaza egalitatea dintre 2 referinte.

In cazul nostru, codul de mai jos implementeaza aceste 2 lucruri: O buna implementare a metodei equals satisface urmatoarele cerinte: - reflexivitate: x.equals(x) trebuie sa returneze true - simetrie: x.equals(y) trebuie sa returneze true daca si numai daca y.equals(x) returneaza true - tranzitivitate: daca x.equals(y) == true si y.equals(z) == true atunci x.equals(z) == true - consistenta: mai multe apelari ale lui x.equals(y) trebuie toate sa reintoarca acelasi rezultat daca x si y nu se modifica intre apeluri - x.equals(null) trebuie sa returneze fals Hashing este important din punct de vedere a optimizarii la regasire. Astfel, pentru fiecare cheie se genereaza hash-ul unic si colectia este implementata ca si un hashTable pe baza acestui hash. Acest hash ne ajuta la cautarea binara (care astfel merge foarte rapid) si de asemenea la regasirea specifica a informatiei (hash-ul ne asigura o performanta aproape identica cu indexarea). Tema: Problemele 19, 22, 33, 45, 49 din Thinking in Java, Bruce Eckel, capitolul 11

S-ar putea să vă placă și