Sunteți pe pagina 1din 157

Centrul de Învăţământ la Distanţă şi Învăţământ cu Frecvenţă Redusă

FACULTATEA DE MATEMATICĂ ȘI INFORMATICĂ


PROGRAM DE LICENȚĂ ID:
Informatică

Medii si Instrumente de Programare


CURS PENTRU ÎNVĂȚĂMÂNT LA DISTANȚĂ

AUTOR: Silviu DUMITRESCU

ANUL II, SEM. I


2017
Programare generica
Programarea generica a fost introdusa in J2SE 5.0 si modifica semnificativ sintaxa limbajului
facand codul mult mai sigur din punct de vedere al tipului de date (type safe).
Programarea generica foloseste parametri cu tip atunci cand defineste clase sau metode.
Codul generic este inrudit cu tipurile parametrizate sau template-urile. Programarea generica
inlatura necesitatea conversiei de tip, cu avantaje substantiale pentru programatori si pentru
procesul de debugging. Astfel, multe dintre erorile de executie sunt detectate inca din faza de
compilare.
Codul non-generic este denumit cod legacy.
// Legacy Code
List list = new ArrayList();

//Generic Code
List<Integer> list = new ArrayList<Integer>();

In exemplul anterior ambele linii creaza o lista de obiecte. Prima creza un sir ce accepta orice
tip de obiect. Aceasta inseamna ca anumite metode ale clasei ArrayList necesita conversie
manuala ceea ce poate conduce la aparitia erorilor la executie.
In cea de-a doua linie am folosit cod generic. Tipul elementelor sirului a fost scris intre
semnele <> si determina ca toate elementele sa fie de tip Integer.
Avantajele folosirii tipului generic sunt:
- Colectiile generice stocheaza numai tipuri cunoscute
- Erorile de tip sunt prinse inca in faza de compilare
- In majoritatea cazurilor elimina nevoia de conversie
- Codul este mai usor de inteles
- Timpul de executie este scurtat
- Este usor de trecut de la legacy la generic si invers pentru ca exista backward
compatibilty intre generic si legacy
Dezvantaje:
- Codul generic e greu de dezvoltat
- Codul generic e greu de interpretat
Urmatorul exemplu ilustreaza producerea unei erori la executie din cauza folosirii defectoase
a tipului row (legacy code). Elementele listei se considera de tip Object. Eroare nu a fost
identificata la compilare. In versiuni mai mari de 5, avem atentionari in ceea ce priveste
folosirea tipului row.
import java.util.*;
public class GenericError {
public static void main(String[] args) {
// This list is not type safe, as it can store any type of object
List list = new ArrayList();

list.add(5);
list.add("42");

for (Iterator i = list.iterator(); i.hasNext();) {


// urmatoarea linie va genera o eroare la rulare relativ la
conversia string-ului "4" in Integer
int value = ((Integer) i.next()) * 10;
System.out.println(value);
}
}
}
Solutia este:
import java.util.*;
public class GenericFix {
public static void main(String[] args) {
// lista va fi declarata de tip Integer
List<Integer> list = new ArrayList<Integer>();

list.add(5);
list.add("4");

for (Iterator<Integer> i = list.iterator(); i.hasNext();) {


int value = (i.next()) * 10;
System.out.println(value);
}
}
}
in care primim eroarea de incompatibilitatea a tipurilor inca din faza de compilare.
Compilatorul foloseste un proces numit erasure sau type erasure pentru a asigura faptul ca
codul generic este compatibil cu codul legacy, de aceea cele doua tipuri de cod pot fi folosite
simultan.
class ABC<T>{
// declara un obiect de tip T
T obj;

// constructorul
ABC (T aObj) {
obj = aObj
}

// o metoda ce returneaza obj


T getObj(){
return obj;
}
}
In aceasta clasa generica procesul de compilare este urmatorul: tipul parametrizat generic, T,
este indepartat si inlocuit de tipul corespunzator prin eventuale conversii. Daca nu se
specifica niciun tip atunci se foloseste tipul Object pentru a asigura compatibilitatea cu alte
clase.
Definirea unui tip generic se face dupa urmatoarea sintaxa:
class nume<lista_parametri_generici>{
}
Sintaxa poate fi aplicata si interfetelor sau oricaror tipuri container din ierarhia Collection
precum: List, Map, etc. Poate fi aplicata, de asemenea, constructorilor sau metodelor unei
clase. Parametrii sunt intotdeauna tipuri clasa. Iata cateva exemple:
public interface<T>{
void add(T e);
}
List<Account> list = new ArrayList<Account>();
Map<Integer, String> level = new HashMap<Integer, String>();
Putem defini metode generice in interiorul claselor generice si putem defini clase generice ce
extind clase generice sau nongenerice.
Urmatorul cod va genera eroare la atribuire din cauza incompatibilitatii tipurilor.
class Gen<Type> {
// diverse metode;
}

class GenTest {
public static void main(String[] args) {
// initializarea instantelor claselor generice Gen
Gen<Integer> a = new Gen<Integer>();
Gen<String> b = new Gen<String>();

a = b; //eroare!
}
}
Putem folosi colectii generice. Acestea pot fi declarate cat si intializate ca generice. Daca o
colectie este initializata ca generica, dar nu are o declaratie generica atunci ea va accepta
obiecte arbitrare. Aceasta este ilustrat in exemplul urmator:
List list =new ArrayList<Integer>();
Apeluri ca:
list.add(5);
list.add("4");
sunt posibile, dar vor genera atentionari la compilare. La executie, insa, urmatoarea sintaxa va
genera eroare:
for (Iterator<Integer> i = list.iterator(); i.hasNext();) {
int value = (i.next()) * 10;
System.out.println(value);
}
O restricie fundamentala in cazul programarii generice este ca un tip generic T nu este
compatibil cu Object. Astfel, nu putem face atribuirea List<T> la un List<Object>.
Motivatia vine din faptul ca un obiect de orice tip ar putea fi inserat in lista.
public class Generic {
public static void main(String[] args) {
List<Account> list = new ArrayList<Account>();

//desi Account extinde Object, o lista Account nu poate


// fi atribuita unei liste Object.
List<Object> objects = list;
}
}

class Account {
}
Tipul List<Object> este practic identic cu codul legacy List.
In anumite situatii trebuie sa facem conversii explicite cand utilizam tipuri generice. Cazul cel
mai frecvent este atunci cand folosim obiecte instante ale unor clase derivate din clasa
declarata ca parametru. Un exemplu este in cele ce urmeaza:
public class Generic {
public static void main(String[] args) {
List<Account> list = new ArrayList<Account>();
// Adaugarea unui element dintr-o clasa derivata din Account
list.add(new Mortgage());
// cand extragem elementul trebuie sa facem un cast
Mortgage mortgage = (Mortgage) list.get(0);
}
}
class Account {
}
class Mortgage extends Account {
}
Putem adauga si liste intregi intre elementele unei liste existente, caz ilustrat mai jos:
import java.util.*;
public class Generic{
public static void main(String[] args) {
List<Mortgage> mortgageList = new ArrayList<Mortgage>();
mortgageList.add(new Mortgage());
List<Deposit> depositList = new ArrayList<Deposit>();
depositList.add(new Deposit());
List<Account> accountList = new ArrayList<Account>();
accountList.addAll(mortgageList);
accountList.addAll(depositList);
}
}
class Account {
}
class Mortgage extends Account {
}
class Deposit extends Account {
}
Daca adaugarea a fost posibila, in cazul extragerii vom avea nevoie de cast. Atribuirea directa
nu este posibila, asa precum am aratat anterior. Urmatorul cod genereaza eroare la compilare:
accountList=mortgageList;
Solutia pentru atribuirea subtipului unui supertip este folosirea sintaxei: <? extends
Account>. ? poarta denumirea de wildcard. Codul din exemplul anterior se modifica, pentru
atribuire, dupa cum urmeaza:
import java.util.*;
public class Generic {
public static void main(String[] args) {
List<Mortgage> mortgageList = new ArrayList<Mortgage>();

mortgageList.add(new Mortgage());

List<? extends Account> list = mortgageList;


list = mortgageList;
}
}
class Account {
}
class Mortgage extends Account {
}
Codul generic - tipul bounded
Tipurile bounded nu exista in cazul codului legacy. Toate obiectele in cod legacy sunt
implicit marginite superior de Object. Astfel, orice metoda poate accepta orice tip de obiect,
ceea ce determina folosirea conversiei pentru a incerca prevenirea exceptiilor. In schimb,
tipurile generice elimina conversiile, dar aceasta introduce o limitare si anume tipurile
generice nu pot fi specificate.
Presupunem ca dorim sa cream o clasa ce returneaza suma, ca un double, a unui sir de
numere de oricare dintre tipurile: int, double sau float. Aceasta ar insemna crearea a trei
clase cu cod aproape identic.
class Sum<Integer> { // calcularea sumei
}

class Sum<Float> { // calcularea sumei


}

class Sum<Double> { // calcularea sumei


}
In Java 5.0 se introduce tipul bounded pentru a elimina acest neajuns. Sintactic, in
programarea generica, tipul bounded este folosit in sintaxa <T extends superclasa>, unde
T poate fi orice subtip al superclasei sau chiar superclasa (superclasa este de tip bounded). In
cazul presupunerii nostre Integer, Float si Double sunt toate subclase ale clasei Number,
care este clasa de tip bounded.
public class Sum<T extends Number> {
T[] nums; // nums este un sir de tip Number sau subclasa a sa

// constructor
Sum (T[] o){
nums = o;
}

// tipul returnat este double in oricare dintre cazuri


double sumTotal() {
double sum = 0.0;

for (int i=0; i < nums.length; i++)


sum += nums[i].doubleValue();

return sum;
}

public static void main(String[]args){


Integer a[]={1,2,3};
Double d[]={8.0,5.0,6.0};
Sum<Integer> intregi=new Sum<Integer>(a);
System.out.println(intregi.sumTotal());
Sum<Double> reali=new Sum<Double>(d);
System.out.println(reali.sumTotal());
}
}
Clasa Number se numeste bounded si compilatorul stie ca obiectele de tip T pot apela orice
metoda declarata de Number. In plus, compilatorul va preveni crearea oricaror obiecte Sum
fara a fi numerice (instanta a lui Number sau derivate din aceasta).
Presupunem, in continuare, ca dorim sa comparam sumele calculate in doua clase Sum. Pentru
aceasta am definit in clasa Sum metoda String compare( Sum<T>). Aceasta, insa, ridica o
problema deoarece Sum<Integer> nu poate fi comparata cu Sum<Number>, chiar daca
Integer este subtip al lui Number. Pentru rezolvarea acestei probleme Java face apel la
elementul ? (wildcard), ce specifica un tip necunoscut. Cu acesta semnatura metodei devine:
String compare( Sum<?>) si acum accepta orice tip de obiect Sum. Codul, cu adaugarea
acestei metode devine:
public class Sum<T extends Number> {
T[] nums; // nums este un sir de tip Number sau subclasa a sa

// constructor
Sum (T[] o){
nums = o;
}

// tipul returnat este double in oricare dintre cazuri


double sumTotal() {
double sum = 0.0;

for (int i=0; i < nums.length; i++)


sum += nums[i].doubleValue();

return sum;
}

String comparare(Sum<?> ob){


if (this.sumTotal()<ob.sumTotal())
return "mai mic";
else
return "mai mare sau egal";
}

public static void main(String[]args){


Integer a[]={1,2,3};
Double d[]={8.0,5.0,6.0};
Sum<Integer> intregi=new Sum<Integer>(a);
System.out.println(intregi.sumTotal());
Sum<Double> reali=new Sum<Double>(d);
System.out.println(reali.sumTotal());
System.out.println(intregi.comparare(reali));
}
}
Si cu wildcardurile putem folosi tipurile marginite (bounded) intr-o sintaxa de tipul <?
extends superclasa>.
Conventie: putem folosi ce identificatoare dorim, dar urmatoarele sunt deseori foarte
sugestive:
- T: Type
- E: Element
- K: Key
- V: Value
- S, U: utilizati ca al doilea tip, al treilea, etc.
In Java SE 7 nu este nevoie sa repetam tipurile in partea dreapta a unei instructiuni.
Parantezele unghiulare simple indica ca parametrii de tip sunt aceeasi.
Ex: List <Integer> l = new ArrayList<>();
Ca bune practici adaugarea unui element intr-o colectie ar trebui facuta printr-o metoda cu
semnatura:
<T> void addItem(T a, Collection<T> c);
Copierea a doua liste ar trebui facuta folosind metode cu semnatura:
<T> void copy(List<T> dest, List<? extends T> src);
Sau intr-un mod fara wild card:
<T, S extends T> void copy(List<T> dest, List<S> src);
Sintaxa ? super T denota un tip necunoscut ce este supertip al lui T si se numeste lower
bound.

Lint warnings
Lint reprezinta o functionalitate a Javei ce ne permite sa identificam cod nesigur. Lint
warnings se genereaza cand incercam sa compilam cod nesigur. Recompilarea unui cod cu
–Xlint:unchecked, va genera avertismentele de cod nesigur indicand locul in care pot
aparea erori la executie.

Declararea unei clase generice


Cand declaram o clasa generica trebuie sa furnizam parametrul tip. Clasa generica se va
executa indiferent de tipurile parametru transmise.
Cand cream o instanta a unui tip generic, argumentul tip transmis parametrului tip trebuie sa
fie tip clasa. className<lista-arg-tip> varName= new className<lista-arg-
tip>(lista-arg-constr); Iata un exemplu:
class GenericClass<T> {
private T genericValue;

public GenericClass(T genericValue) {


this.genericValue = genericValue;
}

public T get() {
return genericValue;
}

public void set(T genericValue) {


this.genericValue = genericValue;
}
}
Unde T este parametrul tip ce va fi inlocuit in momentul in care clasa generica va fi apelata,
ca in exemplul urmator.
import java.util.*;

public class GenericExample {


public static void main(String[] args) {
GenericClass<String> gen1 = new GenericClass<String>("Hello");
GenericClass<Integer> gen2 = new GenericClass<Integer>(8);
GenericClass<Double> gen3 = new GenericClass<Double>(2.34);

System.out.println("gen1.genericValue este " + gen1.get());


System.out.println("gen2.genericValue este " + gen2.get());
System.out.println("gen3.genericValue este " + gen3.get());

gen1.set("World");
System.out.println("gen1.genericValue is " + gen1.get());
}}
Codul declara trei obiecte de tip GenericClass, dar de versiuni diferite.

Crearea metodelor generice si a constructorilor


Constructorul poate fi generic chiar daca clasa nu este generica.
Cand declaram o metoda in interiorul clasei generice aceasta este capabila sa foloseasca
parametrul tip al clasei. Putem apela o metoda generica in metoda main() fara a specifica
argumentul tip.
Urmatoarea secventa, bazata pe exemplul anterior, va genera o eroare la compilare:
gen1.set(gen2.get());
deoarece cele doua obiecte nu au aceeasi valoare a parametrului tip.

Subclase generice
Clasele generice pot forma ierarhii de clase.
Cand o clasa generica mosteneste o alta clasa generica tipul parametru al superclasei trebuie
sa fie declarat de subclasa. Aceasta trebuie realizata chiar daca tipul nu este folosit in
subclasa.
class SubClasaGenerica<T,V> extends ClasaGenerica<T>
Cand o clasa generica mosteneste o clasa negenerica nu trebuie sa specificam niciun
argument.
class SubClasaGenerica<T> extends ClasaNegenerica
In continuare vom da un exemplu pentru a ilustra primul caz.
public class Sum {
public static void main(String[] args) {
GenericClass<String> gen1 = new GenericClass<String>("Hello");
GenericClass<Integer> gen2 = new GenericClass<Integer>(8);
GenericClass<Double> gen3 = new GenericClass<Double>(2.34);

System.out.println("gen1.genericValue este " + gen1.get());


System.out.println("gen2.genericValue este " + gen2.get());
System.out.println("gen3.genericValue este " + gen3.get());

gen1.set("World");
System.out.println("gen1.genericValue este " + gen1.get());
// urmatoarea linie ar cauza eroare la compilare
// gen1.set(gen2.get());

GenericSubclass<String, Integer> subGen = new


GenericSubclass("Andy", 40);

boolean b1 = subGen instanceof GenericClass;


boolean b2 = subGen instanceof GenericSubclass;
// urmatoarea linie ar cauza eroare la compilare
// boolean b3 = subGen instanceof GenericClass<String>;

System.out.println("este subGen instanta a GenericClass: " +


b1);
System.out.println("este subGen instanta a GenericSubclass: " +
b2);

GenericClass<Integer> genInt1 = null;


GenericClass<String> genInt2 = null;

if (b1) {
// urmatoarea linie ar cauza eroare la compilare
// GenericClass<Integer> genInt1 = (GenericClass<String>)
subGen;

// urmatoarea linie NU genereaza eroare la compilare


genInt2 = (GenericClass<String>) subGen;
}

}
}

class GenericClass<T> {
private T genericValue;

public GenericClass(T genericValue) {


this.genericValue = genericValue;
}

public T get() {
return genericValue;
}

public void set(T genericValue) {


this.genericValue = genericValue;
}
}

class GenericSubclass<T, V> extends GenericClass<T> {


private V subType;

public GenericSubclass(T genericValue, V subType) {


super(genericValue);

this.subType = subType;
}

public V getSubtype() {
return subType;
}
}
Operatorul instanceof ne permite sa determinam daca un obiect este o instanta a unei clase.
Acelasi lucru poate fi facut si pentru clasele generice.
In codul anterior subGen este o instanta a lui GenericClass.
Linia ce-l declara pe b3 va genera o eroare la compilare. Tipul GenericClass<String> nu
exista la rulare pentru ca informatiile despre tipul obiectelor sunt indepartate la rulare printr-
un proces numit type erasure.
Putem converti o instanta a unei clase generice daca sursa si destinatia au argumente de tip
identice sau compatibile.

Tema: Sa se scrie o clasa utilitar generica, fara a apela la colectii (liste, set, map, etc), cu
metode statice, ce ofera operatii pe un sir de obiecte generice. Apoi, folosind doar metodele
acestei clase sa se afle maximul unui sir, de obiecte concrete, si pozitia pe care acesta se afla.
In final ultimele doua elemente ale sirului sa se inlocuiasca cu valoarea maximului, respectiv
a pozitiei acestuia.
public static <T, S extends T> S max(S[] coll, Comparator<? super T> comp);
public static <T, S extends T> int binarySearch(S[] list, T key, Comparator<?
super T> c);
public static <T extends Comparable<? super T>> void sort(T[] list);
@SafeVarargs
public static <T, S extends T> void modifyRear(S[] c, T... elements);//pentru
modificarea valorilor ultimelor elemente din sir

Tipul Enum
Folosirea constantelor date prin public static final este foarte folositoare, dar exista un anumit
caz in care ar trebui evitata si anume atunci cand constantele ar produce o interpretare
nepotrivita a unei validari intr-un domeniu de verificat.
Fie urmatorul exemplu: O metoda are ca argument un int, care reprezinta o stare dintr-o
multime de stari (sa spunem 0, 1 sau 2). Valoarea lui int trebuie sa fie doar intr-un domeniu
specificat. Metoda ar trebui sa verifice (cel mai probabil printr-un if) daca acest lucru se
intampla.
Acesata situatie poate fi imbunatatita folosind enumerari.
O enumerare este o lista finita de valori constante. Aceasta multime de valori trebuie sa fie
bine definita si specifica unui anumit tip de data.
Java detine cuvantul rezervat enum cu mult timp inainte de a-l fi implementat. In J2SE 5.0
este inclus tipul enumerare care extinde clasa java.lang.Enum. Spre deosebire de alte
limbaje, enumerarile in Java sunt instante ale unei clase.
Cand declaram enumerari putem include orice metoda, constructori si variabile necesare.
Pentru ca enumerarile Java se bucura de toate beneficiile de a fi clase „fully-fledged” (cu
drepturi depline), compilatorul executa verificari de tip (la compilare) cand folosesc instante
de enumerari. Totusi, compilatorul nu permite definirea claselor care extind in mod explicit
java.lang.Enum.
Enumerarile sunt eficiente pentru ca:
- sunt type safe: adica compilatorul nu va permite folosirea unui obiect incorect atunci
cand un anumit tip de enumerare este asteptat.
- pot contine membri arbitrari
- au valori public static final: deci nu pot fi modificate. Putem compara aceste
valori folosind operatorul == sau metoda equals();
- implementeaza interfetele Comparable si Serializable: deci putem folosi metoda
compareTo() pentru a compara si sorta valorile enumerarii.
- pot suprascrie metoda toString(): folosita pentru a returna numele valorilor din
enumerare. Putem suprascrie metoda pentru a personaliza output-ul
- ofera o metoda valueOf(): Enumerarile includ metodele statice values() (public
static enum-type[] values()) si valueOf() (public static enum-type
valueOf(String string)), metode care ne permit sa iteram valorile enumerarii.
Metoda valueOf() este complementul metodei toString(), asadar trebuie sa
suprascriem ambele metode daca dorim sa suprascriem metoda valueOf().
- definesc o metoda finala numita ordinal(): returneaza fiecare pozitie a valorilor
enumerarii, in ordinea in care ele sunt definite.
Declararea unui tip de date enumerare se face folosind cuvantul cheie enum urmat de numele
tipului si setul de valori.
public static enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY }

public static void main(String[] args) {


for (Day d : EnumSet.range(Day.MONDAY, Day.WEDNESDAY))
System.out.println(d) ;

}
In exemplul anterior am folosit clasa EnumSet din pachetul java.util care gestioneaza
submultimile de valori ale enumerarii.
Putem defini o enumerare intr-o sursa separata, ca in exemplul urmator:
public enum ComplexEnum {
FIRST(1), THIRD(3), TENTH(10) ;

private int index = 0 ;

private ComplexEnum(int index) {


this.index = index ;
}

public int getIndex() {


return index ;
}

public void print() {


System.out.println("indexul este setat in ComplexEnumeration la
" + index) ;
}
}

public static void main(String[] args) {


ComplexEnum complex = ComplexEnum.TENTH ;
complex.print();

for (ComplexEnum c : ComplexEnum.values()) {


switch (c) {
case FIRST:
case THIRD:
System.out.println("ComplexEnum poate fi 1 sau 3") ;
break ;
default:
System.out.println("ComplexEnum este 10") ;
}
}

System.out.println("pozitia lui THIRD este " +


ComplexEnum.valueOf("THIRD").getIndex()) ;
}
Am adaugat metodele getIndex() si print() in acelasi fel ca pentru orice clasa Java. In
plus am modificat si indexul predefinit, folosind constructorul clasei.
- Un enum poate fi declarat afara sau inauntrul unei clase, dar NU in interiorul unei
metode
- Un enum declarat in afara clasei NU trebuie marcat static, final, abstract,
protected sau private.
- Constructorii enum pot avea argumente, si pot fi suprascrisi. De asemenea, ei NU
trebuie niciodata invocati direct in cod. Sunt intotdeauna apelati automat atunci cand
un enum este initializat

Unit Testing
O unitate de tesatare este un test scris de un programator, pentru o functionalitate singulara a
unei aplicatii. Unit testele trebuie sa fie fine grained. Ele nu testeaza functionalitatea de nivel
inalt a aplicatiei. Testarea functionalitatii aplicatiei se numeste acceptance testing si este
facuta de persoane care inteleg mult mai bine functionalitatea aplicatiei decat programatorii.
Unit testele ofera increderea ca codul lucreaza corect. De indata ce toate testele trec inseamna
ca imbunatatirea facuta in cod nu a stricat nimic din ceea ce exista deja. Aceasta tehnica este
numita dezvoltare test-driven.
Programatorii scriu testele pentru ca se considera ca ei stiu cel mai bine codul si pot astfel
anticipa mare majoritate a problemelor ce pot aparea.
Testele trebuie facute pentru orice metoda ce are o implementare nontriviala. Iata descrierea
unui proces test-driven:
- rularea tuturor unit testelor intregului proiect si asigurarea ca ele trec
- scrierea unui unit test pentru noua implementare facuta
- rularea testului si observarea erorii
- reluarea implementarii si corectarea erorii
- reluarea testului pina ce acesta trece
Un pas important este testarea anumitor aspecte ce pot duce la esuarea executiei, cum ar fi
argumentul ilegal al unei metode. Ultimul pas este retestarea intregului proiect, astfel ca noua
functionalitate introdusa in aplicatie sa nu creeze probleme globale.
Toate acestea trebuie facute inainte de comiterea pe un repository.
Trebuie sa cream, de asemenea, teste atunci cand sunt raportate bug-uri. Procesul este:
- scrierea unui test ce expune bug-ul
- rularea intregii proceduri de testare si observarea erorii
- repararea bug-ului
- rularea intregii proceduri de testare si observarea succesului
Toate testele trebuie scrise in stilul trece/nu trece. Fie urmatoarea iesire a unui test:
Now Testing Person.java:
First Name: Tanner
Last Name: Burke
Age: 1
Acest test a trecut sau nu? Ar trebui cautat in cod pentru a descoperi interpretarea acestui
rezultat. Putem grupa testele si afisa un rezultat mult mai transparent dupa cum urmeaza:
Now Testing Person.java:
Failure: Expected Age 2, but was 1 instead
Now Testing Account.java:
Passed!
Now Testing Deposit.java:
Passed!
Summary: 2 tests passed, 1 failed.
Cateva sfaturi pentru realizarea testelor:
- testarea conditiilor la limita, cum ar fi: indicele maxim sau minim al sirurilor si listelor
- testarea intrarilor ilegale in metode
- testarea stringurilor nule sau goale. Testarea stringurilor ce contin spatii goale
nepermise
Testele de acceptare trebuie sa fie, de asemenea, create pentru a trece sau esua, si trebuie
facute cat mai automate cu putinta. Dar spre deosebire de unit teste, testele de acceptare nu
trebuie sa treaca 100%. Spre exemplu testele de acceptare sunt scrise inainte ca
functionalitatea sa die scrisa.

Refactorizarea
Refactorizarea este o practica de imbunatatire a design-ului codului fara a modifica
functionalitatea. Prin refactorizare se mentine codul cat se poate de simplu. Iata un exemplu:
public class Person {
private String firstName;
public void setFirst(String n) {
this.firstName = n;
}
}
Codul poate fi imbunatatit prin alegerea unui nume mai potrivit argumentului:
public class Person {
private String firstName;
public void setFirst(String firstName) {
this.firstName = firstName;
}
}
Codul poate fi imbunatatit si mai mult prin redenumirea metodei:
public class Person {
private String firstName;
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
Modificarea numelui unei metode determina modificarea referintelor metodei din intreaga
aplicatie.
Eliminarea duplicarilor este un alt scop al refactorizarii. Codul duplicat este greu de intretinut,
pentru ca inseamna modificari in mai multe locuri.
Refactorizarea trebuie facuta constant, pe tot timpul existentei proiectului. Refactorizarea tine
cont de design pattern-i. Scrierea unit testelor este o buna metoda de identificare a portiunilor
de cod ce necesita refactorizare. Cand scriem un test pentru o clasa devenim clienti ai clasei si
putem realiza ca API-ul este prea complicat. Este momentul in care acesta trebuie refactorizat
si facut simplu de utilizat.
Este recomandat ca inainte de refactorizare sa avem un unit test la indemana. El ar trebui sa
treaca si dupa refactorizare.

JUnit
Testele unitare sunt simple, preferabil continute intr-o singura comanda.
Pentru a crea un test case cu JUnit vom crea o subclasa a lui junit.framework.TestCase.
Fiecare unit test este o metoda publica, fara argumente ce are prefixul test. Daca nu vom
urma aceasta conventie de nume JUnit nu va fi capabil sa localizeze metodele de testare
automat. In acest caz va trebui sa scriem o metoda suite() si sa construim instante ale test
case-urilor, trimitand numele metodelor de testare constructorului.
Fie urmatorul exemplu:
import junit.framework.TestCase;
/**
* Sample unit tests for the {@link Person} class.
*/
public class TestPerson extends TestCase {
/**
* A unit test to verify the name is formatted correctly.
*/
public void testGetFullName( ) {
Person p = new Person("Aidan", "Burke");
assertEquals("Aidan Burke", p.getFullName( ));
}
/**
* A unit test to verify that nulls are handled properly.
*/
public void testNullsInName( ) {
Person p = new Person(null, "Burke");
assertEquals("? Burke", p.getFullName( ));
// this code is only executed if the previous
assertEquals passed!
p = new Person("Tanner", null);
assertEquals("Tanner ?", p.getFullName( ));
}
}
O metoda de testare poate arunca si o exceptie. In acest caz, JUnit prinde exceptia si
raporteaza o eroare. El va continua sa execute alte metode de testare.
Unit testele utilizeaza metode assertXXX() pentru testare. Spre exemplu:
assertEquals("Aidan Burke", p.getFullName( ));
Aceasta metoda confirma ca cele doua argumente sunt egale, folosind eventual metoda
equals(). Astfel testul trece. Altfel, testul esueaza si se trimite un raport si restul testului este
sarit.
Pentru a rula un JUnit trebuie sa includem junit.jar in classpath.
La laborator exemple de rulare cu Eclipse.
Putem folosi metoda assertTrue() pentru aproape orice test, dar pentru a face testele mult
mai usor de inteles si pentru a avea mesaje mai bune de eroare folosim assertXXX().
Toate metodele accepta un prim parametru, optional, de tip String. Acesta furnizeaza un
mesaj descriptiv in cazul in care testul esueaza. Spre ex:
assertEquals("Employees should be equal after the clone( ) operation.",
employeeA, employeeB);
Metoda assertSame() compara referintele a doua obiecte, asigurandu-ne ca ele refera
aceeasi adresa de memorie. Urmatoarele apeluri sunt echivalente:
assertSame("Expected the two parts to be identical.", part1, part2);
assertTrue("Expected the two parts to be identical.", part1 == part2);
Metoda assertEquals() poate compara si double-uri si float-uri. Metoda accepta un
argument delta folosit pentru rotunjire.
Testarea valorii de null este simpla:
assertNull("Did not expect to find an employee.",
database.getEmployee("someInvalidEmployeeId"));
In mod asemanator putem testa daca valoarea returnata este ne nula:
assertNotNull("Expected to find an employee with id=" + id,
database.getEmployee(id));
Putem explicit cauza esuarea unui test folosind metoda fail(). Spre ex:
fail("Unable to configure database.");
Fiecare unit test ar trebui sa verifice o functionalitate specifica. Nu ar trebui sa combinam
teste multiple, fara legatura, in aceeasi metoda. Spre exemplu:
public void testGame( ) throws BadGameException {
Game game = new Game( );
Ship fighter = game.createFighter("001");
assertEquals("Fighter did not have the correct identifier", "001",
fighter.getId( ));
Ship fighter2 = game.createFighter("001");
assertSame("createFighter with same id should return same object",
fighter, fighter2);
assertFalse("A new game should not be started yet",
game.isPlaying( ));
}
Acesta este un exemplu de design prost. Metoda testeaza parti neinrudite de functionalitate.
Refactorizat, testul anterior se transforma in mai multe teste ce testeaza aspecte independente.
public void testCreateFighter( ) throws BadGameException {
Game game = new Game( );
Ship fighter = game.createFighter("001");
assertEquals("Fighter did not have the correct identifier", "001",
fighter.getId( ));
game.shutdown( );
}
public void testSameFighters( ) throws BadGameException {
Game game = new Game( );
Ship fighter = game.createFighter("001");
Ship fighter2 = game.createFighter("001");
assertSame("createFighter with same id should return same object",
fighter, fighter2);
game.shutdown( );
}
public void testGameInitialState( ) throws BadGameException {
Game game = new Game( );
assertFalse("A new game should not be started yet", game.isPlaying(
));
game.shutdown( );
}
Esuarea unui test nu duce la esuarea si a celorlalte. Combinarea mai multor asertiuni in
acelasi test se justifica atunci cand cand intr-o succesiune de teste, esuarea primului ar duce
oricum si la esuarea urmatoarelor.

Set Up si Tear Down


Putem sa evitam duplicarile de cod in teste folosind metodele setUp() si tearDown() atunci
cand testele au aceleasi initializari si destructori.
JUnit urmareste o secventa specifica de evenimente atunci cand invoca testele. Pentru inceput
contruieste o instanta a testului in fiecare metoda. De aceea daca avem cinci metode de
testare, JUnit construieste cinci instante. Din acest motiv variabilele instanta nu pot fi utilizate
spre a imparti stari intre metodele de testare. Dupa construirea obiectelor de testare, JUnit
urmareste pasii pentru fiecare metoda de testare:
- apeleaza metoda setUp() a testului
- apeleaza metoda test
- apeleaza metoda tearDown() a testului
In exemplul din lectia precedenta folosim aceste metode pentru eliminarea duplicarii codului:
public class TestGame extends TestCase {
private Game game;
private Ship fighter;
public void setUp() throws BadGameException {
this.game = new Game();
this.fighter = this.game.createFighter("001");
}
public void tearDown() {
this.game.shutdown();
}
public void testCreateFighter() {
assertEquals("Fighter did not have the correct identifier",
"001", this.fighter.getId());
}
public void testSameFighters() {
Ship fighter2 = this.game.createFighter("001");
assertSame("createFighter with same id should return same
object", this.fighter, fighter2);
}
public void testGameInitialState() {
assertTrue("A new game should not be started yet",
!this.game.isPlaying());
}
}
Putem deseori ignora metoda tearDown() pentru ca procesele de testare sunt de durata mica
si obiectele sunt colectate de garbage collector. Totusi, metoda are utilitate atunci cand avem
conexiuni deschise la baza de date, vizualizam frame-uri GUI sau folosim alte resurse. Si la
nivelul optimizarii folosirii memoriei, metoda isi dovedeste utilitatea pentru ca o referinta
setata la null ajuta garbage collector-ul sa valorifice memoria.
Observatie: constructorul este apelat inainte de metoda setUp(). Rolul metodei devine
important atunci cand initializarile nu se pot face corect in constructor, spre exemplu intr-o
ierarhie de clase, atunci cand clasa derivata nu este complet construita.
O alta problema importanta este aceea ca am dori sa initializam si sa distrugem o singura data
nu pentru fiecare test in parte. Clasa junit.extensions.TestSetup ofera aceasta facilitate.
public class TestPerson extends TestCase {
public void testGetFullName( ) { ... }
public void testNullsInName( ) { ... }
public static Test suite( ) {
TestSetup setup = new TestSetup(new
TestSuite(TestPerson.class)){
protected void setUp( ) throws Exception {
// do your one-time setup here!
}
protected void tearDown( ) throws Exception {
// do your one-time tear down here!
}
};
return setup;
}
}
TestSetup este o subclasa a lui junit.extensions.TestDecorator, care este clasa de baza
pentru a defini teste custom. Principalul motiv pentru extinderea lui TestDecorator este
obtinerea abilitatii de a executa cod inainte si dupa ce ruleaza un test.
Metodele setUp() si tearDown() sunt apelate o singura data, inainte si dupa ce un test este
trimis constructorului. In cazul nostru trimitem un TestSuite constructorului TestSetup.

Organizarea testelor in suite


Scopul este de a crea suite de teste care sa ruleze toate o data.
Atunci cand rulam un test JUnit cauta metoda suite(). Daca metoda nu este gasita, JUnit
foloseste reflectia pentru a gasi metodele testXXX() in clasa de test si a le grupa intr-o suita.
Apoi ruleaza toate testele. Putem modifica comportamentul predefinit dupa cum urmeaza:
public class TestGame extends TestCase {
...
public static Test suite( ) {
return new TestSuite(TestGame.class);
}
}
Prin trimiterea obiectului TestGame.class constructorului TestSuite ii transmitem JUnit-
ului sa localizeze metodele testXXX() din clasa respectiva si sa le adauge unei suite. Acesta
este de fapt si comportamentul predefinit. In plus insa, putem adauga numai anumite teste sau
putem controla ordinea in care acestea se executa:
public static Test suite( ) {
TestSuite suite = new TestSuite(TestGame.class);
suite.addTest(new TestSuite(TestPerson.class));
return suite;
}
O alta facilitate furnizata de JUnit este posibilitatea rularii repetate a testelor:
public static Test suite( ) {
// run the entire test suite ten times
return new RepeatedTest(new TestSuite(TestGame.class), 10);
}
Primul parametru al constructorului clasei RepeatedTest este obiectul ce construieste suita,
avand ca argument numele clasei de test. Cel de-al doilea argument este numarul de iteratii.

Conventia de nume
O conventie de nume consistenta are doua scopuri: primul este ca face codul mult mai usor de
intretinut si cel de-al doilea ca faciliteaza automatizarea. Am prefixat toate clasele de test,
date ca exemplu pina acum, cu prefixul Test. Spre exemplu TestPerson, care corespunde
clasei Person.
Pe de alta parte creand un test pe fiecare clasa, acesta este mult mai usor de identificat intr-un
director.

Manipularea exceptiilor
In exemplul urmator constructorul clasei Person ar trebui sa arunce o
IllegalArgumentException daca ambele argumente sunt null. Testul esueaza daca nu se
arunca exceptia.
public void testPassNullsToConstructor( ) {
try {
Person p = new Person(null, null);
fail("Expected IllegalArgumentException when both args are
null");
} catch (IllegalArgumentException expected) {
// ignore this because it means the test passed!
}
}
Aceasta este singura tehnica buna de folosit atunci cand asteptam o exceptie. Pentru alte
conditii de eroare, va trebui sa lasam ca exceptia sa se propage catre JUnit. Acesta va prinde
exceptia si o va raporta o eroare de tetare. Mai jos este o situatie ce ar trebui evitata:
public void testBadStyle( ) {
try {
SomeClass c = new SomeClass( );
c.doSomething( );
...
} catch (IOException ioe) {
fail("Caught an IOException");
} catch (NullPointerException npe) {
fail("Caught a NullPointerException");
}
}
Prima problema este ca JUnit oricum prinde exceptiile neprinse, asadar codul scris este inutil,
iar introducerea blocurilor try/catch cresc complexitatea testelor, facandu-le greu de
intretinut. Iata varianta corecta a codului de mai sus:
public void testGoodStyle( ) throws IOException {
SomeClass c = new SomeClass( );
c.doSomething( );
...
}

Rularea concurenta a testelor


Clasa junit.extensions.ActiveTestSuite este folosita pentru a construi o suita de teste
ce ruleaza concurent. Clasa ruleaza fiecare dintre testele sale intr-un fir separat. Suita nu se
incheie pina cand toate firele nu se incheie. Exemplul urmator arata cum ruleaza trei teste in
trei fire diferite:
public static Test suite( ) {
TestSuite suite = new ActiveTestSuite( );
suite.addTest(new TestGame("testCreateFighter"));
suite.addTest(new TestGame("testGameInitialState"));
suite.addTest(new TestGame("testSameFighters"));
return suite;
}
Prin combinarea claselor ActiveTestSuite si RepeatedTest putem descoperi probleme ale
firelor ce apar intermitent.

Exceptii
Aplicatiile intalnesc erori la executie. Aplicatiile robuste trebuie sa manipuleze erorile cat mai
elegant cu putinta. Cateva motive de aparitie a erorilor:
- Aplicatia nu are comportamentul asteptat
- rezultat al unui bug
- factori din spatele aplicatiei: conexiune la baza de date intrerupta, functionarea driver-
elor, etc
In Java, atunci cand folosim resurse externe, compilatorul cere sa manipulam exceptiile ce pot
aparea (spre ex: java.nio, java.sql). A manipula o exceptie inseamna sa adaugam in cod
un bloc de manipulare a erorii (try-catch). A declara o exceptie inseamna ca declaram ca o
metoda poate esua in executie sa (clauza throws).
O exceptie reprezinta, asadar, un eveniment ce intrerupe executia normala a unui program.
Cand se intalneste o exceptie la rulare, metoda in care a aparut exceptia creaza un obiect
exceptie. Metoda poate arunca exceptia inapoi catre metoda apelanta, care probabil o va trata.
Obiectul exceptie include informatii despre exceptie, cum ar fi metoda in care exceptia a
aparut si cauza exceptiei. Metoda apelanta poate trimite exceptia, mai departe, unei alte
metode, care la randul sau a apelat-o. Daca nicio metoda nu manipuleaza exceptia, programul
se termina cand obiectul exceptie atinge metoda main().
Cand apare o exceptie o putem preveni trimitand-o in stiva de apel sau tratand-o, astfel
aplicatia continundu-si executia pe o alta cale.
Blocul try cuprinde codul ce poate cauza o exceptie. Acest bloc este numit si cod protejat
(protected code). Un bloc try este urmat de zero sau mai multe blocuri catch ce specifica
tipurile de exceptie ce pot fi prinse.
catch ar trebui sa fie folosit pentru:
- a reincerca executia unei operatii
- incercarea de a executa o alta operatie
- o iesire eleganta sau apelul lui return
Scrierea unui bloc catch vid este o bad practice.
Blocurile catch se executa in ordinea in care au fost scrise, de sus in jos, pina la intilnirea
primei exceptii ce se potriveste ca tip cu cel al tipului aruncat. Cel mult un bloc catch se
executa. De aceea, vom plasa blocul catch, cel mai general, pe ultima pozitie in blocurile din
sirul blocurilor catch. Daca nu se arunca nicio exceptie atunci nici un bloc catch se executa.
Blocul finally este optional, daca try este urmat de macar un catch. Blocul finally se
executa indiferent daca avem sau nu aruncata exceptie, cu o singura exceptie, atunci cand
executam System.exit.
// sintaxa de baza a lui try
try {
// cod ce poate arunca o exceptie
}
catch (TipExceptie1 Identificator1) {
// cod de manipulare a exceptiei
}
catch (TipExceptie2 Identificator2) {
// cod de manipulare a exceptiei
}

finally {
// 0 sau 1 clauze finally
}
Cand deschidem resurse precum fisiere sau conexiuni la baza de date, trebuie intotdeauna sa
le inchidem. Incercarea de a le inchide in try poate fi problematica deoarece putem incheia
blocul sarind operatia. De aceea este recomandata inchiderea acestora in blocul finally.
Uneori operatia pe care o executam in blocul finally poate genera o exceptie. In acest caz
va trebui sa scrie un bloc try-catch in interiorul lui finally. Putem avea try-catch si in
interiorul lui try sau al lui catch.
public class FinallyExampleMain {
public static void main(String[] args) {
InputStream in = null;
try {
System.out.println("About to open a file");
in = new FileInputStream("missingfile.txt");
System.out.println("File open");
int data = in.read();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
System.out.println("Failed to close file");
}
}
}
}
Toate exceptiile si erorile Java sunt subclase ale clasei Throwable din pachetul java.lang.
Putem loga exceptiile ce apar pe parcursul aplicatiei folosind Apache Log4j si framework-uri
predefinite din java.util.
In Java SE7 avem la dispozitie un try cu resurse, ce va inchide automat resursele. Astfel
eliminam folosirea lui finally. Orice clasa ce implementeaza java.lang.AutoCloseable
poate fi utilizata ca resursa. Pentru ca o resursa sa fie automat inchisa, referinta sa trebuie
declarata in interiorul parantezelor rotunde ale lui try.
public class FinallyExampleMain {
public static void main(String[] args) {
System.out.println("About to open a file");
try(InputStream in = new FileInputStream("missingfile.txt")) {
System.out.println("File open");
int data = in.read();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
Putem deschide mai multe resurse daca ele sunt separate prin punct si virgula. Inchiderea lor
se va face in ordine inversa deschiderii.
Daca o exceptie apare in momentul crearii unei resurse AutoCloseable, controlul va fi
preluat imediat de blocul catch.
Daca o exceptie apare in blocul try, toate resursele vor fi inchise inainte de a rula catch.
Daca o exceptie va fi generata in timpul inchiderii resursei, ea va fi suprimata. Controlul va fi
preluat de un bloc catch. Exceptiile ar putea fi afisate astfel:
public class FinallyExampleMain {
public static void main(String[] args) {
System.out.println("About to open a file");
try (InputStream in = new FileInputStream("missingfile.txt")) {
System.out.println("File open");
int data = in.read();
} catch (Exception e) {
System.out.println(e.getMessage());
for (Throwable t : e.getSuppressed())
System.out.println(t.getMessage());
}
}
}
Resursele din try cu resurse trebuie sa implementeze unul dintre:
- java.lang.AutoCloseable, care poate arunca un Exception
- java.io.Closeable, care extinde AutoCloseable si care poate arunca o
IOException
Interfata AutoCloseable are o singura metoda void close() throws Exception;
Metoda close() din AutoCloseable, spre deosebire de cea din Closeable, nu este
idempotenta. Aceasta inseamna ca apelul sau mai mult decat o data poate avea efecte
secundare. Cei care implementeaza metoda din AutoCloseable vor trebui sa o faca
idempotenta
Java SE7 furnizeaza o clauza multi-catch. Avantajele deriva din faptul ca uneori dorim sa
facem anumite actiuni independent de exceptia ce este generata. Multi-catch reduce cantitatea
de cod scris, prin eliminarea necesitatii scrieri mai multor clauze catch cu acelasi
comportament. Un alt avantaj este acela ca face mai putin probabila incercarea de a prinde o
exceptie generica. Alternativele de tip, separate prin bara verticala, nu trebuie sa aiba o relatie
de mostenire.
public class FinallyExampleMain {
public static void main(String[] args) {
System.out.println("About to open a file");
try (InputStream in = new FileInputStream("missingfile.tx");
Scanner s = new Scanner(in);) {
System.out.println("File open");
int data = s.nextInt();
} catch (NoSuchElementException | IOException e) {
System.out.println(e.getClass().getName());
System.out.println("Quiting");
}
}
}
Numai un obiect Throwable poate fi aruncat, si acesta include exceptii sau erori de sistem.
Metodele din Throwable sunt:
- getMessage(), ce returneaza un string cuprinzand mesajul de eroare al obiectului
Throwable
- toString(), returneaza o descriere a obiectului exceptie incluzand si tipul exceptiei
- initCause(), seteaza cauza exceptiei care este intotdeauna un alt obiect Throwable,
ceea ce permite inlantuirea exceptiilor
- printStackTrace(), permite identificarea metodei ce a aruncat o anumita exceptie
Ierarhia Throwable este urmatoarea:
Clasa Throwable are doua subclase directe:
- Error, si clasele derivate din ea sunt folosite pentru erori de sistem sau erori la
compilare ce nu pot fi tratate de aplicatie, ca spre ex.
ExceptionInInitializerError, StackOverflowError si NoClassDefFoundError
- Exception, si clasele derivate din ea sunt utilizate pentru implementarea exceptiilor
pe care o aplicatie se asteapta sa le intilneasca. Fiecare subclasa a lui Exception
reprezinta un tip particular de exceptie
Clasa RuntimeException si subclasele sale descriu obiectele exceptie ce sunt aruncate
automat de JVM, la rulare. Exceptiile la rulare sunt generate, in general, de bug-uri in codul
sursa. Printre subclasele acesteia amintim:
- ArithmeticException, pentru exceptiile ce cuprind incalcarea regulilor matematice,
cum ar fi impartirea la zero
- IndexOutOfBoundsException, cand indexul unui string sau array nu este in domeniul
acceptat
- NegativeArraySizeException, cand incercam sa cream un sir cu numar negativ de
elemente
Pentru a prinde exceptiile la rulare putem folosi blocurile try-catch, dar acest lucru nu este
obligatoriu. Daca try-catch nu este prezent, toate exceptiile la rulare vor fi considerate
exceptii neverificate (unchecked exceptions). Aceasta inseamna ca compilatorul nu verifica
daca ele au fost declarate sau manipulate.
Toate exceptiile ce mostenesc Exception dar nu mostenesc RuntimeException sunt
cunoscute ca clase exceptie verificate. Acestea trebuie declarate intr-o metoda folosind clauza
throws sau tratate explicit in corpul metodei. Altfel se va genera eroare la compilare. Din
categoria acestor exceptii amintim:
- ClassNotFoundException, este aruncata atunci cand un program incearca sa incarce
o clasa, folosind numele ei, dar definirea clasei cu numele specificat nu este gasita.
Aceste exceptii apar atunci cand un program utilizeaza metoda forName() din Class
sau findSystemClass() sau loadClass() din ClassLoader
- InterruptedException, aruncata atunci cand un fir a fost inactiv pentru mult timp si
un alt fir ce foloseste metoda interrupt() din Thread incearca sa-l intrerupa
- IllegalAccessException, aruncata atunci cand executia metodei curente nu are
acces la definitia unui camp pe care doreste sa-l acceseze sau modifice sau la definitia
unei metode pe care doreste sa o invoce
- FileNotFoundException, apartine pachetului java.io si este aruncata atunci cand
aplicatia nu poate deschide un fisier specificat (fisier inaccesibil sau inexistent).
Exceptiile pot fi tratate in interiorul metodei sau in metoda sa apelanta. Cand o exceptie a fost
aruncata trebuie sa implementam interfata ExceptionListener ce receptioneaza doar
exceptii si apeleza metoda exceptionThrown().
Pentru a ne asigura ca o metoda poate arunca exceptii utilizam cuvantul rezervat throws cand
declaram metoda. In clauza throws specificam tipurile de exceptii ce pot fi aruncate, tipuri
separate prin virgula.
In metoda apelanta exceptia poate fi tratata sau trimisa metodei apelante a acesteia adaugand
clauzei throws exceptiile netratate.
Daca o metoda suprascrie o metoda din clasa de baza ea poate arunca numai aceleasi exceptii
ca si metoda din clasa de baza, o submultime a lor sau subclase ale exceptiilor. Metoda
suprascrisa nu poate arunca exceptii ce sunt superclase ale claselor exceptie ale metodei din
clasa de baza, sau exceptii ce apartin unei alte ierarhii. Aceste reguli se aplica numai
exceptiilor verificate.
Fie urmatorul exemplu:
public class ThrowsMain {
public static void main(String[] args) {
try {
int data = readByteFromFile();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}

public static int readByteFromFile() throws IOException {


try (InputStream in = new FileInputStream("a.txt")) {
System.out.println("File open");
return in.read();
}
}
}
Nu am utilizat catch in definitia metodei readByteFromFile(). Rolul lui try este doar
acela al destionarii resurselor. Tot in semnatura metodei am declarat aruncarea lui
IOException, fara a specifica explicit aruncarea lui FileNotFoundException. Aceasta
datorita faptului ca cele doua clase sunt ierarhice. Totusi, este o buna practica sa le aruncam
pe ambele.
O aplicatie Java SE trebuie sa-si manipuleze exceptile inainte de a fi aruncate de main().
main() este posibil sa arunce exceptii, dar acest lucru este recomandat a fi evitat.
Putem rearunca o exceptie ce deja a fost prinsa.
public static int readByteFromFile() throws IOException {
try (InputStream in = new FileInputStream("a.txt")) {
System.out.println("File open");
return in.read();
}
catch(IOException e){
System.out.println(e.getMessage());
throw e;
}
}
Java SE7 suporta rearuncarea precisa a tipului exceptie. Urmatorul exemplu nu s-ar compila
in Java SE6 pentru ca catch primeste o Exception, dar arunca o IOException.
public static int readByteFromFile() throws IOException {
try (InputStream in = new FileInputStream("a.txt")) {
System.out.println("File open");
return in.read();
}
catch(Exception e){
System.out.println(e.getMessage());
throw e;
}
}
Putem crea clase exceptii custom prin extinderea clasei Exception sau a uneia dintre
subclase ei.
package exceptions;

public class DAOException extends Exception {


private static final long serialVersionUID = 1L;

public DAOException() {
super();
}

public DAOException(String message) {


super(message);
}
}
Exceptiile custom nu sunt aruncate niciodata de clasele standard Java. Sta in priceperea
dezvoltatorului aruncarea lor.
Clasa exceptie standard poate suprascrie metode sau adauga noi functionalitati. Deoarece
exceptiile captureaza informatii despre o problema aparuta va trebui sa adaugam campuri si
metode dependente de tipul de informatie pe care dorim sa-l capturam. Daca un string poate
captura toate informatiile necesare putem utiliza metoda getMessage() pe care toate clase
exceptie o mostenesc din Throwable. Orice constructor de exceptie ce primeste un string il va
stoca, astfel incat sa fie returnat cu getMessage().
Java permite ca exceptiile sa fie create si aruncate:
- implicit, exceptiile sunt create si aruncate automat de catre JVM, la rulare
- explicit, exceptiile sunt create explicit prin specificarea in cod ca un anumit tip de
obiect exceptie trebuie sa fie creat si aruncat atunci cand o anumita situatie este
intalnita. Acest tip de tratare este folositor daca dorim ca metoda sa trimita exceptia
metodei apelante, dar in anumite circumstante. Pentru a arunca o exceptie explicit se
utilizeaza cuvantul rezervat throw, pentru orice obiect ce este o instanta sau o
subclasa a lui Throwable. Noul obiect exceptie trebuie sa fie creat in aceeasi linie
unde se afla throw. Aceasta deoarece urma stivei de apel arata unde obiectul exceptie
a fost creat si nu unde a fost aruncat. Cand cream si aruncam o exceptie explicit putem
adauga propriul mesaj de eroare. Aceasta este posibil deoarece clasa Throwable are
un constructor cu argument string. Mesajul customizat va fi afisat cand aplicatia se
termina.
In versiunile de dinainte de 1.4 era dificil de comunicat cauzele unui esec peste doua sau mai
multe layere abstracte ale aplicatiei. O solutie era trimiterea exceptiei de la un layer la altul.
Aceasta crea o dependenta intre layere care trebuiau sa fie distincte. Aceasta solutie este
dezavantajoasa. Alta solutie ar fi extinderea clasei Exception pentru a asocia o cauza (cause)
Throwable unei exceptii particulare, dar si aceasta solutie este inacceptabila atunci cand
avem nevoie sa folosim altundeva API-ul.
Inlantuirea exceptiilor incepand cu versiunea 1.4, Java ne permite sa pastram urma exceptiei
originale ce a fost aruncata. Aceasta se face pe baza proprietatii cause si a inca doi
constructori ai clasei Throwable ce admit cause ca parametru. Setarea cauzei directe a
fiecarei exceptii aruncate determina posibilitatea urmaririi complete a istoriei exceptiei. O
singura exceptie contine lantul exceptiilor cu care se afla in legatura. Cauza unui obiect
Throwable este o referinta la un alt obiect Throwable care a cauzat aruncarea primului.
Proprietatea cause nu poate fi accesata direct, dar poate fi setata respectiv accesata prin:
- initCause() sau printr-un constructor al clasei Throwable
- getCause()
Exista patru constructori ai clasei Throwable care sunt implementati in clasele Exception,
RuntimeException si Error. Dintre acestia doi initializeaza cauza.
public Throwable();
public Throwable(String message);
public Throwable(Throwable cause);
public Throwable(String message, Throwable cause);
In exemplul urmator putem observa modul in care a fost creata o cauza si apoi cum ea a fost
urmarita:
public class Test{

static void metoda (int x, int y) throws Exception{


try {
for (int i = 1; i >= x ; i--) {
System.out.println (y /i) ;
}
}
catch (ArithmeticException e) {
Exception myExcept = new Exception("Eroare a unei operatii
aritmetice") ;
myExcept.initCause (e) ;
throw myExcept ;
}
}

public static void main (String args[]) {


try {
metoda(-5,6);
} catch (Exception e) {
System.out.println (e.getMessage()) ;
System.out.println (e.getCause().getMessage()) ;
}
}
}
Uneori este necesar sa suprascriem metoda getCause(), spre exemplu atunci cand tratam
exceptii in cod legacy, in versiuni ale Javei in care se folosea doar cod legacy si proprietatea
cause nu exista.
Utilizatorii pot sa-si creeze propriile exceptii cu ajutorul carora pot oferi mai multe informatii
despre cum exceptia a aparut sau sa manipuleze, dependent de aplicatie, exceptia. Pentru
aceasta vom crea o noua clasa ce extinde Exception sau o clasa ce a extins Exception. Clasa
exceptie utilizator nu poate extinde Throwable pentru ca aceasta este clasa de baza atat
pentru exceptii cat si pentru erori. De asemenea, nu poate extinde clasa Error, aceasta fiind
folosita pentru erori pe care aplicatia nu le poate recupera.
Nu este recomandat sa creem subclase utilizator ale clasei RuntimeException si aceasta
deoarece obiectele acestea ar trebui sa fie generate si aruncate de JVM. Utilizatorul nu trebuie
sa se ingrijeasca de aceste exceptii.
Pentru a ascunde tipul unei exceptii ce a fost generate utilizam o exceptie wrapper. Fie
urmatorul exemplu:
import entities.Entity;
import exceptions.DAOException;

public class EntityDAO {


private Entity[] entityArray;

public EntityDAO(int val) {


entityArray = new Entity[val];
}

public Entity findById(int id) throws DAOException {


try {
return entityArray[id];
} catch (ArrayIndexOutOfBoundsException e) {
throw new DAOException("out of bounds", e);
}
}

}
package exceptions;

public class DAOException extends Exception {


private static final long serialVersionUID = 1L;

public DAOException(Throwable cause) {


super(cause);
}

public DAOException(String message, Throwable cause) {


super(message, cause);
}

}
import daos.EntityDAO;
import exceptions.DAOException;

public class TestEntityDao {

public static void main(String[] args) {


EntityDAO ent = new EntityDAO(3);

try {
ent.findById(5);
} catch (DAOException e) {
Throwable t = e.getCause();
System.out.println(e.getMessage() + t.getMessage());
}

}
O implementare DAO poate utiliza o exceptie wrapper pentru a pastra abstractia si pentru a
evita netratarea exceptiilor. Iata explicatia: majoritatea metodelor din DAO trebuie sa trateze
exceptii (IOExceptions sau SQLExceptions). Daca am folosi semnaturi fara throws am
determina clientul sa trateze exceptiile, la implementare. Am pierde astfel abstractia. Prin
folosirea exceptiilor wrapper ii dam clientului posibilitatea de a stii care sunt problemele
aparute la rulare.
In versiunile anterioare ale Javei daca aparea o eroare intr-un fir atunci se arunca o eroare
exceptie si firul se termina, exceptia navigand spre radacina ThreadGroup. Numele firului,
numele exceptiei, mesajul exceptiei si stack trace-ul erau afisate. In 1.4 si versiunile
anterioare inseram cod propriu in ThreadGroup pentru a manipula aceste exceptii. In 1.6
manipularea exceptiilor neprinse este simplificata pentru ca putem defini manipulatorul
exceptiilor neprinse, la nivelul firului de executie.
Metoda setUncaughtExceptionHandler(), introdusa in 1.5, ne permite sa preluam
controlul deplin asupra raspunsului unui fir, in cazul exceptiilor neprinse, prin setarea unui
manipulator de exceptii neprinse. Pentru a seta manipulatorul in toate firele vom trimite ca
parametru al acestei metode o implementare a interfetei
Thread.UncaughtExceptionHandler. Metoda acestei interfete este uncaughtException().
Daca niciun manipulator nu este setat obiectul ThreadGroup va functiona ca un manipulator
de exceptie.
Metoda getUncaughtExceptionHandler() returneaza handler-ul invocat atunci cand un fir
se termina din cauza unei exceptii neprinse. Pentru a determina cum sa manipuleze exceptia
neprinsa, JVM mai intai cauta un manipulator de fire pe care sa-l invoce. Daca exista il
invoca. Apoi JVM trimite exceptia in ierarhia ThreadGroup pina ce atinge radacina acesteia,
ceea ce inseamna ca ThreadGroup nu a suprascris uncaughtException(). In cele din urma
JVM apeleza Thread.getDefaultExceptionHandler() pentru a invoca manipulatorul
predefinit.
In exemplul urmator vom crea un manipulator de exceptie, HandlerPropriu, pe care il vom
seta intr-un fir, ExceptieNeprinsa. Metoda run() a acestui fir nu prinde nicio exceptie, desi
face o impartire la zero. Exceptia este preluata automat de catre manipulatorul definit.
class HandlerPropriu implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.err.printf("%s: %s la linia %d in fisierul %s%n",
t.getName(), e.toString(), e.getStackTrace()[0].getLineNumber(),
e.getStackTrace()[0].getFileName());
}
}

public class ExceptieNeprinsa extends Thread{


public ExceptieNeprinsa() {
setName("Exceptia neprinsa explicit:") ;
setUncaughtExceptionHandler(new HandlerPropriu()) ;
}

public void run(){


System.out.println(3/0);
}

public static void main (String args[]) {


ExceptieNeprinsa x=new ExceptieNeprinsa();
x.start();
}
}

Asertiuni (assertions)
Asertiunea ne permite sa testam presupunerea despre cum ruleaza un program. Asertiunea
contine o expresie booleana, avand valoarea predefinita true. Asertiunea este diferita de toate
celelalte teste pentru ca verifica conditii ce nu pot fi violate. Daca expresia booleana are
valoarea false atunci sistemul arunca o AssertionError. Mecanismul de asertiuni este
valoros deoarece ofera debugging mult mai eficient, ajuta la intretinerea codului prin
documentarea lucrului intern al programului, incurajeaza forma de programare contractuala
(contract programming).
Programarea contractuala este o tehnica utilizata pentru a incuraja un inalt nivel al calitatii
softului, reutilizarii si increderii. Furnizorul si consumatorul unui apel de metoda sunt
impreuna de acord sa adere la un contract bine definit, ce este fortat de folosirea asertiunilor.
Asertiunile sunt in mod predefinit dezactivate. Putem sa le activam sau dezactivam folosind
comenzi la rulare. Asertiunile asigura ca o regula stabilita in cod nu poate fi niciodata
modificata. Putem folosi asertiunile ca pe o masura de siguranta, pentru a confirma
presupunerile despre comportamentul unui program.
Exista doua forme de asertiuni:
- simpla, ce poate fi exprimata ca: assert Expresie; unde Expresie este o expresie
logica. Cand asertiunile sunt activate, sistemul ruleaza asertiunile si evalueaza
expresia din ele. Daca aceasta are valoarea false atunci se va arunca o
AssertionError, fara niciun mesaj
- complexa, ce poate fi exprimata ca: assert Expresie1:Expresie2;. Aceasta se
foloseste pentru a furniza un mesaj complex in caz de eroare. Expresie1 este
booleana si va fi evaluata. In caz de eroare valoarea lui Expresie2 va fi trimisa
constructorului lui AssertionError si un mesaj de eroare detaliat va fi afisat.
AssertionError este o eroare ce opreste executia programului si reprezinta o
subclasa a java.lang.Error. Aceasta eroare nu poate fi prinsa.
Nu vom folosi asertiunile pentru:
- verificarea argumentelor metodelor: metodele publice au un contract clar definit cu
utilizatorii, care nu depinde de faptul ca asertiunile sunt activate. In locul asertiunile
pentru argumente ilegale se poate folosi IllegalArgumentException.
- efectuarea functiilor critice cerute de aplicatie. Aceasta problema apare atunci cand
asertiunile sunt dezactivate
- producerea efectelor secundare. Acesta problema apare atunci cand asertiunile sunt
dezactivate
Urmatorul exemplu ilustreaza de ce nu putem folosi asertiunile pentru executarea operatiilor
critice
assert carNames.remove(null);
In cazul in care asertiunile sunt dezactivate metoda remove() nu se va executa, ceea ce ar
produce rezultate inconsitente. Solutia de rezolvare consistenta a situatiei este urmatoarea:
boolean nullsRemoved = carNames.remove( null );
assert nullsRemoved;
Asertiunile pot fi folosite pentru testarea:
- invariantilor interni: un invariant intern este o conditie ce trebuie sa fie true tot
timpul. Asertiunile se folosesc ori de cate ori facem o presupunem asupra unui
invariant. O folosim in if-uri multiple, pe ultima ramura else sau in switch pe
ramura default. Spre exemplu:
if (j % 5 == 0) {
...
} else if (j % 5 == 1) {
...
} else {
assert j % 5 == 2 : "presupunerea este incorecta";
....
}
- invariantilor de control ai fluxului: pentru aceasta plasam asertiunea intr-o locatie de
cod in care presupunem ca nu putem ajunge. Spre exemplu:
void metoda() {
for (...) {
if (...)
return;
}
assert false; // acest punct al executiei nu ar trebui atins
}
De obicei se foloseste assert false;
- Preconditiilor, postconditiilor si invariantilor clasei. O preconditie este o conditie pe
care o presupunem adevarata la inceputul metodei. Putem testa presupunerea intr-o
metoda nepublica, la inceputul acesteia
private void metoda(int param) {
assert ((param >= 50) && (param <= 500)) : param;
...
}
Putem folosi asertiunile pentru a testa postconditiile atat in metode publice cat si
nepublice. Putem testa o post conditie chiar inainte de instructiunea return. Pentru
aceasta insa trebuie sa ne asiguram ca exista o singura iesire din metoda. Un invariant
de clasa este o conditie ce trebuie sa fie adevarata pentru fiecare instanta a clasei.
Presupunem ca avem o metoda booleana privata numita IsCorrect() care trebuie sa
returneze true inainte si dupa ce orice metoda s-a incheiat. In cod adaugam o
asertiune inainte de instructiunea return pentru fiecare metoda publica si constructor.
In acest moment testul devine un invariant.
Folosim comenzi in linia de comanda pentru a activa asertiunile. Putem activa asertiunile
pentru o aplicatie, un pachet sau o clasa. Sintaxa generala este:
java [-enableassertions | -ea] [:package name"..." | :class name]
Comanda urmatoare activeaza asertiunile aplicatiei Application:
java –ea Application
Pentru o clasa folosim comanda:
java –ea:com.vehicle.motorcar.YourClass
iar pentru un pachet:
java –ea:com.vehicle.motorcar…
cele trei puncte din finalul comenzii precizeaza ca asertiunile sunt active pentru pachetul
motorcar si pentru toate subpachetele sale.
Unele clase nu sunt incarcate de class loader ci de JVM. De aceea folosim in completarea
comenzii de activare a asertiunilor clauza -eas sau –enablesystemassertions. Spre
exemplu:
java –esa –ea:com.vehicle.motorcar.YourClass
Dezactivarea asertiunilor se face folosind clauza –da sau –disableassertions.
Cuvantul rezervat assertion nu poate fi folosit cu versiunile mai vechi ale Javei. Compilarea
cu versiunea 1.3 va genera o eroare sau un avertisment. In rest compilarea se face cu: javac
–source 1.x fisier, unde x este 4, 5 sau 6.
Vom exemplifica utilizarea asertiunilor folosind frameworkul JUnit 4 din Eclipse. Vom crea
mai intai o clasa a carei metoda dorim sa o testam.
public class Asertiuni {

private final String[] ZILELE_SAPTAMANII = {"Luni", "Marti",


"Miercuri", "Joi", "Vineri", "Sambata", "Duminica"} ;

public String numeleZilei (int zi) {


assert ((zi >= 1) & (zi <= 7)) : "In afara domeniului:" + zi ;
return ZILELE_SAPTAMANII[zi-1] ;
}
}
Metoda numeleZilei() contine o asertiune ce verifica domeniul indicelui. Asertiunea va
trimite si un mesaj in caz de eroare customizat. Vom crea o unitate de testare folosind JUnit
Test Case-ul Eclipse, numit Test1, in pachetul Testare, avand Class under test clasa
anterioara. Putem selecta, in asistent, metodele pe care dorim sa le supunem testarii.
import static org.junit.Assert.*;

import org.junit.Test;

public class Test1 {

@Test
public final void testMain() {
Asertiuni a=new Asertiuni();
a.numeleZilei(8);
}

}
Observatie: daca folosim asertiuni (ca in cazul exemplului nostru) din
Window>Preferences>Java>JUnit selectam checkbox-ul Add ‘-ea’….
Rulam apoi aplicatia ca pe un JunitTest. Un API complet gasim in org.junit.Assert.

JUnit (more) in Detail


Static imports with Eclipse

JUnit uses a lot of static methods and Eclipse cannot automatically import static imports. You
can make the JUnit test methods available via the content assists.

Open the Preferences via Window -> Preferences and select Java > Editor > Content Assist >
Favorites. Add then via "New Member" the methods you need. For example this makes the
assertTrue, assertFalse and assertEquals method available.

You can now use Content Assist (Ctrl+Space) to add the method and the import.

I suggest to add at least the following new members.

· org.junit.Assert.assertTrue
· org.junit.Assert.assertFalse
· org.junit.Assert.assertEquals
· org.junit.Assert.fail

Annotations

The following give an overview of the available annotations in JUnit 4.x

Table 1. Annotations

Annotation Description
Annotation @Test identifies that this
@Test public void method()
method is a test method.
Will perform the method() before each
test. This method can prepare the test
@Before public void method()
environment, e.g. read input data,
initialize the class)
Annotation Description
@After public void method() Test method must start with test
Will perform the method before the start
of all tests. This can be used to perform
@BeforeClass public void method()
time intensive activities for example be
used to connect to a database
Will perform the method after all tests
have finished. This can be used to
@AfterClass public void method()
perform clean-up activities for example be
used to disconnect to a database
Will ignore the test method, e.g. useful if
the underlying code has been changed and
@Ignore the test has not yet been adapted or if the
runtime of this test is just to long to be
included.
Tests if the method throws the named
@Test(expected=IllegalArgumentException.class)
exception
Fails if the method takes longer then 100
@Test(timeout=100)
milliseconds

Assert statements

The following gives an overview of the available test methods:

Table 2. Test methods

Statement Description
Let the method fail, might be usable to check that a
fail(String)
certain part of the code is not reached.
assertTrue(true); True
assertsEquals([String message], Test if the values are the same. Note: for arrays the
expected, actual) reference is checked not the content of the arrays
assertsEquals([String message], Usage for float and double; the tolerance are the
expected, actual, tolerance) number of decimals which must be the same
assertNull([message], object) Checks if the object is null
assertNotNull([message], object) Check if the object is not null
assertSame([String], expected, actual) Check if both variables refer to the same object
assertNotSame([String], expected, Check that both variables refer not to the same
actual) object
assertTrue([message], boolean
Check if the boolean condition is true.
condition)

Proprietati
Clasa java.util.Properties este utilizata pentru a incarca si salva perechi cheie-valoare,
care sunt stocate in fisiere text si au de obicei extensia .properties. Proprietatile sunt
folosite pentru a seta valorile predefinite ale aplicatiei si sunt in mod obisnuit citite la pornirea
acesteia. O alta aplicabilitate este aceea a internationalizarii unei aplicatii.

public class PropertiesExample {


public static void main(String[] args) {

Properties myProps = new Properties();


try {
FileInputStream fis = new
FileInputStream("ServerInfo.properties");
myProps.load(fis);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}

// Print Values
System.out.println("Server: " + myProps.getProperty("hostName"));
System.out.println("User: " + myProps.getProperty("userName"));
System.out.println("Password: " + myProps.getProperty("password"));

}
}

Iar fisierul de proprietati va avea urmatorul continut:


# ServerInfo Properties
hostName = www.example.com
userName = user
password = pass

Clasa System
Pune la dispozitie urmatoarele facilitati:
- Gestioneaza fluxurile de intrare, de iesire si de eroare
- Returneaza consola asociata JVM curenta
- Incarca fisiere si librarii
- Acceseaza proprietati definite extern
- Copiaza portiuni din siruri
Toti membrii sunt statici. Constructorul este privat.
Putem folosi urmatoarele metode ale clasei pentru a accesa si modifica proprietatile sistem:
- public static String getProperty(String), returneza valoarea unei proprietati
sistem, identificata prin parametru. Exemplu, afisarea class path-ul Javei:
public static void main(String[] args){
System.out.println(System.getProperty("java.class.path"));
}
Metoda arunca NullPointerException daca parametrul are valoarea null. Putem
folosi varianta supraincarcata, cu doi parametri, pentru a defini valoarea returnata daca
proprietatea sistem este invalida, ca al doilea argument
- public static Properties getProperties(), returneza toate proprietatile sistem
simultan. Obiectul Properties returnat contine o lista a proprietatilor si a valorilor
lor corespunzatoare. Acest obiect trebuie transformat apoi intr-o multime de
proprietati sistem ale mediului curent de rulare. Daca sistemul nu gaseste aceasta
multime de proprietati el initializeaza o multime predefinita. Exemplu:
public static void main(String[] args){
System.getProperties().list(System.out);
}
Argumentul lui list() este System.out ceea ce determina listarea continutului
obiectului Properties prin fluxul predefinit de iesire. Metoda poate arunca o
SecurityException daca metoda checkPropertiesAccess() din SystemManager
nu permite accesul la proprietatile sistemului.
- public static String setProperty(String, String), modifica valoarea unei
proprietati. Primul argument este numele proprietatii iar al doilea este noua valoarea.
Metoda poate arunca o SecurityException daca metoda
checkPropertiesAccess() din SecurityManager nu permite accesul la proprietatile
sistemului sau o NullPointerException daca numele proprietatii nu este precizat.
- public static void setProperties(Properties), modifica proprietati aplicatiei
curente prin obiectul argument. Exemplu, setarea proprietatilor sistemului:
public static void main(String[] args){
Properties p = new Properties(System.getProperties());
p.load("UserProperties.txt");
System.setProperties(p);
}
In exemplul anterior am creat un obiect caruia i-am atribuit proprietatile sistemului
apeland metoda load() din clasa Properties, ce are ca argument numele unui fisier
text ce contine noile proprietati, UserProperties.txt. Metoda poate arunca o
SecurityException daca metoda checkPropertiesAccess() din SystemManager
nu permite accesul la proprietatile sistemului
- public static String getenv(String) si
public static Map<String, String> getenv() pentru a gestiona proprietatile de
mediu. Proprietatile de mediu definesc procesele globale in timp ce proprietatile
sistem definesc subprocesele Java. Prima versiune este folosita atunci cand se
specifica numele proprietatii, ca parametru, iar a doua pentru a returna o mapare
nemodificabila a intregului mediu incluzand valorile si cheile nule. Perechile returnate
sunt de forma nume=valoare. Exemplu:
public static void main(String[] args){
System.out.println("proprietatile de mediu sunt:");
System.out.println(java.lang.System.getenv());
}
Cand pornim platforma Java clasa System intializeaza proprietatile ce contin date despre
mediul de dezvoltare. Dam in continuare un tabel cu unele dintre proprietatile sistem:
Clasa java.lang.System gestioneaza fluxurile de intrare iesire ce cuprind:
- intrarile standard (System.in). Metoda System.in.read() citeste intrarile de la
consola, ca intrare, sub forma de text. System.in este referinta pentru un obiect
InputStream
- Iesirile standard (System.out) ce afiseaza datele de iesire, in general la monitor sau
sunt expediate catre imprimanta. System.out este referinta pentru un obiect
PrintStream
- Erorile standard (System.err) ce afiseaza mesajele de eroare. System.err este
referinta pentru un obiect PrintStream
Toate aceste fluxuri se afla in pachetul java.io. Este de evitat reasignarea fluxurilor standard
de iesire, dar putem redirectiona erorile catre un fisier de iesire folosind functia setErr(), ce
are ca parametru un obiect de tip PrintStream.
Unele dispozitive nu au iesire standard si de aceea trebuie sa trimitem obligatoriu iesirea catre
un fisier. Aceasta se face folosind functia setOut(), ce are ca parametru un obiect de tip
PrintStream.
Daca dorim sa testam aplicatia folosind diferite multimi de intrare, memorate intr-un fisier va
trebui sa cream un nou flux de intrare care sa accepte intrari din fisier in loc de cele de la
tastatura. Aceasta se face folosind functia setIn() ce are ca parametru un obiect
InputStream. Daca security manager este instalat acesta poate verifica daca avem
autorizarea de a reasigna fluxurile standard de intrare/iesire.
Clasa System contine urmatoarele metode folositoare:
- public static void arraycopy(Object src, int src_pos, Object dst, int
dst_pos, int length)copiaza o portiune dintr-un array intr-un altul
- public static long currentTimeMillis(), returneaza timpul scurs in
milisecunde de la 1 ianuarie 1970, ora 00.00.00.
- public static void exit(int status), folosita pentru a termina programat toate
firele si incheia executia interpretorului
- public static void gc(), folosita pentru a cere JVM sa faca o operatie de garbage
collector
- public static long nanoTime(), folosita pentru a returna valoarea timpului
exprimat in nanosecunde a cronometrului sistem. Este folosit in special pentru a
calcula timpul scurs intre doua puncte ale unui program, puncte identificate prin doua
apeluri ale metodei. Exemplu:
public static void main(String[] args){
long startTime = System.nanoTime () ;
long interval = System.nanoTime () - startTime ;
System.out.println (interval) ;
}
- public static Console console(), introdusa in 6.0 pentru a returna obiectul
consola si a accesa dispozitivul asociat JVM-ii curente, in mod caracter. De obicei,
cand un program Java este lansat, in mod automat nu avem disponibila o consola si de
aceea metoda va returna NULL. Obiectul Console detine majoritatea functionalitatilor
fluxului standard, dar si functionalitati noi cum ar fi introducerea securizata a parolei.
Exemplu:
public static void main(String[] args){
Console thisConsole = System.console();

if (thisConsole != null) {
thisConsole.writer().print("introduceti parola");
thisConsole.flush();
char[] password = thisConsole.readPassword();

}
}
In exemplul anterior metoda readPassword() citeste parola fara ecou si o returneaza ca pe
un sir de caractere. Exemplul va returna o consola nenula doar in linia de comanda.

Clasa PrintWriter
Scrie caractere in loc de octeti. Clasa implementeaza toate metodele de printare existente in
PrintStream. Clasa PrintStream converteste caracterele in octeti folosind codarea de
caractere predefinita a platformei. Spre deosebire de PrintStream, daca flush-ul automat este
activ, el va fi facut doar cand una dintre metodele println(), printf() sau format() este
invocata, in loc de a fi facut ori de cate ori caracterul linie noua este inclus la iesire.
public class PrintWriterExample {
public static void main(String[] args) {
PrintWriter pw = new PrintWriter(System.out, true);
pw.println("This is some output.");
}
}
In exemplul anterior am creat un obiect ce foloseste optiunea de auto flush. Valoarea true va
forta PrintWriter-ul sa faca flush dupa fiecare linie afisata la consola.
Java furnizeaza mai multe metode de a formata string-uri: metodele printf() si
String.format().
public class PrintfExample {
public static void main(String[] args){
PrintWriter pw = new PrintWriter(System.out, true);
double price = 24.99; int quantity = 2; String color = "Blue";
System.out.printf("We have %03d %s Polo shirts that cost $%3.2f.\n",
quantity, color, price);
System.out.format("We have %03d %s Polo shirts that cost $%3.2f.\n",
quantity, color, price);
String out = String.format("We have %03d %s Polo shirts that cost
$%3.2f.", quantity, color, price);
System.out.println(out);
pw.printf("We have %03d %s Polo shirts that cost $%3.2f.\n", quantity,
color, price);
}
}

Clasa String
Java utilizeaza caractere Unicode pe 16 biti pentru a codifica datele unui string. Aceasta
tehnica permite Javei sa codifice pina la 65536 de caractere. In Java stringurile sunt
incapsulate prin clasele String si StringBuffer. Clasa String extinde Object si
implementeaza interfetele Serializable, Comparable si CharSequence. Obiectele clasei
String nu sunt obligatoriu a fi create prin operatorul new, ci doar printr-o simpla atribuire a
unei valori literale. Exista mai multi constructori asociati clasei String ce ne permit sa cream
instante pentru:
- un sir de bytes, adica un string codificat UTF-8. Intr-un caracter unicode doar un byte
reprezinta caracterul, celalalt este un cod de limba. ( public String(byte[])).
Exemplu:
byte[] encodedBytes = new byte[] {111, 99, 122, 123, 96, 117} ;
String s = new String(encodedBytes) ;
System.out.println(s);
- o submultime a unui sir de bytes (public String(byte[],int indexInceput,int
lung)). Exemplu:
byte[] encodedBytes = new byte[] {111, 99, 122, 123, 96, 117} ;
String s = new String(encodedBytes, 2, 3) ;
System.out.println(s);
- un sir de caractere (public String(char[])). Exemplu:
char[] chars = new char[] {'a', 'r', 'r', 'a', 'y', 's'} ;
String s = new String(chars) ;
System.out.println(s);
- o submultime a unui sir de caractere ( public String(char[],int
indexInceput,int lung))
- un alt string (public String(String))
- un string buffer (public String(StringBuffer))
Unii constructori ne permit sa folosim un string, ca al doilea parametru, pentru a specifica o
codificare specifica a caracterelor. Acest constructor este folosit in special atunci cand mapam
o multime de bytes unui multimi speciale de caractere. Avem mai multe codificari. Printre
acestea: UTF-8 pentru codificarea Unicode, EUC (Extended Unix Code) codifica caracterele
in diverse limbi asiatice, US-ASCII, ISO-8859-1, UTF-16, etc. Sintaxa acestui constructor
este: String (byte[], String charset); Exemplu:
byte[] encodedBytes = new byte[] {15, 22, 22, 53, 96, 17} ;
String s;
try {
s = new String(encodedBytes, "UTF-8");
System.out.println(s);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Odata creat obiectul String acesta nu mai poate fi modificat. Cand asignam o noua valoare
obiectului, practic, cream un nou string.
Orice aplicatie are un pool de stringuri. In momentul compilarii compilatorul compara toate
stringurile intalnite cu cele deja existente in pool si le adauga pe cele inexistente. Daca
stringul exista, compilatorul economiseste memoria si utilizeaza o copie din pool.
Clasa StringBuffer permite modificarea stringului valoare.
Clasa String contine mai multe metode pentru compararea stringurilor: equals() (compara
doua stringuri caracter cu caracter) , equalsIgnoreCase() si contentEquals() (pentru
compararea obiectului curent String cu un obiect StringBuffer). Putem folosi in loc de
equals si operatorul ==, care poate da rezultate bune din cauza poolului de stringuri, dar acest
lucru nu este garantat.
Alte metode ale clasei String:
- int length(): returneaza numarul de caractere ale stringului curent
- boolean startsWith(String): returneaza true daca stringul curent incepe cu
stringul parametru
- boolean endsWith(String): returneaza true daca stringul curent se termina cu
stringul parametru
- String trim(): returneaza un string fara spatiile de inceput si sfarsit ale stringului
curent si fara caracterele de control ASCII
- String substring(int inceput, int sfarsit): returneaza un substring al
stringului cuprins intre indexul inceput si pina la indexul sfarsit
- String toUpperCase(): returneaza un string cu toate literele mari ale stringului
curent
- String toLowerCase(): returneaza un string cu toate literele mici ale stringului
curent
- boolean isEmpty(): returneaza true daca stringul curent este gol sau are lungime 0

Utilizarea expresiilor regulate cu stringuri


Pentru a verifica ca o secventa de caractere se potriveste unui anume sablon folosim expresii
regulate. In Java crearea si utilizarea expresiilor regulate sunt gestionate de clasele Pattern si
Matcher din pachetul java.util.regex. Pentru a utiliza o expresie regulata trebuie:
- sa o definim
- sa o aplicam unei secvente de caractere
Tabelul de mai jos prezinta o lista a simbolurilor folosite in crearea expresiilor regulate.

In plus, [^abc] este utilizata pentru a specifica orice caracter care nu este a, b sau c, iar |
specifica alternarea.
\D este folosita ca o clasa negare si reprezinta [^0-9].
\W este folosita ca o clasa negare si reprezinta [^a-zA-Z0-9_].
\S este folosita ca o clasa negare si reprezinta [^ \r\t\n\f\0XB].
Caracterele albe sunt: \t – tab, \n – linie noua, \r – retur de car, \f – form feed, \0XB – tab
vertical.
Clasa Pattern este folosita pentru a defini o expresie regulata prin metoda
Pattern.compile() ce returneaza un obiect Pattern. Exemplu:
public static void main(String args[]) {
Pattern sablon = Pattern.compile("[0-9][0-9][a-z]");
Matcher potrivire = sablon.matcher("lmn89t");
boolean potrivireGasita = potrivire.matches();
if (potrivireGasita)
System.out.println("se potriveste!") ;
else
System.out.println("nu se potriveste!") ;

}
In exemplul anterior obiectul sablon defineste o expresie regulata formata din doua cifre
urmate de o litera. Apoi cream un obiect Matcher pentru stringul in care dorim sa facem
cautarea. Cautarea se face prin apelul metodei matcher() a clasei Pattern. Pentru a gasi in
stringul de cautat o potrivire cu expresia regulata apelam metoda booleana matches() a
clasei Matcher.
Alt exemplu:
public class CustomCharClassExample {
public static void main(String[] args) {
String t = "It was the best of times";

Pattern p1 = Pattern.compile("w.s");
Matcher m1 = p1.matcher(t);
if (m1.find()) {
System.out.println("Found: " + m1.group());
}

Pattern p2 = Pattern.compile("w[abc]s");
Matcher m2 = p2.matcher(t);
if (m2.find()) {
System.out.println("Found: " + m2.group());
}

Pattern p3 = Pattern.compile("t[^eou]mes");
Matcher m3 = p3.matcher(t);
if (m3.find()) {
System.out.println("Found: " + m3.group());
}
}
}
Am creat un obiect Pattern pentru a stoca expresia regulata dupa care dorim sa facem
cautarea, am creat obiectul Matcher prin trimiterea textului in care se face cautarea, metodei
din obiectul Pattern anterior creat (aceasta metoda returneaza un Matcher).
Matcher.group() returneaza caracterele ce se potrivesc sablonului.
Unele dintre clasele caracter se folosesc repetat.
public class PredefinedCharClassExample {
public static void main(String[] args){
String t = "Jo told me 20 ways to San Jose in 15 minutes.";
System.out.println(t);

Pattern p1 = Pattern.compile("\\d\\d");
Matcher m1 = p1.matcher(t);
while (m1.find()){
System.out.println("Found: " + m1.group());
}

Pattern p2 = Pattern.compile("\\sin\\s");
Matcher m2 = p2.matcher(t);
if (m2.find()) System.out.println("Found: " + m2.group());

Pattern p3 = Pattern.compile("\\Sin\\S");
Matcher m3 = p3.matcher(t);
if (m3.find()) System.out.println("Found: " + m3.group());
}
}
In exemplu: \\d\\d gaseste oricare doua cifre, \\sin\\s gaseste in inclus intre doua spatii,
\\Sin\\S gaseste in inclus intre doua caractere non-spatiu.
Cuantificatorii permit selectarea usoara a unui domeniu de caractere:
- {n}, caracterul anterior apare de exact n ori
- {m,n}, caracterul anterior apare de m pina la n ori
- {m,}, caracterul anterior apare de cel putin m ori
- (xx){n}, grupul de caractere apare repetat de n ori
public class QuantifierExample {
public static void main(String[] args){
String t = "Longlonglong ago, in a galaxy far far away.";

Pattern p1 = Pattern.compile("ago.*");
Matcher m1 = p1.matcher(t);
if (m1.find()) System.out.println("Found: " + m1.group());

Pattern p2 = Pattern.compile("gal.{3}");
Matcher m2 = p2.matcher(t);
if (m2.find()) System.out.println("Found: " + m2.group());

Pattern p3 = Pattern.compile("(long){2}");
Matcher m3 = p3.matcher(t);
if (m3.find()) System.out.println("Found: " + m3.group());
}
}
In exemplu:
- ago.*, gaseste ago si 0 sau mai multe caractere ramase pe linie
- gal.{3}, gaseste gal si urmatoarele trei caractere
- (long){2}, gaseste long repetat de doua ori
O expresie regulata incearca de fiecare data sa aduca cat mai multe caractere cu putinta.
Acesta este numit principiul „greediness”. Utilizam ? pentru a limita cautarea la cea mai
scurta potrivire posibila.
public class GreedinessExample {
public static void main(String[] args){
String t = "Longlonglong ago, in a galaxy far far away.";

Pattern p1 = Pattern.compile("ago.*far");
Matcher m1 = p1.matcher(t);
if (m1.find()) System.out.println("Found: " + m1.group());
// Produces: ago, in a galaxy far far

Pattern p2 = Pattern.compile("ago.*?far");
Matcher m2 = p2.matcher(t);
if (m2.find()) System.out.println("Found: " + m2.group());
// Produces: ago, in a galaxy far
}
}
In exemplu:
- ago.*far, ilustreaza principiul greediness
- ago.*?far, ilustreaza inhibarea principiului greediness
Caracterele boundary pot fi utilizate pentru a potrivi diferite parti ale unei linii:
- ^, potriveste inceputul liniei
- $, potriveste sfarsitul liniei
- \b, potriveste inceputul sau sfarsitul unui cuvant
- \B, nu potriveste inceputul sau sfarsitul unui cuvant
public class BoundaryCharExample {
public static void main(String[] args){
String t = "it was the best of times or it was the worst of times";

Pattern p1 = Pattern.compile("^it.*?times");
Matcher m1 = p1.matcher(t);
if (m1.find()) System.out.println("Found: " + m1.group());

Pattern p2 = Pattern.compile("\\sit.*times$");
Matcher m2 = p2.matcher(t);
if (m2.find()) System.out.println("Found: " + m2.group());

Pattern p3 = Pattern.compile("\\bor\\b.{3}");
Matcher m3 = p3.matcher(t);
if (m3.find()) System.out.println("Found: " + m3.group());
}
}
Cu expresii regulate putem folosi parantezele pentru a identifica parti ale unui string pentru
potriviri.
public class MatchingExample {
public static void main(String[] args){
String email = "george.washington@example.com";

Pattern p1 = Pattern.compile("(\\S+?)\\.(\\S+?)\\@(\\S+)");
Matcher m1 = p1.matcher(email);
if (m1.find()){
System.out.println("First: " + m1.group(1));
System.out.println("Last: " + m1.group(2));
System.out.println("Domain: " + m1.group(3));
System.out.println("Everything Matched: " + m1.group(0));
}
}
}
In exemplu potrivim partile componente ale unei adrese de email. Fiecare grup de paranteze
este numerotat. Intr-o expresie regulata group(0) sau group() potriveste tot textul.
In exemplu:
- ^it.*?times, reprezinta secventa ce incepe o linie cu it urmata de niste caractere si
times, greediness fiind dezactivat
- \\sit.*times$, reprezinta secventa ce incepe cu it urmata de niste caractere si
times
- \\bor\\b.{3}, cauta or incercuit decuvinte si urmatoarele trei caractere
Clasa String are cateva expresii ce suporta expresii regulate:
- boolean matches(String): descrisa anterior
- String replaceFirst(String exReg, String inlocuitor): returneaza stringul
obtinut prin inlocuirea in stringul curent a primei aparitii a stringului exReg ce se
potriveste cu inlocuitor
- String replaceAll(String exReg, String inlocuitor): returneaza stringul
obtinut prin inlocuirea in stringul curent a tuturor aparitiilor ale stringului exReg ce se
potrivesc cu inlocuitor
public class ReplacingExample {
public static void main(String[] args){
String header = "<h1>This is an H1</h1>";

Pattern p1 = Pattern.compile("h1");
Matcher m1 = p1.matcher(header);
if (m1.find()){
header = m1.replaceAll("p");
System.out.println(header);
}
}
}
- String[] split(String): returneaza un sir de stringuri, obtinut din obiectul curent
prin folosirea parametrului pe post de separator
public class StringSplit {
public static void main(String[] args){
String shirts = "Blue Shirt, Red Shirt, Black Shirt, Maroon Shirt";

String[] results = shirts.split(", ");


for(String shirtStr:results){
System.out.println(shirtStr);
}
}
}
Clasa StringTokenizer face acelasi lucru ca split() dar intr-un mod diferit. Trebuie sa
iteram tokenii pentru a avea acces la ei.
Clasa Scanner poate imparti un string sau un stream in token-i. In plus un Scanner poate fi
utilizat pentru a tokeniza numere sau a le converti la tipuri primitive.

Clasele StringBuffer si StringBuilder


Aceste clase sunt preferate a fi folosite atunci cand dorim sa concatenam string-uri. Ele sunt
mult mai eficiente decat folosirea operatorului +. Din punct de vedere al concurentei
StringBuilder nu este thread safe in timp ce StringBuffer este. Ca indicatie de
performanta este recomandarea ca sa setam capacitatea initiala la nevoile pe care le avem.
Orice redimensionare conduce la scaderea performantei.
public class StringBuilding {
public static void main(String[] args){
StringBuilder sb = new StringBuilder(500);

sb.append(", the lightning flashed and the thunder rumbled.\n");


sb.insert(0, "It was a dark and stormy night");

sb.append("The lightning struck...\n").append("[ ");


for(int i = 1; i < 11; i++){
sb.append(i).append(" ");
}
sb.append("] times");

System.out.println(sb.toString());
}
}

Folosim StringBuffer-ul cand dorim sa modificam un string, dinamic, prin cod. Clasa are
urmatorii patru constructori:
- StringBuffer() construieste un string gol
- StringBuffer(int) construieste un string gol de capacitate data de parametru. Daca
bufferul depaseste capacitatea aceasta este automata marita cu valoarea parametrului
- StringBuffer(String) construieste un string initializat cu parametrul
- StringBuffer(CharSequence) construieste un string initializat cu parametrul
Metode uzuale din aceasta clasa:
- void setLength(int): modifica lungimea bufferului. Caracterele ramin aceleasi,
cele care sunt eventual in plus vor fi caractere null
- void setCharAt(int,char): modifica caracterul de la pozitia indicata de primul
parametru
- StringBuffer reverse(): inverseaza ordinea caracterelor din buffer
- StringBuffer append(String): adauga stringul parametrului, bufferului
- StringBuffer insert(int, String): insereaza in buffer stringul parametru
incepand de pe pozitia indicata de primul parametru al functiei
Exemplu de folosire a functiilor clasei StringBuffer:
public class StringBufferDemo {
static void printBuffer( StringBuffer buf ) {
System.out.println( "buf = " + buf );
System.out.println( "length = " + buf.length() );
System.out.println( "capacity = " + buf.capacity() );
}
public static void main( String [] args ) {
StringBuffer buf = new StringBuffer( "This is a
StringBuffer." );
printBuffer( buf ); // prints "This is a StringBuffer.",
// length = 23, capacity = 39
buf.setLength ( 7 );
printBuffer( buf ); // prints "This is",
// length = 7, capacity = 39
StringBuffer buf2 = new StringBuffer( 10 );
printBuffer( buf2 ); // prints "",
// length = 0, capacity = 10
buf2.append( "Write once, run anywhere.") ;
printBuffer( buf2 ); // prints "Write once, run anywhere."
// length = 25, capacity = 25
buf2.setCharAt( 10, '.' );
buf2.setCharAt( 12, 'R' );
printBuffer( buf2 ); // prints "Write once. Run anywhere."
// length = 25, capacity = 25
StringBuffer buf3 = new StringBuffer();
buf3 = buf2.insert( 12, "Distribute. " );
printBuffer( buf3 ); // prints "Write once. Distribute. Run
anywhere."
// length = 37, capacity = 41
buf3 = buf3.append( " Hooray!" );
printBuffer( buf3 ); // prints "Write once. Distribute. Run
anywhere. Hooray!"
// length = 45, capacity = 57
CharSequence charSeq = "Java" ;
StringBuffer buf4 = new StringBuffer( charSeq );
printBuffer( buf4 ); // prints "Java"
// length = 4, capacity = 20
buf4.reverse();
printBuffer( buf4 ); // prints "avaJ"
// length = 4, capacity = 20
}
}
StringBuffer a fost creata pentru a fi sigura in firele de executie. StringBuilder este
desemnata sa inlocuiasca StringBuffer, acolo unde bufferul este utilizat de un singur fir.
TEMA:
1. se da o lista de tricouri, caracteristicile unui tricou fiind separate prin virgula. Sa se
afiseze lista de tricouri astfel:
2. fie urmatorul fisier text:

<html>
<title>Gettysburg Address</title>
<style type="text/css">
body {
font-family: sans-serif;
}

h2,h4 {
text-align: center;
}
</style>
<body>
<h2>Gettysburg Address</h2>
<h4>Abraham Lincoln</h4>
<h4>Thursday, November 19, 1863</h4>

<div class="paragraph">
<p class="line">Four score and seven years ago our fathers brought
forth on this continent a new nation, conceived in liberty,
and
dedicated to the proposition that all men are created
equal.</p>
<p class="line">Now we are engaged in a great civil war, testing
whether that nation, or any nation, so conceived and so
dedicated,
can long endure.</p>
<p class="line">We are met on a great battle-field of that war.</p>
<p class="line">We have come to dedicate a portion of that field,
as a final resting place for those who here gave their lives
that
that nation might live.</p>
<p class="line">It is altogether fitting and proper that we should
do this.</p>
</div>

<div class="paragraph">
<p class="line">But, in a larger sense, we can not dedicate, we
can not consecrate, we can not hallow this ground.</p>
<p class="line">The brave men, living and dead, who struggled
here, have consecrated it, far above our poor power to add or
detract.</p>
<p class="line">The world will little note, nor long remember what
we say here, but it can never forget what they did here.</p>
<p class="line">It is for us the living, rather, to be dedicated
here to the unfinished work which they who fought here have
thus far
so nobly advanced.</p>
<p class="line">It is rather for us to be here dedicated to the
great task remaining before uses that from these honored dead
we
take increased devotion to that cause for which they gave the
last
full measure of devotion that we here highly resolve that
these
dead shall not have died in vain that this nation, under God,
shall have a new birth of freedom and that government of the
people, by the people, for the people, shall not perish from
the
earth.</p>
</div>
</body>
</html>
Scrieti o aplicatie ce cauta in fisierul anterior un text folosind expresii regulate. Daca
textul este gasit sa se afiseze numarul liniei si linia intreaga in care se afla textul cautat. (Spre
exemplu: toate liniile ce contin <h4>, toate liniile ce contin cuvantul to, toate liniile ce incep
cu 4 spatii, toate liniile ce contin tag-uri de inchidere HTML)
3. in exemplul anterior inlocuim tag-urile <p> prin <span>. Mai mult, valoarea
atributului class va fi sentence in loc de line. Folositi expresii regulate pentru a gasi
liniile in care dorim sa facem modificarile. Apoi, folositi expresii regulate pentru a
transforma tag-urile si atributele vizate. Noul fisier va fi afisat la consola.

Autoboxing si Unboxing
Simplifica sintaxa si produc cod mai curat si usor de citit.
Integer intObject = new Integer(1);
int intPrimitive = 2;

Integer tempInteger;
int tempPrimitive;

tempInteger = new Integer(intPrimitive);


tempPrimitive = intObject.intValue();

tempInteger = intPrimitive; // Auto box


tempPrimitive = intObject; // Auto unbox
Autoboxing si unboxing sunt facilitati Java ce permit sa facem atribuiri fara o sintaxa
explicita de cast. Java va face cast-ul la rulare.
Observatie: folosirea autoboxing-ului in cicluri trebuie facuta cu grija pentru ca atrage dupa
sine costuri de performanta.

Colectii Java
In Java colectiile inmagazineaza, grupeaza si sorteaza obiecte. Putem implementa interfete si
extinde clase colectie pentru a defini colectii utilizator. JCF (Java Collection Framework) este
o arhitectura ce pune la dispozitie clase si interfete pentru crearea si manipularea colectiilor.
Asadar, JFC contine:
- interfete, unele interfete sunt goale si functioneaza ca markere
- implementari, ce asigura dezvoltarea colectiilor utilizator
- algoritmi, sunt folositi pentru a rezolva anumite probleme cum ar fi sortarea sau
cautarea intr-o colectie. Algoritmii sunt polimorfici.
O colectie este un obiect destinat sa gestioneze un grup de obiecte:
- un obiect din colectie se numeste element
- primitivele nu sunt permise intr-o colectie
Avantajele folosirii JCF:
- ofera colectii predefinite cum ar fi multimile, tabelele de distributie si hartile
- faciliteaza referirea cu ajutorul interfetelor
- reduce efortul de design si implementare
Avem colectii de diferite tipuri ce implementeaza multe structuri de date comune: stive, cozi,
siruri dinamice, tabele de distributie.

Interfetele colectie formeaza o ierarhie. Radacina este formata din Collection. Elementele
unei Collection pot fi sortate sau nu si pot contine duplicari. Nu exista implementari directe
ale acestei interfete. Toate colectiile pot fi traversate folosind sintaxa foreach. Subinterfete
derivate sunt:
- Set, este o colectie ce nu poate contine duplicari si poate contine cel mult un element
null. Set-ul nu are index.
- o interfata direct derivata din Set este SortedSet in care elementele sunt, in plus,
ordonate natural (dupa codurile caracterelor). TreeSet furnizeaza o implementare
sortata
- direct derivata din SortedSet este interfata NavigableSet, care adauga metode de
navigare bidirectionala (crescator sau descrescator) a multimii si returneaza elemente
ce se potrivesc intr-o oarecare masura unui sablon de cautare. Exemplu de folosire a
lui NavigableSet:
public static void main(String args[]) {
//cream o SortedSet ca instanta a unei clase TreeSet
SortedSet<String> s=new TreeSet<>();
//cream un NavigableSet
NavigableSet<String> n=(NavigableSet<String>)s;
//adaugam elemente
n.add("ion");
n.add("Ion");
n.add("Vasile");
n.add("vasile");
n.add("gelu");

System.out.println("afisare in ordine crescatoare");

Iterator<String> a=n.iterator();
while(a.hasNext()){
System.out.println(a.next());
}

System.out.println("afisare in ordine descrescatoare");


Iterator<String> d=n.descendingIterator();
while(d.hasNext()){
System.out.println(d.next());
}
}
- List, direct derivata din Collection, poate contine elemente duplicat. Elementele
sunt stocate intr-o anumita ordine, data de un index intreg. Elementele pot fi inserate
sau sterse de oriunde din lista. Sunt oferite si metode de cautare. Poate contine mai
multe elemente nule. Listei ii este atasat un iterator. Clasa ArrayList reprezinta un sir
ce creste dinamic daca numarul de elemente depaseste dimensiunea initiala.
- Queue, direct derivata din Collection, contine metode aditionale pentru inserare,
inspectare sau extragere de elemente. Nu este recomandabil sa se insereze elemente
nule
- BlockingQueue, direct derivata din Queue, contine metode aditionale ce ne permit sa
asteptam pina se creaza un spatiu liber in coada inainte de a insera un nou element
- BlockingDeque suporta operatii de blocare, precum asteptarea pentru eliberarea unui
spatiu sau asteptarea ca coada sa devina nevida inainte de a extrage un element. Este
thread safe, iar numarul sau de elemente poate fi limitat. Extinde atat Deque cat si
BlockingQueue.
- Deque (double ended queue). Permite inserarea si stergerea la oricare capat. Interfata
nu furnizeaza metode pentru acces indexat, dar putem utiliza metode ca
removeFirstOccurence() si removeLastOccurence() pentru a extrage elemente
interioare dintr-o coada dubla. Putem sa o folosim pentru a crea structuri LIFO sau
FIFO. Nu este recomandabil sa inseram elemente nule, pentru ca null este folosit
pentru a evidentia ca coada este vida
Avem si alte interfete ce contin colectii aditionale in Collection Framework:
- Map, contine perechi cheie-valoare. Nu poate contine chei duplicat si fiecare cheie
poate contine o singura valoare. Mai este cunoscut sub numele „associative arrays”.
Map nu extinde interfata Collection pentru ca reprezinta mapari si nu colectii de
obiecte. HashTable este o implementare sincronizata a lui Map. HashMap este o alta
implementare a lui Map doar ca accepta chei si valori nule si nu este sincronizata
- SortedMap, extensie a lui Map si retine elementele intr-o ordine ascendenta.
Elementele pot fi sortate folosind un Comparator, pe care il vom defini impreuna cu
colectia. Toate iteratiile si operatiile de view facute pe o mapare sortata sunt returnate
in ordinea cheii. TreeMap este o implementare directa a lui SortedMap.
- ConcurrentMap, extensie directa a lui Map, este o versiune thread safe.
- NavigableMap extinde capabilitatile lui SortedMap prin introducerea metodelor de
navigare si returnare a elementelor ce sunt apropiate de un anumit sablon. Exemplu de
folosire a interfetei:
public static void main(String[] args) {
//cream un SortedMap folosind clasa TreeMap
SortedMap<Double, String> s=new TreeMap<>();
NavigableMap<Double, String> n=(NavigableMap<Double, String>)(s);
//adaugam elemente
n.put(1.0,"Ion");
n.put(2.0,"ion");
n.put(3.0,"vasile");
n.put(4.0,"Vasile");
n.put(5.0,"gicu");
Double test=new Double(9.0);
//lowerKey() este utilizata pentru a afisa cea mai mare cheie
//strict mai mica decat cheia data: test
Double lower_key=(Double)n.lowerKey(test);
System.out.println("cheia mai mica: "+test+" - "+lower_key+" !");
//higherKey() este utilizata pentru a afisa cea mai mica cheie
//strict mai mare decat cheia data: test
Double higher_key=(Double)n.higherKey(test);
System.out.println("cheia mai mare: "+test+" - "+higher_key+" !");
}
Deoarece este bidirectional navigabila, elementele unui SortedMap pot fi accesate in
ordinea crescatoare sau descrescatoare a cheii.
- Interfata ConcurrentNavigableMap extinde atat ConcurrentMap cat si
NavigableMap. Astfel, interfata furnizeaza metode de navigare ce returneaza cea mai
apropiata potrivire a unui sablon dat, precum si traversarea bidirectionala, atat in
ordine crescatoare cat si descrescatoare. Interfata este thread safe.
In 6.0 au fost adaugate urmatoarele clase ce implementeaza unele dintre interfetele anterioare:
- ConcurrentSkipListSet, implementeaza NavigableSet. Elementele sale sunt
sortate dupa ordinea naturala sau printr-un Comparator, furnizat la instantiere. Nu
sunt permise valori null
- ConcurrentSkipListMap, implementeaza ConcurrentNavigableMap si are o
functionalitate asemanatoare cu ConcurrentSkipListSet. Nu sunt permise valori
null pentru chei
- LinkedBlockingDeque, implementeaza BlockingDeque. Are un constructor cu
parametru intreg pentru a specifica capacitatea maxima. Daca nu este specificata
aceasta va fi Integer.MAX_VALUE
- AbstractMap.SimpleEntry este folosita pentru a crea o mapare utilizator, in care un
obiect Entry este utilizat pentru gestiunea maparilor. Metoda setValue() este
folosita pentru modificarea valorilor
- AbstractMap.SimpleImmutableEntry este folosita pentru a crea o mapare utilizator
in care un obiect AbstractMap.SimpleImmutableEntry este utilizat pentru gestiunea
maparilor. Nu suporta metoda setValue(). Este thread safe.
- ArrayDeque este o implementare a lui Deque, este redimensionabila si nu este
sincronizabila, ceea ce inseamna ca mai multe fire nu pot acces sirul in mod
concurent. Nu este thread safe si nu permite adaugarea valorilor null. Deoarece
metodele add(), addFirst(), offerFirst(), offerLast() fac apeluri unchecked
catre un obiect Deque, trebuie sa compilam acest cod cu –Xlint:unchecked option.
Exemplu de folosire a clasei ArrayDeque:
public static void main(String args[]) {
ArrayDeque ad = new ArrayDeque(1);
//inserare folosind metodele unchecked add() si addFirst()

ad.add("Java");
ad.add("VB");
ad.add("C++");
ad.addFirst("Oracle");

//inserare folosind metodele unchecked offerFirst() si


offerLast()
ad.offerFirst("MySql");
ad.offerLast("SQL");

//metode de consultare
System.out.println("primul element este :" + ad.peekFirst());
System.out.println("ultimul element este :"+ ad.peekLast());

//metode de stergere
System.out.println("stergem primul element :"+ ad.pollFirst());
System.out.println("stergem ultimul element :"+ ad.pollLast());
}
JCF este sustinuta de anumite interfete auxiliare ce se gasesc in pachetul java.util. Aceste
interfete ne permit sa manipulam elementele unei colectii. Acestea sunt:
- RandomAccess este o interfata marker folosita de liste pentru a inlesni accesul rapid la
elemente listelor cu acces secvential sau aleator. Implementari ca Vector sau
ArrayList folosesc aceasta interfata
- Comparable ce contine metoda compareTo() si permite doar o optiune de sortare
- Comparator ne asigura ca o colectie este ordonata folosind o operatie de ordonare
utilizator. Interfata are doua metode compare() (returneza un intreg) si equals()
(returneza un boolean). Ne permite definirea mai multor optiuni de sortare. Este
implementata partial de clasa abstracta Collator.
- Iterator permite parcurgerea inainte intr-o colectie. Are metodele next(),
hasNext() si remove(). Iteratorul unifica accesul la colectii
- ListIterator este o extensie a Iterator si permite navigarea in ambele directii. Are
metodele add(), remove() si set() pentru manipulare, next() si previous() pentru
parcurgere, nextIndex() si previousIndex() ce returneza indexul si hasNext() si
hasPrevious() pentru a verifica daca mai exista un element dupa respectiv inaintea
elemetului specificat
Interfata List este implementata de clasele ArrayList, LinkedList si Vector. Metodele
interfetei sunt:
- add(element), add(index,element) adauga elementul specificat la sfarsitul listei
sau la un index specificat.
- remove(element), remove(index,element) sterge prima aparitie a elementului
specificat sau de la un index specificat. Stergerea determina mutarea spre stanga a
elementelor listei
- size()returneaza numarul elemetelor din lista.
- contains(element)returneaza true daca lista contine un anumit element.
- set(index,element)seteaza valoarea unui element de pe o anumita pozitie.
In cele ce urmeaza prezentam un cod ce ilustreaza principalele operatii dintr-o lista:
public class WordQuery {

static BufferedReader query = new BufferedReader(new InputStreamReader(


System.in));

static String[] nume = new String[] { "Ion", "Vasile", "Gicu", "Nelu",


"Elvis", "Poasca" };

// definim un ArrayList ce contine o lista de nume


static List<String> listaNume = new ArrayList<>(Arrays.asList(nume));

private static int getWordCount() {


// returnam numarul de elemente din lista
int numarDeNume = listaNume.size();

System.out.println("avem " + numarDeNume + "nume in lista");


return numarDeNume;
}

private static void outputSortedList() {


// cream o lista de elemente sortate. pentru inceput cream o noua
lista
List<String> listaSortata = (List<String>) listaNume;
// utilizam metoda sort pentru sortare
Collections.sort(listaSortata);

String j = null;
// obtinem un iterator al listei
Iterator<String> i = listaSortata.iterator();
// parcurgem lista, verificand daca mai avem elemente disponibile
while (i.hasNext()) {
// trecem la urmatorul element disponibil
j = (String) i.next();
System.out.println(j);
}
}

private static boolean checkName(String nume) {


// verificam daca un element se afla in lista
boolean esteInLista = listaNume.contains(nume);

if (esteInLista)
System.out.println("acest element este in lista");
else
System.out.println("acest element nu este in lista");

return esteInLista;
}

private static void insertName(String nume) {


System.out.println("numele de inserat: " + nume);
try {
// adauga un element in lista
listaNume.add(nume);
}
// tartarea exceptiilor posibil a aparea
catch (UnsupportedOperationException e) {
System.out.println("Unsupported operation");
} catch (ClassCastException b) {
System.out.println("Class cast");
} catch (NullPointerException n) {
System.out.println("Null pointer");
} catch (IllegalArgumentException t) {
System.out.println("Illegal argument");
}
}

private static boolean removeName(String nume) {


if (checkName(nume))
// indeparteaza un element din lista
return (listaNume.remove(nume));
else
return false;
}

// utilitar pentru obtinerea unui nume de la intrare


private static String getName() {
String name = "";
try {
System.out.print("Nume: ");
name = query.readLine();
} catch (IOException e) {
System.out.println("Invalid name entry");
}
return name;
}

private static void printCommands() {


System.out.println("1: numara cuvinte");
System.out.println("2: sorteaza cuvinte");
System.out.println("3: cauta cuvant");
System.out.println("4: insereaza cuvant");
System.out.println("5: sterge cuvant");
System.out.println("6: afiseaza lista de optiuni");
System.out.println("0: Exit");
}

public static void main(String args[]) {


boolean gata = true;
printCommands();

while (gata) {
try {
System.out.print("Comanda: ");
int argType = Integer.parseInt(query.readLine());

switch (argType) {
case 0: {
gata = false;
break;
}
case 1: {
getWordCount();
break;
}
case 2: {
outputSortedList();
break;
}
case 3: {
checkName(getName());
break;
}
case 4: {
insertName(getName());
break;
}
case 5: {
removeName(getName());
break;
}
case 6: {
printCommands();
break;
}
default: {
System.out.println("Comanda invalida");
break;
}
}
} catch (Exception e) {
System.out.println("Operatie invalida, programul va
iesi" + e.getMessage());
System.exit(0);
}
} // while
} // main
} // class
Implementari ale interfetelor din JCF sunt grupate in urmatoarele categorii:
- implementari cu caracter general, ce cuprind majoritatea cerintelor:
o pot fi serializabile si clonate utilizand metoda clone()
o permit folosirea cheilor, a valorilor duplicat si a elementelor null
o au un comportament consistent
o contin iteratori care pot detecta orice modificari neautorizate si produc oprirea
iterarii
o sunt predefinit desincronizate
Principalul factor in alegerea uneia sau alteia dintre implementari il reprezinta viteza.
Printre aceste clase avem:
o HashMap, TreeMap si LinkedHashMap ce implementeaza interfata Map.
TreeMap este folosit pentru a ne asigura ca rezultatul iteratiilor este ordonat. In
6.0 implementarea lui TreeMap implementeaza si interfata NavigableMap.
Daca ordinea elementelor adaugate intr-un TreeMap nu este importanta putem
folosi HashMap. Ordinea intr-un HashMap nu este garantat ca ramine aceeasi pe
tot parcursul exploatarii. LinkedHashMap contine o iteratie ordonata, dar de
costuri mai mici decat TreeMap. La creare putem specifica ordinea iteratiei si
aceasta este ordinea de inserare, sau putem folosi ordinea predefinita de
accesare de la cea mai recenta cheie accesata la cea mai demult accesata. Clasa
poate fi implementata ca un cash.
o HashSet, TreeSet si LinkedHashSet ce implementeaza interfata Set.
TreeSet utilizeaza o iteratie logaritmica pentru operatii ca adaugarea si
stergerea. Ordinea elementelor ramine aceeasi. In 6.0 TreeSet implementeaza
si interfata NavigableSet. Daca ordinea elementelor adaugate intr-un
TreeSet nu este importanta putem folosi HashSet. LinkedHashSet contine o
iteratie ordonata, dar de costuri mai mici decat TreeSet. Ordinea este de la
ultimul adaugat element la cel mai recent adaugat.
o ArrayList si LinkedList, implementeaza interfata List. ArrayList este mai
rapida decat LinkedList pentru ca utilizeaza metoda locala
System.arraycopy() si ofera timp de acces constant. Adaugarea se face
printr-o operatie de complexitate amortizata. Ordinea elementelor ramine
aceeasi. Celelalte operatii se executa in timp linear
o PriorityQueue, implementeaza interfata Queue si este o coada ordonata ce
foloseste o structura de heap pentru prioritati. Poate fi ordonata in ordine
naturala sau prin implementarea unui Comparator. Capul cozii este accesat
prin metode ca: poll(), remove() sau peek()
o ArrayDeque, implementeaza interfata Deque si este redimensionabila. Nu este
thread safe.
o Hashtable, implementeaza interfata Map. Este identica cu HashMap, dar este
sincronizata si nu poate avea elemente null. Hashtable inmagazineaza
elemente printr-o cheie asociata, ceea ce inseamna acces rapid atunci cand
lucram cu cantitati mari de date. Ordinea elementelor ramane constanta de-a
lungul timpului.
- Implementari comode sau mini-implementari, contin in general metode statice:
o Collections.EMPTY_SET, Collections.EMPTY_MAP si
Collections.EMPTY_LIST, reprezinta un SET, MAP respectiv LIST, vide. Pot fi
utilizate ca date de intrare pentru metode, atunci cand nu dorim sa transmitem
valori. Acestea sunt nemodificabile
o Collections.singleton, returneza un SET nemodificabil ce contine doar un
singur element
o Collections.nCopies, returneza un LIST nemodificabil ce contine copii ale
aceluiasi element. Exemplu: List l = new
ArrayList(Collections.nCopies(100, "vaca")); Am creat o lista
ce contine de 100 de ori cuvantul vaca
o Array.asList, returneza un LIST de dimensiune fixa. Orice adaugare sau
stergere arunca o UnsupportedOperationException pentru ca dimensiunea
este nemodificabila.
- Implementari Wrapper, contin metode factory (care reprezinta un fel de constructor
generic pe care il putem folosi pentru a crea mai multe tipuri de instante ale claselor)
statice ce impacheteaza colectiile actuale si aduc extra functionalitati. Avem
urmatoarele categorii de wrapperi:
o Nemodificabili: previne modificarea colectiei de catre utilizator prin aruncarea
unei exceptii UnsupportedOperationException in cazul in care s-ar
intampla acest lucru. Prin aceasta marim securitatea structurii de date, facand-
o read-only. In aceasta categorie avem urmatoarele metode:
public static Collection unmodifiableCollection(Collection c);
public static Set unmodifiableSet(Set s);
public static List unmodifiableList(List list);
public static Map unmodifiableMap(Map m);
public static SortedSet unmodifiableSortedSet(SortedSet s);
public static SortedMap unmodifiableSortedMap(SortedMap m);
In acest sens iata un exemplu:
public class Unsupported {
static void test(String msg, List<String> list) {
System.out.println("--- " + msg + " ---");
Collection<String> c = list;
Collection<String> subList = list.subList(1, 8);
// Copy of the sublist:
Collection<String> c2 = new ArrayList<String>(subList);
try {
c.retainAll(c2);
} catch (Exception e) {
System.out.println("retainAll(): " + e);
}
try {
c.removeAll(c2);
} catch (Exception e) {
System.out.println("removeAll(): " + e);
}
try {
c.clear();
} catch (Exception e) {
System.out.println("clear(): " + e);
}
try {
c.add("X");
} catch (Exception e) {
System.out.println("add(): " + e);
}
try {
c.addAll(c2);
} catch (Exception e) {
System.out.println("addAll(): " + e);
}
try {
c.remove("C");
} catch (Exception e) {
}
// The List.set() method modifies the value but
// doesn’t change the size of the data structure:
try {
list.set(0, "X");
} catch (Exception e) {
System.out.println("List.set(): " + e);
}
}

public static void main(String[] args) {


List<String> list = Arrays.asList("A B C D E F G H I J K L".split("
"));
test("Modifiable Copy", new ArrayList<String>(list));
test("Arrays.asList()", list);
test("unmodifiableList()",
Collections.unmodifiableList(new
ArrayList<String>(list)));
}
}
o De sincronizare: pentru definirea colectiilor sincronizate. In aceasta categorie
avem urmatoarele metode:
public static Collection synchronizedCollection(Collection c);
public static Set synchronizedSet(Set s);
public static List synchronizedList(List list);
public static Map synchronizedMap(Map m);
public static SortedSet synchronizedSortedSet(SortedSet s);
public static SortedMap synchronizedSortedMap(SortedMap m);
o De verificare: este o masura pentru verificarea tipului colectiei.
ClassCastException este aruncata daca se incearca a se adauga un element
de tip nepotrivit. In aceasta categorie avem urmatoarele metode:
public static Collection checkedCollection(Collection c, Class
type);
public static List checkedList (List list, Class type);
public static Map checkedMap (Map map, Class valueType);
public static Set checkedSet (Set set, Class type);
public static SortedMap checkedSortedMap (SortedMap set, Class
type);
public static SortedSet checkedSortedSet (SortedSet set, Class
type);
- Implementari pentru scopuri speciale.
o EnumSet si CopyOnWriteArraySet implementeaza interfata Set. EnumSet este
folosita cu tipuri enum. Ordinea de iteratie este ordinea naturala. Nu este
thread safe. CopyOnWriteArraySet unde implementarea metodele add(),
set() si remove() determina creerea unei copii a sirului de baza.
Implementarea este thread safe
o CopyOnWriteArrayList implementeaza interfata List. Este asemanatoare ca
functionalitate cu CopyOnWriteArraySet. Putem folosi aceasta clasa atunci
cand facem modificari rare ale listei, dar frecvente traversari, in aplicatii
multifir
o EnumMap, WeakHashMap si IdentityHashMap implementeaza interfata Map.
EnumMap este folosita pentru chei de tip enumerare si fiecare cheie trebuie sa
fie de acelasi tip enumerare. Este reprezentata intern ca un sir, iar cheile sunt
ordonate natural. Nu este thread safe. WeakHashMap stocheaza chei slabe.
Aceasta inseamna ca daca o cheie nu mai este folosita atunci este automat
trimisa la garbage collector. IdentityHashMap foloseste egalitatea
referintelor, aceasta inseamna o relatie de egalitate in sensul operatorului ==.
- Implementari concurente, care sunt thread safe si sunt continute in pachetul
java.util.concurrent.
o ConcurrentSkipListSet, este o implementare concurenta a interfetei
NavigableSet. Ordinea elemente este implicit cea naturala, dar putem sa o
customizam furnizand un Comparator. Toate operatiile de inserare, stergere
sau acces al elementelor sunt thread safe. Aceasta implementare nu permite
elemente null.
o LinkedBlockingQueue, simuleaza o FIFO, elementele fiind aranjate inlantuit
de la cel mai tarziu la cel mai devreme element inserat in coada, cu cel mai
batrin la inceput. ArrayBlockingQueue, asemanatoare lui
LinkedBlockingQueue, dar implementata cu siruri.
PriorityBlockingQueue, asemanatoare lui PriorityQueue.
o DelayQueue este o coada de elemente intarziate. Un element intarziat este un
element a carui intarziere a expirat, astfel incat metoda getDelay() returneaza
o valoare <=0. Doar elementele intarziate pot fi obtinute din coada. In capul
cozii se afla elementul ce a expirat cel mai recent
o SynchronousQueue este o clasa in care nu putem insera un element pina cand
un alt fir nu incearca sa-l extraga
o LinkedBlockingDeque, implementeaza interfata BlockingDeque sub forma
unor noduri inlantuite
o ConcurrentHashMap, este o implementare a interfetei ConcurrentHashMap.
Operatiunile de regasire nu presupun blocarea si nu avem suport pentru
blocarea intregii structuri in vederea accesului
o ConcurentSkipListMap, este o implementare a interfetei
ConcurrentNavigableMap. Ordinea este cea naturala sau data de furnizarea
unui Comparator
- Implementari abstracte
o AbstractCollection, ofera o implementare de baza a interfetei Collection
si nu este nici Set si nici List. Trebuie implementate metodele size() si
iterator()
o AbstractSet, ofera o implementare de baza a interfetei Set. Trebuie
implementate metodele size() si iterator()
o AbstractList, ofera o implementare de baza a interfetei List. Trebuie
implementate metodele size() si get(). Nu trebuie furnizata definitie pentru
iterator()
o AbstractSequentialList, trebuie implementate metodele size() si
listIterator
o AbstractQueue, ofera o implementare de baza a interfetei Queue. Trebuie
implementate metodele peek(), poll() si size()
o AbstractMap, ofera o implementare de baza a interfetei Map. Trebuie
implementata metoda entrySet(). Are doua inner clase:
- AbstractMap.SimpleEntry, suporta metoda setValue()
- AbstractMap.SimpleImmutableEntry, nu suporta metoda
setValue()

Metoda hashCode()
Este membra a clasei Object si este folosita pentru a genera o valoare intreaga pentru orice
obiect. Fiecare obiect genereaza si inmagazineaza o unica valoare hashcode pe post de cheie.
Sintaxa metodei folosita pentru asa ceva este:
public int hashCode(Object);
si ofera suport pentru implementarile Hashtable si HashMap.
Codul unic al unui obiect este determinat de datele continute in obiect. Daca datele continute
in doua obiecte sunt egale atunci valoarea hashcode-ului este acelasi, adica daca
ob1.equals(ob2) atunci ob1.hashCode()==ob2.hashCode().
Putem invoca metoda hashCode() din obiecte ce implementeaza interfata Collection.
Pentru aceasta trebuie sa ne asiguram ca clasele ce suprascriu Object.equals() suprascriu si
Object.hashCode(). Conform contractului daca doua obiecte sunt egale via equals()
trebuie sa aiba acelasi hashCode(). Daca metoda este invocata de mai multe ori din acelasi
obiect ea va produce acelasi intreg pe toata durata aplicatiei.

Algoritmi folositi in gestionarea colectiilor


Metodele colectiilor, care sunt polimorfice, cuprind utilitatile predefinite oferite de JDK.
Colectiile au o ordine naturala predefinita (data de codificarea ASCII sau Unicode), aceasta
ordine poate fi modificata de utilizator. Metodele ce implica compararea sunt in general
supraincarcate. O prima varianta de supraincarcare are ca argument o lista sau o colectie in
timp ce a doua are pe linga acest argument inca unul, un Comparator.
Pentru a sorta elementele unei liste toate elementele listei treebuie sa implementeze interfata
Comparable. Apelul lui sort() sorteaza elementele in ordine naturala, dar putem defini un
Comparator pentru a modifica aceasta ordine. Avem doua forme ale metodei sort:
· public static <T extends Comparable<? super T>> void sort(List<T>
list), foloseste algoritmul de sortate prin interclasare (merge sort)
· public static <T> void sort(List<T> list, Comparator<? super T> c),
sortarea se face in acord cu comparatorul definit
Pentru a cauta un element intr-o colectie folosim metoda binarySearch(), care presupune ca
lista este ordonata crescator. Asadar, anterior apelelului trebuie sa apelam sort(). Are doua
forme:
· public static <T> int binarySearch(List<? extends Comparable<? super
T>> list, T key)
· public static <T> int binarySearch(List<? extends T> list, T key,
Comparator<? super T> c)
Valoarea returnata este mai mare sau egala cu zero, reprezentand indexul cheii, daca cheia
este gasita sau un rezultat negativ, altfel.
Avem trei algoritmi cu ajutorul carora manipulam datele unei liste.
· public static <T> void fill(List<? super T> list, T obj), ceea ce
determina initializarea tuturor elementelor listei cu valoarea celui de-al doilea
parametru
· public static void reverse(List<?> list), rearanjeaza in ordine inversa
elementele listei. Ruleaza in timp liniar si arunca o
UnsupportedOperationException daca lista nu suporta setari
· public static <T> void copy(List<? super T> dest, List<? extends T>
src), suprascrie o lista destinatie cu toate elementele dintr-o lista sursa

Suport pentru monitorizare si management


Platforma Java pune la dispozitie interfete si instrumente pentru a ne ajuta sa monitorizam
JVM si aplicatiile Java. Unele dintre cele mai intalnite probleme de catre programatori pe
timpul dezvoltarii aplicatiilor sunt:
- Memorie insuficienta: indicata, de obicei, printr-o exceptie OutOfMemoryException.
Cauzele pot fi urmatoarele:
o Spatiu insuficient in heap atunci cand aplicatia creaza un nou obiect
o Esecul incarcarii unei clase intr-o zona de memorie non-heap a Hotspot VM
implementation
o O alocare esueaza in heapul nativ
Java Heap Analysis Tool, sau JHAT, este un instrument de diagnostic ce ne permite sa
vizualizam problemele heap-ului, utilizand un browser. Permite interogari utilizator
ale problemelor heap-ului, utilizand Object Query Language (OQL)
- Scurgeri de memorie: cand o aplicatie devine blocata, aceasta se poate datora
memoriei neeliberate, anterior alocata. Cauzele ar putea fi:
o Referinte neintentionate catre obiecte
o Rate de crestere ridicate ale claselor si obiectelor
o Un numar neasteptat de instante ale clasei
Pentru a diagnostica aceasta folosim JConsole, JStat, JMap
- Finalizari: suprautilizarea sau subutilizarea finalizarilor poate cauza
OutOfMemoryError. Clasa Object defineste metoda finalize(), care este invocata
de garbage collector inaintea eliberarii spatiului de memorie. In cazul in care
finalize() este suprascrisa trebuie sa ne asiguram ca aceasta nu interfera operatiile
garbage collector-ului. Una dintre cauzele erorii mai sus mentionate este ca obiectele
asteapta sa fie finalizate si nu pot fi astfel colectate de garbage collector. Folosim
JConsole si JMap pentru a monitoriza aceasta problema
- Deadlock: gestionate de pachetul java.util.concurrent.locks. Spre exemplu,
clasa AbstractOwnableSynchronizer din pachet, permite unui fir sa fie proprietarul
exclusiv al unui sincronizator
- Fire ciclabile: sunt generate de cicluri infinite. Putem folosi JConsole cu JTop.
- High lock contention: atunci cand un numar foarte mare de fire asteapta ca un obiect
sa fie deblocat. JConsole gestioneaza situatia.
Java Management Extensions (JMX) poseda o multime de instrumente si extensii pentru
monitorizarea si managementul aplicatiilor. Aceasta se face prin introducerea unui agent JMX
in aplicatie catre care clientii JMX se pot conecta. JMX foloseste diverse mecanisme de
transport precum RMI. In 5.0 trebuie sa pornim aplicatia utilizand –D
com.sun.management.jmxremote. Aceasta nu mai este obligatoriu in 6.0 pentru ca
JConsole o poate atasa orice aplicatie ce ruleaza. Lansarea toolului se face prin comanda
jconsole in linia de comanda. Cand JConsole porneste ea trebuie sa fie capabila sa
detecteze aplicatiile Java care ruleaza de pe masina locala. Putem alege aplicatia din interfata
utilizator JConsole. Cand aplicatia a fost selectata suntem conectati cu succes la agentul
JMX. Cateva dintre functiile lui JConsole:
- Monitorizarea utilizarii memoriei de catre aplicatie
- Monitorizarea numarului de clase incarcate
- Detalii despre firele in rulare
- Verificarea statutului JVM ce gazduieste aplicatia
Avem urmatoarele comenzi utilitare in linia de comanda pentru diagnosticare si monitorizare,
incepand cu 6.0:
- JStat este un instrument de monitorizare statistic, experimental. Comanda jstat –
gcutil arata o statistica a utilizarii heapului si garbage collection dat de id-ul de
proces. Pentru a determina id-ul folosim comanda jps.
- JHat analizeaza problemele heapului. Pune la dispozitie interogari predefinite pentru a
examina clasele, instantele, histogramele de heap si obiectele ce asteapta sa se
finalizeze
- JStack afiseaza traseele stivei Java. Acest tool permite detectarea firelor blocate
mortal
- JMap permite sa vizualizam histograme ale heapului, prin comanda jmap –histo

Pasi pentru monitorizare: inseram plug-in-ul pentru JMX in Eclipse: Help>Install new
Software...>Add

· Name: eclipse-jmx
· URL: http://eclipse-jmx.googlecode.com/svn/trunk/net.jmesnil.jmx.update/
Acceptam termenii. Resetam Eclipse. Intram in perspectiva JMX. La conectare localhost,
port: 8686. portul trebuie aflat din server.log, la pornirea Glassfish-ului. din administration
Console, nodul: admin service>JMX Connector vedem numele portului si trebuie sa ne
asiguram ca la address avem 127.0.0.1.

Tema:
1. Sa se scrie o clasa derivata din ArrayList care poate fi parcursa in ordine naturala,
dar si in doua ordonari custom: din trei in trei sau in ordinea, primul,ultimul, al doilea,
penultimul, etc.
2. Se da o lista de coduri de produse (ex: "1S01", "1S02", "1M01", "1S01", etc) si o
descriere a fiecarui cod sub forma: ("Tricou Albastru", "1S01"), ("Tricou Negru",
"1S02") etc. Sa se creeze un program eficient pentru obtinerea unui raport ce cuprinde
descrierea fiecarui produs si numarul de aparitii in lista de coduri. Raportul va fi
ordonat alfabetic dupa descriere
3. Folositi o structura de date Deque, ca stiva, pentru a testa ca parantezele rotunde se
inchid corect intr-o expresie data de un String. Ca date de intrare vor fi mai multe
expresii, considerate constante (ex: „if ((a == b) && (x != y));”, „if ((a ==
b) && (x != y)));”). Testul va afisa un mesaj de forma:
Expresia 1 – corect
Expresia 2 – false ....
4. Fie un magazin de tricouri, il vom numi Duke’s Choice. Tricourile (id, descriere,
culoare, marime, numar bucati) intra (se achizitioneaza) sau ies (se vand din magazin).
Dorim sa facem un inventar al tricourilor si sa tiparim la consola raportul rezultat,
dupa un numar de tranzactii (id tricou, tip tranzactie, numar bucati tranzactionate).
Dorim doua rapoarte: unul in ordinea crescatoare a numarului de bucati si altul in
ordinea alfabetica a descrierii.

I/O introducere
Java contine o bogata multime de biblioteci ce efectueaza functii de intrare/iesire. Java
defineste un canal de intrare-iesire ca pe un flux (stream). Fluxul este o sursa de intrare sau o
destinatie de iesire. Sursele si destinatiile pot fi de multiple feluri de date precum octeti, tipuri
primitive, caractere si obiecte. Unele fluxuri doar trimit date altele le manipuleaza si le
transforma intr-un mod usor de interpretat.
Un program utilizeaza un flux de intrare pentru a citi date de la o sursa, un item la un moment
dat. Acelasi lucru se intampla cu fluxurile de iesire.
Indiferent cum lucreaza ele intern, toate fluxurile reprezinta o secventa de date.
Sursele si destinatiile se numesc fluxuri nod. Spre exemplu: fisiere, memorie, pipes intre fire
sau procese.
Un dezvoltator de aplicatie utilizeaza in mod uzual fluxurile I/O pentru a citi sau scrie fisiere,
pentru a citi sa scrie informatii de la sau la dispozitive de iesire (tastatura – intrarea standard,
consola – iesirea standard). O aplicatie foloseste un socket pentru a comunica cu o alta
aplicatie sau sistem la distanta.
Java suporta doua tipuri de stream-uri: caracter si byte. Intrarea si iesirea datelor caracter sunt
manipulate de readers si writers, iar a datelor octet de input streams si output streams.
Mai precis, intrarile pentru fluxuri de octeti sunt gestionate de subclase ale lui InputStream
iar iesirile de subclase ale OutputStream. La nivelul caracterelor (codificate Unicode) avem
clasele de baza Reader si Writer.
Fluxurile de octeti sunt in general folosite pentru a citi fisiere imagine, fisiere audio si
obiecte.
Cele trei metode de baza pentru citire din InputStream sunt:
- int read();, returneaza fie un byte citit din flux fie -1, ce indica atingerea
sfarsitului de fisier
- int read(byte[] b);, citeste fluxul intr-un sir de octeti si returneaza numarul de
octeti cititi
- int read(byte[] b, int off, int len);, la fel ca anterioara doar ca ultimele
doua argumente indica un subdomeniu al sirului, care urmeaza a fi umplut
Pentru eficienta se recomanda sa citim date in blocuri cat mai mari sau sa folosim buffere de
flux.
Dupa folosire un flux trebuie inchis. In Java SE 7 InputStream implementeaza
AutoCloseable.
Alte metode:
- int available();, returneaza numarul de octeti ce sunt disponibili imediat pentru a
fi citite din flux. O operatie de citire ce urmeaza acestui apel poate returna mai multi
octeti
- long skip(long n);, ignora numarul specificat de octeti din flux
- boolean markSupported(); void mark(int readLimit); void reset();,
efectueaza operatii push-back pe un stream, daca strimul suporta aceste operatii.
markSupported() returneaza true daca metodele mark() si reset() sunt
operationale pe fluxul curent. mark() indica faptul ca punctul curent din flux va fi
marcat si un buffer suficient de mare cat cel putin numarul de octeti specificat de
argument va fi creat. Parametrul metodei specifica numarul de octeti ce pot fi recititi
apeland reset().
Cele trei metode de scriere din OutputStream sunt:
- void write(int c);
- void write(byte[] buffer);
- void write(byte[] buffer, int offset, int length);
alte metode:
- void close();
- void flush();, care forteaza scrierea in flux
Ca si la citire este recomandata scrierea datelor in blocuri cat mai mari.
In mod asemanator avem metodele de citire din Reader:
- int read();, returneaza fie un int citit din flux, reprezentand un caracter Unicode,
fie -1, ce indica atingerea sfarsitului de fisier
- int read(char[] b);, citeste fluxul intr-un sir de caractere si returneaza numarul
de octeti cititi
- int read(char[] b, int off, int len);, la fel ca anterioara doar ca ultimele
doua argumente indica un subdomeniu al sirului, care urmeaza a fi umplut
Iar pentru scriere metodele din Writer sunt:
- void write(int c);
- void write(char[] buffer);
- void write(char[] buffer, int offset, int length);
- void write(String string);
- void write(String string, int offset, int length);
Fie urmatorul exemplu de copiere a unui flux de caractere, dat intr-un fisier in.txt.
public class CharStreamCopyTest {

public static void main(String[] args) {

char[] c = new char[128];


int cLen = c.length;

try (FileReader fr = new FileReader("in.txt");


FileWriter fw = new FileWriter("out.txt")) {
int count = 0;
int read = 0;
while ((read = fr.read(c)) != -1) {
if (read < cLen)
fw.write(c, 0, read);
else
fw.write(c);
count += read;
}
System.out.println("Wrote: " + count + " characters.");
} catch (FileNotFoundException f) {
System.out.println("File not found: " + f);
} catch (IOException e) {
System.out.println("IOException: " + e);
}
}
}
Copierea se face folosind un sir de caractere. FileReader si FileWriter sunt clase destinate
citirii si scrierii fluxurilor de caractere, precum fisierele text.

Inlantuirea fluxurilor I/O

Un program foloseste arareori un sigur obiect pentru flux, ci inlantuie o serie de stream-uri
pentru a procesa date. In prima parte a figurii anterioare este dat un exemplu de flux de
intrare: fluxul de fisier este operat de un buffer pentru cresterea eficientei si apoi este
convertit in date. Partea a doua exemplifica drumul invers.
public class BufferedStreamCopyTest {

public static void main(String[] args) {


try (BufferedReader bufInput = new BufferedReader(new FileReader(
"in.txt"));
BufferedWriter bufOutput = new BufferedWriter(new
FileWriter("out.txt"))) {
String line = "";
// read the first line
while ((line = bufInput.readLine()) != null) {
// write the line out to the output file
bufOutput.write(line);
bufOutput.newLine();
}
} catch (FileNotFoundException f) {
System.out.println("File not found: " + f);
} catch (IOException e) {
System.out.println("Exception: " + e);
}
}
}
Fata de exemplul anterior, in loc sa citim un sir de caractere, citim o linie intreaga folosind
readLine(), pe care o depunem intr-un String. Aceasta furnizeaza o foarte buna eficienta.
Motivul este ca orice cerere de citire facuta de un Reader cauzeaza o cerere de citire
corespunzatoare a fi facuta in fluxul de caractere. BufferReader-ul citeste caracterele din
flux intr-un buffer (dimensiunea bufferului poate fi setata, dar valoarea predefinita este in
general suficienta).
Un flux de procesare efectueaza o conversie catre un alt flux. Dezvoltatorul este cel care alege
tipul de flux bazat pe functionalitatea pe care o avem nevoie pentru fluxul final.

Fluxuri standard
In clasa System din java.lang avem trei campuri instanta statice:
- out, care reprezinta fluxul de iesire standard. Acesta este in permanenta deschis si
pregatit sa accepte date de iesire. In mod obisnuit acest stream corespunde monitorului
sau unei alte destinatii de iesire specificata de mediul gazda sau de utilizator. Este o
instanta a lui PrintStream
- in, care reprezinta fluxul de intrare standard. Acesta este in permanenta deschis si
pregatit sa furnizeze date de intrare. In mod obisnuit acest stream corespunde tastaturii
sau unei alte surse de intrare specificata de mediul gazda sau de utilizator. Este o
instanta a lui InputStream
- err, care reprezinta fluxul de iesire standard al erorilor. Acesta este in permanenta
deschis si pregatit sa accepte date de iesire. In mod obisnuit acest stream este utilizat
pentru afisarea mesajelor de eroare sau a altor informatii ce ar putea veni in atentia
imediata a unui utilizator. Este o instanta a lui PrintStream
In afara de obiecte PrintStream, System poate accesa instante ale java.io.Console.
Obiectul Console reprezinta o consola bazata pe caracter si asociata JVM curente. Daca o
masina virtuala are o consola aceasta depinde de platforma si de modul in care masina
virtuala este invocata.
public class SystemConsoleTest {

public static void main(String[] args) {


String username = "oracle";
String password = "tiger";
boolean userValid = false;
Console cons;
// Get a Console instance
cons = System.console();
if (cons != null) {
String userTyped;
String pwdTyped;
do {
userTyped = cons.readLine("%s", "User name: ");
pwdTyped = new String(cons.readPassword("%s", "Password:
"));
if (userTyped.equals(username) &&
pwdTyped.equals(password)) {
userValid = true;
} else {
System.out
.println("User name and password do
not match existing credentials.\nTry again.\n");
}
} while (!userValid);
System.out.println("Success! you are now logged in.");
} else {
System.out
.println("The console is not attached to this VM.
Try running this application at the command-line.");
}
}
}
Eclipse nu are atasat o consola. Exemplul anterior va trebui rulat in linia de comanda.
Exemplul ilustreaza metodele clasei consola.
Scrierea la fluxul standard de iesire se face prin metodele clasei PrintStream:
- print(), care printeaza argumentul fara caracterul linie nou
- println(), care printeaza argumentul cu caracterul linie noua
Cele doua metode sunt supraincarcate pentru majoritatea tipurilor primitive (boolean, char,
int, long, float si double) si pentru char[], Object si String. Daca argumentul este
Object metodele apeleaza metoda toString() a argumentului. Alaturi de acestea putem
folosi si metoda pentru scrierea formatata printf().
Un exemplu de citire de la tastatura prin fluxul standard de intrare este dat mai jos:
public class KeyboardInput {

public static void main(String[] args) {


// Wrap the System.in InputStream with a BufferedReader to read
// each line from the keyboard.
try (BufferedReader in = new BufferedReader(new
InputStreamReader(System.in))) {
String s = "";
// Read each input line and echo it to the screen.
while (s != null) {
System.out.print("Type xyz to exit: ");
s = in.readLine().trim();
System.out.println("Read: " + s);
if (s.equals("xyz")) {
System.exit(0);
}
}
} catch (IOException e) { // Catch any IO exceptions.
System.out.println("Exception: " + e);
}
}
}
In instructiunea try cu resurse am deschis un BufferedReader ce este inlantuit cu un
InputStreamReader, ce este inlantuit cu System.in.
Observatie: stringul null este returnat daca s-a ajuns la sfarsitul fluxului (spre exemplu
utilizatorul a apasat Ctrl+C).
Un canal citeste octeti si caractere in blocuri, in loc de a citi un octet sau un caracter.
public class ByteChannelCopyTest {

public static void main(String[] args) {

try (FileChannel fcIn = new FileInputStream("in.txt").getChannel();


FileChannel fcOut = new FileOutputStream("ou.txt").getChannel()) {
System.out.println("File size: " + fcIn.size());
// Create a buffer to read into
ByteBuffer buff = ByteBuffer.allocate((int) fcIn.size());
System.out.println("Bytes remaining: " + buff.remaining());
System.out.println ("Bytes read: " + fcIn.read(buff));
buff.position(0);
System.out.println ("Buffer: " + buff);
System.out.println("Bytes remaining: " + buff.remaining());
System.out.println("Wrote: " + fcOut.write(buff) + " bytes");
} catch (FileNotFoundException f) {
System.out.println("File not found: " + f);
} catch (IOException e) {
System.out.println("IOException: " + e);
}

}
}
In exemplu am creat un buffer de dimensiune potrivita, buffer folosit atat la citire cat si la
scriere. Citirea respectiv scrierea se fac intr-o singura operatie cu consecinte importante de
performanta.

Persistenta si serializare
Salvarea datelor intr-un depozit permanent se numeste persistenta.
Un obiect nepersistent exista doar cat masina virtuala Java ruleaza. Serializarea este un
mecanism pentru salvarea obiectelor ca un sir de octeti, care apoi pot fi reconstruiti intr-o
copie a obiectului. Pentru a serializa un obiect al unei clase specifice, aceasta trebuie sa
implementeze interfata java.io.Serializable. Aceasta interfata nu are metode, este o
interfata marker ce indica faptul ca clasa poate fi serializata.
Cand un obiect este serializat, doar campurile obiectului sunt pastrate. Atunci cand un camp
refera un obiect, campurile obiectului referit sunt de asemenea serializate (obiectul referit
trebuie si el serializat). Arborele campurilor unui obiect constituie un graf obiect.
Serializarea traverseaza graful obiect si scrie datele in fluxul de iesire pentru fiecare nod al
grafului.
Unele clase nu sunt serializate pentru ca ele reprezinta informatii specifice tranzitorii.
Daca un obiect graf contine o referinta neserializata, se va arunca o
NotSerializableException si operatia de serializare esueaza. Campurile ce nu trebuie
serializate vor fi marcate prin cuvantul rezervat transient.
Modificatorul de acces al campurilor nu influenteaza serializarea campurilor. De asemenea,
campurile statice nu sunt serializate.
La deserializarea obiectelor valorile campurilor statice sunt setate la valorile declarate in
clasa. Valorile campurilor nestatice tranzitorii sunt setate la valorile predefinite ale tipului.
Pe timpul serializarii un numar de versiune, serialVersionUID, este utilizat pentru a asocia
iesirea serializata cu clasa utilizata in procesul de serializare. La deserializare
serialVersionUID este folosit pentru verificarea faptului ca incarcarea claselor este
compotibila cu obiectul ce este deserializat. Daca se observa o alta valoare a
serialVersionUID, deserializarea va genera un InvalidClassException. O clasa
serializata poate declara un camp static si final de tip long pentru serialVersionUID.
Daca o clasa serializata nu declara in mod explicit un serialVersionUID, atunci la rulare
se va calcula o valoare predefinita pe baza diverselor aspecte ale clasei. Este, insa, puternic
recomandat ca clasele serializate sa declare explicit serialVersionUID. Altfel, la
deserializare pot apare InvalidCastException, pentru ca valoarea lui serialVersionUID
este foarte sensibila la diversele implementari ale compilatorului Java. Este recomandat, de
asemenea, ca serialVersionUID sa fie private, pentru ca nu are niciun rol in procesul de
mostenire.
Fie un exemplu de folosire a unui obiect graf:
public class Stock implements Serializable {

private static final long serialVersionUID = 100L;


private String symbol;
private int shares;
private double purchasePrice;
private transient double currPrice;

public Stock(String symbol, int shares, double purchasePrice) {


this.symbol = symbol;
this.shares = shares;
this.purchasePrice = purchasePrice;
setStockPrice();
}

// This method is called post-serialization


private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
// perform other initiliazation
setStockPrice();
}

public String getSymbol() {


return symbol;
}

public double getValue() {


return shares * currPrice;
}

// Normally the current stock price would be fetched via a feed


// Here we will simulate that
private void setStockPrice() {
Random r = new Random();
double rVal = r.nextDouble();
double p = 0;
if (currPrice == 0) {
p = purchasePrice;
} else {
p = currPrice;
}
// calculate the new price
if (rVal < 0.5) {
currPrice = p + (-10 * rVal);
} else {
currPrice = p + (10 * rVal);
}
}

@Override
public String toString() {
double value = getValue();
return "Stock: " + symbol + "\n" + "Shares: " + shares + " @ "
+
NumberFormat.getCurrencyInstance().format(purchasePrice)
+ "\n" + "Curr $: "
+ NumberFormat.getCurrencyInstance().format(currPrice) +
"\n"
+ "Value: " +
NumberFormat.getCurrencyInstance().format(value)
+ "\n";
}
}

public class Portfolio implements Serializable {

private static final long serialVersionUID = 101L;


private Set<Stock> stocks = new HashSet<>();

public Portfolio() {
}

public Portfolio(Stock... stocks) throws PortfolioException {


for (Stock s : stocks) {
addStock(s);
}
}

private void addStock(Stock newStock) throws PortfolioException {


try {
if (!stocks.add(newStock)) {
throw new PortfolioException("Stock " +
newStock.getSymbol()
+ " is a duplicate.");
}
} catch (Exception e) {
throw new PortfolioException("Exception from Set.add method: "
+ e);
}
}

public double getValue() {


double value = 0;
for (Stock s : stocks) {
value += s.getValue();
}
return value;
}

public String toString() {


StringBuilder sb = new StringBuilder("Portfolio Summary\n");
for (Stock s : stocks) {
sb.append(s);
}
return sb.toString();
}
}

public class PortfolioException extends Exception {


private static final long serialVersionUID = 102L;

public PortfolioException(String message) {


super(message);
}

public PortfolioException(String message, Throwable t) {


super(message, t);
}
}

public class SerializeStock {

public static void main(String[] args) {


// Create a stock portfolio
Stock s1 = new Stock("ORCL", 100, 32.50);
Stock s2 = new Stock("APPL", 100, 245);
Stock s3 = new Stock("GOGL", 100, 54.67);
Portfolio p = null;
try {
p = new Portfolio(s1, s2, s3);
} catch (PortfolioException pe) {
System.out.println("Exception creating Portfolio: " + pe);
System.exit(-1);
}

System.out.println("Before serializaton:\n" + p + "\n");

// Write out the Portfolio


try (FileOutputStream fos = new FileOutputStream("test.out");
ObjectOutputStream out = new ObjectOutputStream(fos)) {
out.writeObject(p);
System.out.println("Successfully wrote Portfolio as an
object");
} catch (IOException i) {
System.out.println("Exception writing out Portfolio: " + i);
}

// Read the Portfolio back in


try (FileInputStream fis = new FileInputStream("test.out");
ObjectInputStream in = new ObjectInputStream(fis)) {
Portfolio newP = (Portfolio) in.readObject();
System.out.println("Success: read Portfolio back in:\n" +
newP);
} catch (ClassNotFoundException | IOException i) {
System.out.println("Exception reading in Portfolio: " + i);
}
}
}

Clasa Portfolio este formata dintr-o multime de Stock-uri. Pe timpul serializarii


currPrice nu este serializat, fiind marcat cu transient. Am creat o metoda care sa seteze
valoarea campului tranzitoriu.
In clasa de test am creat un FileOuputStream inlantuit cu un ObjectOutputStream.
Aceasta permite ca octetii generati de ObjectOutputStream sa fie scrisi intr-un fisier
folosind metoda writeObject(). Aceasta metoda parcurge graful si scrie datele continute in
campurile non-transient si non-static.
Invers, am creat un FileInputStream inlantuit cu un ObjectInputStream. Octetii sunt cititi
de readObject() si obiectul este refacut pentru campurile non-statice si non-transient.
Metoda returneaza un Object ce trebuie convertit la tipul cerut.
Un obiect ce este serializat si deserializat isi poate controla serializarea campurilor. Metoda
writeObject() este invocata pe obiectul ce urmeaza a fi serializat. Daca obiectul nu contine
o astfel de metoda, metoda defaultWriteObject() este invocata. Metoda
defaultWriteObject() trebuie apelata o data si doar o data de catre metoda
writeObject() a obiectului serializat.
Pe timpul deserializarii metoda readObject() este invocata pe obiectul ce se deserializeaza.
Semnatura metodei este importanta (vezi exemplul din clasa Stock). In aceasta clasa am
furnizat metoda readObject() pentru a ne asigura ca currPrice este setat dupa
deserializarea obiectului.
Observatie: currPrice este setat si in constructor, dar constructorul nu este apelat pe timpul
deserializarii.

Clasa java.io.File
Pachetul java.io permite programelor Java sa citeasca si sa scrie date in diverse formate
(texte, grafice, sunete si video). Pachetul contine clase ce permit accesul la date secvential sau
aleator.
Clasele Reader si Writer precum si subclasele lor sunt utilizate pentru acces secvential,
adica un flux secvential de biti.
Clasa RandomAccessFile si subclasele sale sunt folosite pentru a scrie sau citi aleator datele
din fisier.
In exemplul urmator am creat si apoi am adaugat, intr-un fisier cu acces aleator, mai multe
intrari editate intr-un TextField:
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SampleWriteFile implements ActionListener {
private Frame f;
private Button write, clear, exit;
private TextField txt;
public SampleWriteFile(String s) {
f=new Frame(s);
write=new Button("Scrie in fisier");
write.addActionListener(this);
clear=new Button("sterge");
clear.addActionListener(this);
exit=new Button("iesire");
exit.addActionListener(this);
txt=new TextField(20);
Label l=new Label("introdu un text");
Panel p=new Panel();
Panel p2=new Panel();
p.add(l);p.add(txt);
p2.add(write);p2.add(clear);p2.add(exit);
f.add(p, "North");
f.add(p2);
f.pack();
f.setResizable(false);
f.setVisible(true);
}

public static void main(String args[]) {

new SampleWriteFile("creare fisier");


}

public void actionPerformed(ActionEvent e) {

if(e.getSource()==clear) {
txt.setText("");
}

if(e.getSource()==exit) {
System.exit(0);
}

if(e.getSource()==write) {
writeFile();
}
}

public void writeFile() {


try {
RandomAccessFile file=new
RandomAccessFile("sampletext.txt", "rw");
file.seek(file.length());
file.writeBytes(txt.getText()+ "\n");
}
catch(IOException e) {
JOptionPane.showMessageDialog(null,"nu putem scrie in
fisier","Exception",JOptionPane.ERROR_MESSAGE);
}

}
}
Metoda seek() a fost utilizata pentru a specifica locatia din fisier in care are loc o anumita
operatie. In exemplul nostru am facut pozitionarea la sfarsitul fisierului. Metoda
writeBytes() este folosita pentru a scrie datele in fisier.
Clasa java.io.File poate reprezenta un director sau un fisier. Aceasta permite navigarea,
descrierea si accesul la fisiere si directoare. Managerul de securitate Java permite doar acele
operatii care sunt in interiorul unui context de securitate. Deoarece majoritatea browserelor nu
permit orice tip de acces, clasa File si clasele inrudite sunt utilizate in aplicatii si nu in
applet-uri.
Crearea unui obiect File nu inseamna automat crearea unui fiser sau director real. Si
stergerea unui obiect File de catre garbage collector nu inseamna neaparat stergerea fizica a
acestuia. Constructorii clasei sunt:
· File (File directoryObj, String fileName), creaza o noua instanta utilizand
path-ul unui fisier (director) existent, dat ca prim parametru, si avand numele dat de al
doilea parametru
· File (String pathName), creaza o instanta avand numele si calea date de parametru
· File (String pathName, String fileName) , creaza o noua instanta utilizand
path-ul dat de primul parametru, si avand numele dat de al doilea parametru
Avem urmatoarele metode in File:
- exists(), care confirma daca fisierul specificat exista
- getName(), returneaza numele fisierului sau directorului (ultimul element al unui cai
complete)
- getAbsolutePath(), returneaza un string ce este calea absoluta a unui fisier sau
director
- isDirectory()
- isFile()
- canRead()
- getTotalSpace(), returneaza un long ce reprezinta dimensiunea in bytes a partitiei
numita de calea abstracta
- getFreeSpace(), returneaza un long ce reprezinta numarul de bytes disponibili ai
partitiei numita de calea abstracta. Valoarea nu este neaparat certa.
- setWritable(), permite unui fisier sa poata fi scris. Primul parametru boolean, daca
are valoarea true, permite scrierea. Al doilea parametru boolean da permisiune de
scriere doar celui care a creat sau este proprietarul fisierului, daca valoarea este true,
altfel tuturor
- setExecutable(), permite unui fisier sa se execute operatii pe el. Primul parametru
boolean, daca are valoarea true, permite acest lucru. Al doilea parametru boolean
da permisiunea doar celui care a creat sau este proprietarul fisierului, daca valoarea
este true, altfel tuturor
- canExecute(), returneaza true daca fisierul poate fi executat
In exemplul urmator am definit o functie de cautare a unui fisier intr-un director specificat.
Functia returneaza calea completa a fisierului sau mesajul fisier negasit.
static String search (File f, String fileName) {
String contents[] = f.list() ;
int i = 0;
String found = "fisier negasit" ;

for (i=0; i<contents.length; i++) {


if (fileName.equals (contents[i]))
return (new File(f, contents[i]).getAbsolutePath()) ;
}
i = 0 ;
while ((i < contents.length) & (found.equals ("fisier
negasit"))) {
File child = new File (f, contents[i]) ;
if (child.isDirectory())
found = search (child, fileName);
i++ ;
}
return found ;
}

public static void main(String args[]) {


File f= new File("E://J2EE");

System.out.println(search(f,"sampletext.txt"));
}
Functia list() returneaza un sir de stringuri, listand continutul directorului. Daca File nu
este un director list() va returna null.
Cand Java citeste sau scrie date, deschide un flux, citeste si scrie informatii in flux si apoi
inchide fluxul. Clasele stream utilizeaza intrari si iesiri de date generale in timp ce clasele
writer si reader folosesc stringuri Unicode. Datele receptionate de la sau trimise la dispozitive
de I/O constau in bytes. Java suporta si alte tipuri de date precum intregi, caracter sau string.
Java foloseste o ierarhie de clase pentru a lucra cu diferite tipuri de date. Clasele
InputStream si OutputStream sunt abstracte si folosesc fluxuri low-level pentru a citi bytes
de la sau a-i trimite la fisiere, sockets sau pipes. Fluxurile low-level au acces direct la bytes
procesati. Fluxurile high-level sunt construite peste cele low-level, introducand noi
capabilitati.

Clasele FilterInputStream si FilterOutputStream extind clasele InputStream respectiv


OutputStream si utilizeaza fluxuri high-level pentru a citi sau scrie date precum stringurile
sau intregii din fluxuri de octeti.
Majoritatea claselor de intrare au o clasa de iesire corespunzatoare.
Urmatoarele clase fac operatii de intrare-iesire fara formatare (la nivel de jos, binar):
- ByteArrayInputStream si ByteArrayOutputStream, scriu si citesc siruri de octeti
prin zone tampon
- FileInputStream si FileOutputStream, scriu si citesc siruri de octeti din fisiere
- ObjectInputStream si ObjectOutputStream, deserializeaza (serializeaza) date
primitive si obiecte
- PipedInputStream si PipedOutputStream, lucreaza cu fire de comunicatie
- SequenceInputStream, permite concatenarea la alte fluxuri de intrare si citirea din
fiecare pe rand
Subclasele lui InputStream si OutputStream au structura si functii complementare,
incluzand:
- Constructori. Clasele FileInputStream si FileOutputStream au constructori
similari. Sintaxa constructorilor este:
FileInputStream(String pathname)
FileInputStream(File file)
FileOutputStream(String pathname)
FileOutputStream(File file)
- Metode de citire si scriere. Metoda read() din FileInputStream citeste urmatorul
byte din fisier. Metoda write() din FileOutputStream scrie un byte in fisier.
Sintaxa celor doua metode este:
int read () throws IOException
void write (int <b>) throws IOException
- Metode de citire si scriere a sirurilor. Clasele FileInputStream si
FileOutputStream au metode complementare pentru citirea si scrierea sirurilor de
octeti. Sintaxa acestor metode este:
int read(byte[] b)
int read(byte[] b, int off, int len)

void write(byte[] b)
void write(byte[] b, int off, int len)
Fluxurile de intrare-iesire high-level comunica cu fluxurile low-level.
Majoritatea claselor de intrare iesire de nivel inalt mostenesc atribute din superclasele
FilterInputStream si FilterOutputStream. Constructorul unei astfel de clase primeste ca
argument un Input/OutputStream. O diagrama a claselor este ilustrata in figura urmatoare:

Presupunand cazul constructorului clasei DataInputStream, va trebui sa-i trimitem ca


parametru un constructor InputStream:
DataInputStream(InputStream objectName)
Observatie: Putem folosi oricare dintre clasele care extind clasa abstracta InputStream ca
parametru al constructorului.
Cand un obiect flux de nivel inalt receptioneaza octeti de la un flux de nivel jos, proceseaza
octetii si ii converteste intr-un tip de date potrivit. Spre exemplu, clasa DataInputStream
contine metoda de citire ce converteste bytes catre toate tipurile primitive precum si la
stringuri. Metoda readInt() citeste urmatorii patru octeti si ii converteste intr-un intreg.
Programatorul este cel care trebuie sa se asigure ca urmatorii patru bytes reprezinta un intreg.
Pentru a inchide un flux se utilizeaza metoda close().
Daca dorim sa inchidem un lant de obiecte flux atunci trebuie sa o facem in ordine inversa
crearii. Aceasta previne inchiderea unui flux low-level inaintea unui flux high-level.
Dam, in continuare, un exemplu de scriere intr-un fisier, silviu.dat, a unor date in format
UTF folosind fluxul high-level DataInputStream construit pe baza fluxului low-level
FileOutputStream, care la randul sau s-a creat din fisierul File. De remarcat ca inainte de
scrierea prima oara silviu.dat exista ca un fisier vid, in directorul aplicatiei.
public static void main(String args[]) {
File f = new File ("silviu.dat");
if(f.exists() && f.isFile() && f.canWrite()) {
try {
FileOutputStream fostream = new FileOutputStream(f);
DataOutputStream dostream = new DataOutputStream(fostream);

dostream.writeUTF("date codificate UTF");


dostream.close();

fostream.close();
}
catch (IOException e) {
}
}
}
De observat, la sfarsitul codului, modalitatea de inchidere a fluxurilor, in ordine inversa fata
de ordinea crearii.

Clasele Reader si Writer


Sunt clase abstracte si sunt similare claselor de flux respective. Ambele sunt impartite in clase
low-level si high-level. Clasele low-level comunica cu dispozitivele de intrare iesire iar cele
high-level cu cele low-level. Aceste clase au fost desemnate special pentru a lucra cu
caractere Unicode. Astfel, clasele low-level lucreaza cu caractere.

Avem urmatoarele clase low-level:


- FileReader, folosita pentru a citi fluxuri de caractere dintr-un fisier. Folosim aceasta
clasa pentru a citi fisiere text
- CharArrayReader, folosita pentru a citi siruri de caractere, sub forma unui flux de
caractere. Clasa implementeaza un buffer de caractere
- PipedReader, folosita pentru a citi caractere pintr-un flux pipe
- StringReader, folosita pentru a citi stringuri
Clasele high-level sunt:
- BufferedReader, folosita pentru a citi texte de la un flux de intrare, de caractere. Este
folosita pentru a spori eficienta codului, furnizand un buffer. Este recomandat sa
folosim intotdeauna intrari si iesiri folosind un buffer
- FilterReader, este abstracta si este folosita pentru a citi fluxuri de caractere filtrate,
dintr-un fisier. Clasele derivate manipuleaza sirurile de caractere citite oferind o
filtrare a datelor citite. Spre exemplu, putem filtra liniile citite dintr-un fisier pe baza
unei expresii regulate
- InputStreamReader, folosita pentru a converti un stream de bytes intr-o multime de
caractere folosind un Charset specificat. Putem folosi aceasta clasa pentru a accepta
System.in
Clasa FileReader are doi constructori:
FileReader(String pathname)
FileReader(File file)
Acesta este formatul general al constructorilor tuturor claselor din ierarhia ce implementeaza
direct Reader.
Vom da, in continuare, un exemplu in care vom citi un text de la tastatura, pe care apoi il vom
afisa:
public static void main(String args[]) {
try {
InputStreamReader buff = new InputStreamReader(System.in) ;
BufferedReader theFile = new BufferedReader(buff);
System.out.println("introduceti un text:");
String s = theFile.readLine();
theFile.close();
buff.close();
System.out.println("textul introdus este: " + s);
}
catch(Exception e){

}
}
Constructia este asemanatoare celei din exemplul precedent.
Majoritatea claselor de citire au in corespondenta clase de scriere.
Daca metoda de citire cel mai frecvent utilizata este readLine(), corespondentul sau la
scriere este write(). Scrierea din buffer in destinatie nu se face daca cantitatea de date este
mai mica decat cea a bufferului. Abia cand dimensiunea bufferului a fost atinsa se produce
scrierea. Pentru a preveni pierderea de informatii datorate inchiderii bufferului folosim
metoda flush(). Apelul acesteia determina golirea bufferului si trimiterea tuturor datelor
fisierului cu care este in corespondenta. flush() va fi apelata inainte de close().
Cateva dintre exceptiile importante atunci cand lucram cu operatii de I/O sunt:
- FileNotFoundException, localizarea unui fisier se face fara succes
- EOFException, sfarsitul fisierului atins intr-un mod neasteptat
- IterruptedIOException, intrerupere neasteptata
- ObjectStreamException, clasa de baza pentru erorile aruncate de clasele
ObjectStream
In Java 6 clasa File contine metode noi ce furnizeaza informatii despre utilizarea discului de
memorie:
- getTotalSpace(), returneaza dimensiunea in octeti a partitiei in care se afla fisierul
- getFreeSpace(), returneza dimensiunea in octeti a zonei libere din partitia in care se
afla fisierul
- getUsableSpace(), asemanatoare functiei precedente, dar verifica si restrictiile
sistemului de operare si permisiunile de scriere asociate fisierului.
Alte extensii din versiunea 6 sunt utilizate pentru a restrictiona sau permite citirea, scrierea
sau executarea unui fisier:
- setWritable(), specifica permisiune de scriere asupra unui obiect File. Are doua
semnaturi:
setWritable(boolean writable)
setWritable(boolean writable, boolean ownerOnly)
prima este utilizata pentru a specifica daca fisierul poate fi scris sau nu. A doua
specifica daca permisiunea la scriere este valabila doar proprietarului fisierului
- setReadable(), specifica permisiune de citire asupra unui obiect File. Are doua
semnaturi:
setReadable(boolean readable)
setReadable(boolean readable, boolean ownerOnly)
prima este utilizata pentru a specifica daca fisierul poate fi citit sau nu. A doua
specifica daca permisiunea la citire este valabila doar proprietarului fisierului. Daca
sistemul nu poate decide proprietarul va acorda permisiune tuturor indiferent de ceea
ce stabileste aceasta metoda
- setExecutable(), specifica permisiune de executie asupra unui obiect File. Are
doua semnaturi:
setExecutable(boolean writable)
setExecutable(boolean writable, boolean ownerOnly)
Metoda care verifica posibilitate ca un fisier sa poata fi executat este canExecute().

Pachetul java.nio
Denumirea provine de la „new I/O”. Inlocuieste java.io si are scopul de a facilita operatii de
intrare-iesire neblocabile, dar si blocarea partiala a unui fisier.
NIO are performante mai bune si este mai eficient. Spre exemplu, permite citirea si scrierea
folosind o singura conexiune in doua sensuri FileChannel. Permite lucrul in conjuctie cu
APIul existent. Principalele facilitati sunt:
- Fisiere mapate si blocate: fisierele mapate in memorie creaza o mapare care ne
permite sa lucram cu fisierele de date ca si cum ar ocupa un spatiu in memorie.
Fisierele blocate permit accesul exclusiv al unei singure aplicatii sau accesul mai
multora
- I/O asincrone: ne permite sa adaugam I/O nonblocabile si selectabile in codul Java.
Cand folosim I/O neblocabile toate actiunile necesare pentru citire sau scriere sunt
executate imediat. Cand folosim I/O asincrone firul principal este gata sa selecteze un
flux care este pregatit sa transmita sau sa receptioneze date. El se muta la un alt flux
daca cel original se blocheaza
- Canale: un canal functioneaza ca un punct final pentru comunicatie. Ele seamna cu
fluxurile. Dispozitivele folosesc metodele proprii pentru citirea sau scrierea de date.
Interfata Channel are doar doua metode: isOpen() si close()
- Bufferi: pachetul este construit in jurul obiectelor ByteBuffer, in timp ce java.io
foloseste siruri de octeti. Obiectele ByteBuffer ne permit sa setam bufferele la read-
only si sa urmarim pozitia datelor scrise sau citite dintr-un buffer. Ne permit, de
asemenea, sa scriem toate tipurile de date primitive. Putem seta bufferii ca bufferi
directi, ce utilizeaza bufferi gazduiti de sistemul de operare
- Coderi si decodari de caractere: permit conversia sirurilor de date in caractere si
invers. Clasa Charset din acest pachet permite acest lucru.
Buffer este o subclasa directa a lui Object. Buffer inmagazineaza propria stare. Este
abstracta. Are trei proprietati:
- position: determina pozitia unde urmatorul element va fi citit sau scris in buffer.
- limit: determina pozitia primului element din buffer ce nu va fi scris sau citit. Limita
poate fi intre 0 si capacitatea buferului si poate fi setata la rulare prin apelul metodei
limit()
- capacity: este capacitatea bufferului sau numarul sau de elemente. Capacitatea este
setata la crearea bufferului
Clasele Buffer si ByteBuffer (clasa care implementeaza interfata) au urmatorele metode:
- clear(), seteaza proprietatea position la zero si limit la valoarea capacitatii. Nu
afecteaza datele din buffer. Este folosita atunci cand dorim sa introducem date noi in
buffer. Datele initiale se pastreaza dar vor fi suprascrise
- mark(), seteaza o pozitie in buffer la care ne putem intoarce la un apel viitor. Se poate
folosi pentru a retine locul in care datele au fost scrise
- reset(), seteaza proprietatea position la pozitia data de mark. Nu afecteaza datele
din buffer. Este folositoare atunci cand dorim sa copiem sau sa rescriem date dupa ce
au fost sterse
- put(), este folosita pentru a scrie octeti intr-un ByteBuffer. Pozitia in care scriem
este intotdeauna intre mark si limit.
- get(), este folosita pentru a citi octeti dintr-un ByteBuffer. Pozitia din care citim
este intotdeauna intre mark si limit
- flip(), seteaza limit la position. Apoi seteaza position la 0. Este folosita dupa
scrierea de date in buffer
- compact(), muta toate datele la inceputul bufferului. Apoi seteaza position la
valoarea unde datele se incheie si limit la valoarea capacitatii
In exemplul urmator vom da construirea si gestionarea unui buffer, folosind metodele anterior
descrise:
import java.nio.*;
public class Test{
public static void showBufferProperties(Buffer b){
System.out.println("proprietatile bufferului:");
System.out.println("capacitatea="+ b.capacity());
System.out.println("limita="+ b.limit());
System.out.println("pozitia="+ b.position());
}

public static void showBufferData(ByteBuffer b){


System.out.println("afisarea datelor bufferului");
while(b.hasRemaining())
System.out.println(b.get());
}

public static void showArrayData(byte[] array){


System.out.println("afisarea datelor bufferului");
for(int cnt = 0;cnt < array.length; cnt++){
System.out.println(array[cnt]);
}
}

public static void main(String[] args){


//construirea unui buffer dintr-un array
System.out.println("crearea, popularea si afisarea unui
buufer de bytes");
byte[] array = {0,1,2,3,4,5,6,7};
showArrayData(array);
System.out.println("crearea bufferului");
ByteBuffer b = ByteBuffer.wrap(array);
showBufferProperties(b);
showBufferData(b);
showBufferProperties(b);
System.out.println("modificarea primului element in
array");
array[0] = 10;
showArrayData(array);
System.out.println("apelul met flip() in buffer");
b.flip();
showBufferProperties(b);
showBufferData(b);
System.out.println("rearanjarea bufferului");
b.rewind();
showBufferProperties(b);
showBufferData(b);
b.put(3,(byte)20);
b.rewind();
showBufferData(b);
showArrayData(array);
System.out.println("marcarea la pozitia 2 ");
b.rewind().position(2).mark();
System.out.println("setarea pozitiei la 4");
b.position(4);
showBufferData(b);
System.out.println("resetarea la marcajul anterior");
b.reset();
showBufferData(b);
System.out.println("Bufferul este read only:"+
b.isReadOnly());
}
}
Canalele sunt folosite pentru a comunica cu fisiere si dispozitive. Canalele sunt cuprinse in
pachetul java.nio.
Canalele implementeaza interfata Channel. Aceasta interfata este accesata de cate ori canalul
este deschis sau inchis si are metoda close() pentru a inchide canalul. O data inchis un canal
nu mai poate fi redeschis.
Avantajele folosirii canalelor:
- o ierarhie a claselor mult mai clara
- operatii de citire scriere comune: un canal reprezinta o conexiune in doua sensuri catre
un fisier, socket sau dispozitiv. Putem face operatii de citire scriere folosind o singura
conexiune
- imbunatatirea integrarii bufferului: cu ajutorul canalelor putem mapa direct fisierele
bufferelor de memorie si atunci putem citi si scrie direct din obiectele ByteBuffer
java.nio.channels cuprinde diverse clase ce implementeaza o varietate de interfete:
- clasa FileChannel implementeaza printre altele interfetele ByteChannel,
GatheringByteChannel si ScatteringByteChannel. Cu ajutorul acestei clase putem
citi sau scrie intr-un fisier. Permite, de asemenea, maparea unei largi portiuni din fisier
memoriei. Este asemanatoare ca functionlitate lui RandomAccessFile.
- clasa SocketChannel implementeaza printre altele interfetele ByteChannel,
GatheringByteChannel si ScatteringByteChannel. Permite comunicarea prin
socket neblocabil, inchidere asincrona si este thread safe.
- clasa Pipe.SourceChannel implementeaza interfetele ReadableByteChannel si
ScatteringByteChannel. Acest canal reprezinta partea de sfarsit ce poate fi citit intr-
un pipe.
- clasa Pipe.SinkChannel implementeaza interfetele WritableByteChannel si
GatheringByteChannel. Acest canal reprezinta partea de sfarsit ce poate fi scrisa
intr-un pipe.
Clasa FileChannel prezinta urmatoarele avantaje ale folosirii ei:
- blocarea fisierului pentru a preveni scrierea sau citirea simultana. Avem doua tipuri de
blocare: share-uita si exclusiva. Blocarea exclusiva previne plasarea unei alte blocari
atunci cand fisierul este deja blocat
- mapari in memorie pentru cazurile in care dorim ca fisierul sa fie disponibil unui
singur obiect ByteBuffer. Fisierul este mapat in memorie ceea ce economiseste
resursele atunci cand intentionam sa accesam fisierul
- optimizarea transferului de fisier prin metodele transferTo() si transferFrom() ce
pot fi mai eficiente decat un ciclu ce ce copiaza octetii cititi intr-un fisier destinatie
Dam, in continuare, cateva exemple de folosire a facilitatilor acestei clase:
private FileChannel myReadWrite ;

public FileWriting (String openFile) {


try {

// deschiderea unui FileOutputStream in mod append


myReadWrite = new FileOutputStream(openFile, true).getChannel() ;

}
catch (IOException e) {
myReadWrite = null ;
}
}
In exemplul anterior am creat un FileChannel dintr-un obiect FileOutputStream prin
apelul metodei getChannel(). In mod append, dat de valoarea true al celui de-al doilea
parametru al constructorului, variabila position va avea valoarea dimensiunii fisierului.
public void memoryMapAFile (String fileToMap ) {
// cream un FileChannel si mapam un buffer pentru el
try {
FileChannel mappedFile = new RandomAccessFile (fileToMap,
"rw").getChannel() ;
MappedByteBuffer mappedBuff = mappedFile.map
(FileChannel.MapMode.READ_WRITE, 0, mappedFile.size() ) ;
}
catch (IOException e) {}
}
In exemplul anterior am folosit functia map() pentru a crea un MappedByteBuffer, care
incorporeaza toate metodele din ByteBuffer plus cele referitoare la mapare. Canalul a fost
deschis pentru citire si scriere ( FileChannel.MapMode.READ_WRITE).
public void bulkCopy (String fileIn, String fileOut ) {
try {
FileInputStream inFile = new FileInputStream (fileIn) ;
FileOutputStream outFile = new FileOutputStream (fileOut, true)
;

FileChannel in = inFile.getChannel () ;
FileChannel out = outFile.getChannel () ;

in.transferTo (0, (int) in.size(), out ) ;

in.close () ;
out.close () ;
}
catch (IOException e) {}
}
In exemplul anterior am creat o copie a unui fisier in Java. Metoda transferTo() transfera
datele intre cele doua canale. Ca parametru am trimis si dimensiuniea fisierului din canalul
sursa. (in.size()).
Metoda wrap(), impacheteaza un sir de octeti intr-un ByteBuffer si il scrie intr-un
ByteBuffer.
myBuff = ByteBuffer.wrap(myBytes) ;
f.myReadWrite.write (myBuff) ;
Metoda force() este folosita pentru ca schimbarile facute asupra continutului unui buffer sa
fie scrise si in canal.
System.out.println ( "Bytes written: " + totalBytes ) ;
System.out.println ( "File length now: " + f.myReadWrite.size () ) ;
f.myReadWrite.force( true ) ;
f.myReadWrite.close() ;
Metoda close() determina inchiderea canalului.

Clasa Scanner
Permite citirea facila, parsarea stringurilor si tipurilor primitive folosind expresii regulate. Se
afla in pachetul java.util. Clasa Scanner citeste intrari de la consola, fisier, string sau orice
alte surse ce implementeza interfata Readable sau ReadableByteChannel.
Simplifica citirea datelor formatate, de intrare, cu ajutorul tokenilor ce urmaresc expresii
regulate. Un token este o secventa de caractere pe care o specificam atunci cand cream un
scanner folosind delimitatori potriviti. In mod predefinit delimitatorul este spatiul.
O expresie regulata este un string ce cuprinde un sablon de potrivire. Modul in care expresia
regulata se potriveste unui token in codul sursa va determina parsarea datelor. Putem folosi
metode predefinite pentru a extrage expresii precum stringuri, double sau intregi.
Putem sa ne definim si propriile expresii folosind metoda next(). Metoda are ca parametru
un Pattern sau un String si returneza un String, care reprezinta urmatorul token ce se
potriveste sablonului. Procesul de scanare implica urmatorii pasi:
- scannerul localizeaza un token folosind delimitatorii specifici
- scannerul identifica potrivirea unui token cu o expresie regulata, daca este specificata
vreuna
- tokenul este stocat intr-o variabila
Scanner are urmatorii constructori folositi pentru a crea obiecte de citire a intrarilor:
- Scanner(InputStream), dintr-un flux de intrare
- Scanner(InputStream sursa, String charsetName), dintr-un flux de intrare
formatat, cel de-al doilea parametru specifica tipul de codificare folosit pentru a
converti fluxul de intrare la caractere
- Scanner(String), dintr-un string
- Scanner(File), dintr-un fisier
- Scanner(File sursa, String charsetName), dintr-un fisier formatat, cel de-al
doilea parametru specifica tipul de codificare folosit pentru a converti continutul
fisierului la caractere
- Scanner(Readable), dintr-un obiect Readable
- Scanner(ReadableByteChannel), dintr-un obiect ReadableByteChannel
- Scanner(ReadableByteChannel sursa, String charsetName), dintr-un obiect
ReadableByteChannel, cel de-al doilea parametru specifica tipul de codificare folosit
pentru a converti canalul de octeti la caractere
Urmatorul exemplu creaza un scanner de la fluxul standard de intrare.
import java.util.*;

public class ScanConsole {


public static void main(String[] args) {
// Declaram si initializam un Scanner de la System.in
Scanner in = new Scanner(System.in);

}
}
Iar urmatorul dintr-un FileReader
import java.util.*;
import java.io.*;

public class ScanConsole {


public static void main(String[] args) {

FileReader fileIn = new FileReader( "Scanner.txt" );


Scanner file = new Scanner(fileIn);

}
}
Pentru a citi din fluxul de intrare vom urma pasii:
- determinam daca mai exista ceva de citit folosind metoda public boolean
hasNextX(), care este supraincarcata. Printre cele mai utilizate: hasNextBoolean(),
hasNextInt()si hasNext(String), unde parametrul este o expresie regulata
- citim efectiv folosind metoda public String nextX(), care este supraincarcata.
Printre cele mai utilizate: public boolean nextBoolean(),public int
nextInt()si public String next(String), unde parametrul este o expresie
regulata. Daca nu exista niciun token se va arunca o NoSuchElementException.
Metoda blocheaza scannerul pe timpul citirii intrarii. Pe timpul blocarii metoda se
opreste si asteapta ca utilizatorul sa introduca date.
- repetam ultimii doi pasi
Un exemplu de folosire a clasei Scanner este prezentat in cele ce urmeaza:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str = " ";

System.out.println("introduceti numele si varsta sub forma nume


varsta,");
System.out.println("si apasati enter");

// scaneaza intarea si o blocheaza daca este cazul


if (in.hasNext())
str = "numele este " + in.next();

// scaneaza intarea si o blocheaza daca este cazul


if (in.hasNextInt())
str += " si varsta este " + in.nextInt();

// afiseaza rezultatul
System.out.println(str);

}
Pentru a modifica delimitatorul predefinit folosim metoda Scanner
useDelimiter(String/Pattern).
Pentru a determina delimitatorul curent folosim Pattern delimiter().
Spre exemplu apelul metodei:
in.useDelimiter("; *");
determina setarea delimitatorului la ; urmat de spatiu si apoi orice caractere.
Iesiri formatate
Incepand cu 5.0 se introduce metoda printf(). Functia poseda control asupra iesirilor
formatate. Functia scrie un string intr-un string de iesire folosind un format specificat si
argumente. Functia se foloseste cu fluxuri print, spre ex System.out sau System.err. Putem
utiliza metoda cu doua tipuri de obiecte:
- PrintStream, pentru o iesire pe octeti
- PrintWriter, pentru o iesire pe caracter
Cele doua forme ale metodei sunt:
public PrintStream printf(Locale, String, Object);
in cazul in care se precizeaza o localizare, respectiv:
public PrintStream printf(String, Object);
ce foloseste o localizare predefinita. Stringul da formatul de care se va tine cont la afisare.
Ultimele argumente corespund formatului dat in string. Putem folosi urmatoarele argumente
de format:
- %n, inserarea unui sfarsit de linie la afisare
- %e sau %E, afisarea cu exponent a numerelor zecimale. Argumentul trebuie sa fie de
tip: Double, BigDecimal sau Float
Vom da, in continuare, un exemplu de folosire a citirii cu formatare:
public static void main(String[] args) {
Date datePrinted = Calendar.getInstance().getTime();

// printeaza data completa


System.out.printf("data este: %tc\n", datePrinted);

// printeaza un string si un intreg


System.out.printf("%s are %d de ani\n", "ion", 30);

// printeaza un double sau un float


System.out.printf("greutatea este de: %3.2f kg\n", 150.537);

// printeaza un double folosind formatul stiintific


System.out.printf("inaltimea este de: %e cm\n", 180.28);

Clasa Formatter
Ne permite sa convertim stringuri, numere si date in aproape orice format. Ne permite, de
asemenea, sa formatam tipuri de date Java: byte, Calendar si BigDecimal. Se afla in
java.util. Clasa Formatter este stricta, ea afiseaza o eroare daca sesizeaza o formatare
invalida.
Exemplu de folosire a acestei clase;
public static void main(String[] args) {
int numVisitor = 100;

StringBuilder buffer = new StringBuilder();

// Construim un obiect Formatter cu localizarea US


Formatter formatter = new Formatter(buffer, Locale.US);

// Formatam un caracter, un string si un zecimal


formatter.format("%c. sunteti %s %d\n", 'A', "vizitatorul",
numVisitor);

//format() face acelasi lucru ca printf()


System.out.printf("sunteti %s %d\n", "vizitatorul", numVisitor);

System.out.println(formatter);
}
In exemplul anterior format() si printf() au acelasi rol.
Clasa Formatter converteste intrari binare in text formatat, care va fi retinut intr-un buffer.
Putem obtine continutul bufferului invocand metoda out(). Putem, de asemenea, converti
bufferul la un string apeland toString(). Clasa are urmatorii constructori frecvent utilizati:
- Formatter(), creaza un obiect general ce utilizeaza un obiect StringBuilder pentru
a stoca iesirea formatata, la locatia predefinita, specificata de JVM
- Formatter(Appendable), creaza un obiect ce stocheaza iesirea formatata intr-un
buffer
- Formatter(String) throws FileNotFoundException, creaza un obiect pentru a
stoca iesirea formatata intr-un fisier. Daca fisierul nu poate fi creat arunca o exceptie
In plus constructorii clasei pot specifica: locatia obiectului creat, setul de caractere folosit.
Cateva dintre metodele clasei:

Metoda format() este folosita pentru a formata argumentele in concordanta cu specificatorii.


In modul cel mai utilizat are doi parametri: un string ce contine o combinatie de text si
specificatori de format. Specificatorii de format sunt precedati de % urmati de un singur
caracter. Pentru fiecare specificator de format trebuie sa fie un obiect in lista de argumente.
Folosim %t si %T ca specificatori de format pentru a afisa date, cu argumente de tip Calendar,
Date, Long sau long. Urmatoarele sufixe sunt cele mai utilizate impreuna cu specificatorii de
mai sus:
- d, data curenta a lunii, formata din 2 cifre (01-31)
- r, afiseaza timpul in 12 ore (hh:mm:ss AM sau PM)
- c, afiseaza timpul in format hh:mm:ss zona an
- m, afiseaza luna, formata din 2 cifre (01-12)
- M, minutul orei, format din 2 cifre (00-59)
- b, abrevierea lunii (Jan-Dec)
- B, luna completa (Janury-December)
- I, ora curenta
Pentru numere putem specifica lungimea si precizia. Lungimea minima a reprezentarii unui
numar se face dupa % indicand valoarea, iar precizia urmeaza lungimii, separata de aceasta
prin punct.
public static void main(String[] args) {
int numVisitor = 100;
double d1 = 9.837;

StringBuilder buffer = new StringBuilder();


Formatter formatter = new Formatter(buffer, Locale.US);

// Formatul numeric octal


formatter.format("formatul octal pentru %d este %o\n", numVisitor,
numVisitor);

// Formatul numeric hexadecimal


formatter.format("formatul hexazecimal %f este %a\n", d1, d1);

System.out.println(formatter);
}
Urmatoarele flag-uri dupa procent modifica formatul:
- 0, in loc de spatii va adauga zerouri nesemnificative
- +, adauga semnul + in fata numerelor pozitive
- (, scrie numerele negative intre paranteze rotunde
- Spatiu, scrie un spatiu in fata numerelor pozitive pentru alinierea cu numerele
negative
- -, aliniere la stanga
- ,, pentru separarea miilor
public static void main(String[] args) {
Date date = new Date();
Calendar cal = Calendar.getInstance();

StringBuilder buffer = new StringBuilder();


Formatter formatter = new Formatter(buffer, Locale.US);

// afiseaza timpul in format orar de 12 ore


formatter.format("%tr\n", date);

// afiseaza informatii complete despre data/timp


formatter.format("%tc\n", cal);

// afiseaza numai orele/minutele


formatter.format("%1$tl:%1$tM\n", cal);

// afiseaza numele lunii


formatter.format("%1$tB %1$tb %1$tm\n", cal);

System.out.println(formatter);
}
Formatarea numerelor:
public static void main(String[] args) {
StringBuilder buffer = new StringBuilder();
Formatter formatter = new Formatter(buffer, Locale.US);

buffer.setLength(0);
printNums(formatter);
}

static void printNums(Formatter formatter) {


double d = -2.2834;

for (short i=1; i <= 8; i++, d += d+i) {


formatter.format("%15f %15.5f %15.2f%n", d, d*d, d*d*d);
}

System.out.println(formatter);
}

NIO2 Introducere
API-ul nio2 aduce urmatoarele imbunatatiri:
- Imbunatateste interfata File System
- Functionalitate completa Socket-Channel
- I/O asincrone scalabile
JSR 51 care sta la baza NIO API este orientat spre buffer-e, channel-uri si charset-uri. In plus
aduce socket-uri scalabile furnizand un API neblocabil si multiplexat, care permite
dezvoltarea serverelor scalabile.
Noul API:
- Functioneaza mai consistent peste platforme
- Face mai usoara scrierea programelor, care sunt in stare sa manipuleze operatiile
esuate ale sistemului de fisiere
- Furnizeaza acces mai eficient la o multime mult mai mare de atribute ale fisierului
- Permite dezvoltarea aplicatiilor sofisticate, facand posibila folosirea facilitatilor
dependente de platforma, acolo unde este absolut necesar
- Furnizeaza suport pentru ca fisierele sistem non-native sa fie „plugged in” platforma
API-ul java.io.File a prezentat de-a lungul timpului numeroase provocari pentru
dezvoltatori:
- Multe metode nu aruncau exceptii atunci cand esuau, astfel incat era imposibil de
obtinut un mesaj de eroare folositor
- Multe operatii lipseau (copierea, mutarea fisierelor, etc)
- Metoda rename nu functiona consistent pe diverse platforme
- Nu exista suport pentru link-urile simbolice
- Lipsa de suport pentru metadate precum: permisiune la fisier, proprietar de fisier,
atribute de securitate
- Accesul metadatelor de fisier era ineficient (orice apel pentru metadate rezulta intr-un
apel de sistem)
- Multe dintre metode nu se scalau (cererea unei liste mari dintr-un director aflat pe
server putea duce la intreruperea conexiunii)
- Nu era posibil scrierea de cod corect ce traversa recursiv o arborescenta de fisiere daca
erau link-uri simbolice circulare
Mai mult, IO nu a fost scris pentru a fi extins. Dezvoltatorii cereau posibilitatea de a scrie
propriile implementari de fisiere. Spre exemplu, pastrarea unui pseudofisier in memorie sau
formatarea fisierelor ca arhive.
In NIO2 fisierele si directoarele sunt reprezentate de o cale, care este o locatie absoluta sau
relativa a fisierului sau directorului.
Asa dupa cum am mai afirmat, inainte de NIO2 fisierele erau reprezentate de clasa
java.io.File. In NIO2 instante ale obiectelor java.nio.file.Path sunt folosite pentru a
reprezenta locatii relative sau absolute ale unui fisier sau director.
Fisierele sunt structuri ierarhice. Ele pot avea unul sau mai multe directoare radacina.
Calea absoluta intotdeauna contine elementul radacina si lista completa a directoarelor pentru
a localiza fisierul: /home/peter/statusReport. Toate informatiile necesare a localiza
fisierul sunt continute in string-ul de cale.
Calea relativa trebuie combinata cu o alta cale pentru a accesa un fisier: clarence/foo. Fara
alte informatii programul nu poate localiza directorul.
Obiectele sistemului fisier sunt directoare si fisiere. Unele dintre ele suporta notatia de link-
uri simbolice, notatie care mai este cunoscuta ca „symlink” sau „soft link”.

Un link simbolic este un fisier special ce serveste ca referinta pentru un alt fisier. Un link
simbolic este de obicei transparent pentru utilizator. Citirea sau scrierea unui link simbolic
este aceeasi cu citirea sau scrierea unui alt fisier sau director.
In imaginea anterioara logFile pare a fi un fisier obisnuit, dar, in realitate, este un link
simbolic catre dir/logs/homeLogFile. homeLogFile este target-ul link-ului.
In NIO2 avem urmatoarele pachete si clase:
- java.nio.file.Path, care localizeaza un fisier sau director folosind o cale
dependenta de sistem
- java.nio.file.Files, care folosind un Path executa operatii pe fisiere sau
directoare
- java.nio.file.FileSystem, care furnizeaza o interfata catre un sistem de fisiere si
un factory pentru crearea unui Path si a altor obiecte ce acceseaza sistemul de fisiere
- toate metodele ce acceseaza sistemul de fisiere arunca o IOException sau o subclasa
a sa, ce furnizeaza detalii despre cauza exceptiei
Diferenta semnificativa intre NIO2 si java.io.File este arhitectura de acces la sistemul de
fisiere. In clasa File metodele folosite pentru a manipula informatii despre cai sunt in aceeasi
clasa cu metodele utilizate pentru a scrie si citi fisiere si directoare.
In NIO2 cele doua concepte sunt separate. Caile sunt create si manipulate folosind interfata
Path, in timp ce operatiile pe fisiere si directoare sunt in responsabilitatea clasei Files, ce
opereaza doar pe obiecte Path.

Interfata Path
Furnizeaza punctul de intrare pentru manipularea fisierelor si directoarelor prin NIO2. Pentru
a obtine un obiect Path:
- obtinem o instanta a sistemului de fisiere predefinit si apoi invocam metoda
getPath().
FileSystem fs = FileSystems.getDefault();
Path p44 = fs.getPath("C:\\teste\\Steeams\\out.txt");
- Apelam metoda get() din helper-ul Paths
Path p1 = Paths.get("/home/student/./bob/file");
Obiectele Path sunt nemodificabile, o data ce au fost create nu mai pot fi schimbate.
Observatie: daca dorim sa lucram cu sistemul de fisiere predefinit, care este sistemul de
fisiere pe care JVM ruleaza, utilitarul Paths ofera metoda cea mai scurta de obtinere a lui
Path. Altfel, cand dorim sa efectuam operatii pe Path, pe un alt sistem de fisiere decat cel
default, va trebui sa obtinem mai intai instanta sistemului si apoi sa construim obiectul Path.
Observatie: sistemul de fisiere windows utilizeaza un back slash in mod predefinit. Windows
accepta ambele delimitatoare: slash si back slash. Back slash-urile in Java sunt caractere
escape, de aceea pentru a reprezenta un back slash va trebui sa-l scriem dublu.
Metodele lui Path cuprind:
- Accesul componentelor unei cai:
o Path getFileName(); returneaza end pointul acestei cai
o Path getParent(); returneaza calea parinte sau null (fisier sau director)
o getNameCount()
- Operare pe o cale: normalize(), toUri(), toAbsolutePath(), subpath(),
resolve(), relativize()
- Compararea cailor: startsWith(), endsWith(), equals()
Este bine sa gandim obiectele Path in acelasi fel ca pe obiectele String. Obiectele Path pot
fi create dintr-un singur text sau dintr-o multime de componente:
- O componenta root, ce identifica ierarhia de sistem
- Un element cu nume, cel mai indepartat de radacina, ce defineste fisierul sau
directorul catre care indica calea
- Alte elemente, separate de un caracter special sau un delimitator ce identifica numele
directoarelor ce sunt parte a ierarhiei
Exemplu:
public class PathOperationsTest {
public static void main(String[] args) {
// Normalize
System.out.println("Normalize");
Path p1 = Paths.get("/home/student/./bob/file");
Path pN1 = p1.normalize();
Path p2 = Paths.get("/home/student/bob/../sally/file");
Path pN2 = p2.normalize();
System.out.format("Normalized paths: %s%n%s%n", pN1, pN2);
// Subpath
System.out.println("Subpath");
Path p3 = Paths.get("D:\\Temp\\foo\\bar");
Path p4 = p3.subpath(0, 2);
System.out.println("Count: " + p3.getNameCount());
System.out.println(p3.getName(0));
System.out.println(p4);
// Resolve
System.out.println("Resolve");
Path p5 = Paths.get("/home/clarence/foo");
Path p6 = p5.resolve("bar");
System.out.println(p6);
Path p7 = Paths.get("foo").resolve("/home/clarence");
System.out.println(p7);
// Relativize
System.out.println("Relativize");
Path p8 = Paths.get("peter");
Path p9 = Paths.get("clarence");
Path p8Top9 = p8.relativize(p9);
Path p9Top8 = p9.relativize(p8);
System.out.println(p8Top9);
System.out.println(p9Top8);
}
}
Descrierea metodelor din exemplu:
- int getNameCount();, returneaza numarul de elemente nume ce formeaza aceasta
cale
- Path getRoot();, returneaza componenta radacina a caii
- boolean isAbsolute();, returneaza true daca calea contine un element radacina. In
Windows elementul radacina contine o litera si doua puncte. Pentru Unix true este
returnat pentru orice cale ce incepe cu slash
- Path toAbsolutePath();, returneaza calea absoluta a caii
- java.net.URI toUri();, returneaza URI-ul absolut
Multe sisteme de fisiere utilizeaza notatia . pentru a marca directorul curent si .. pentru
directorul parinte. Putem intalni astfel situatii in care Path contine informatii redundante.
Metoda normalize() elimina orice elemente redundante. Este de retinut ca metoda nu
verifica sistemul in momentul in care modifica calea, este o operatie pur sintactica.
O portiune a unei cai poate constitui o subcale si se poate obtine prin metoda: Path
subpath(int beginIndex, int endIndex);. Elementul cel mai apropiat de radacina are
indexul 0. Elementul cel mai indepartat de radacina are indexul count-1. Rezultatul este o cale
ce cuprinde elementele ce incep de la beginIndex pina la endIndex-1.
Metoda resolve() este utilizata pentru a combina doua cai. Ea accepta o cale partiala, care
este calea ce nu include elementul radacina, cale ce este adaugata caii originale. Daca
trimitem ca parametru o cale absoluta metoda returnea calea trimisa.
Metoda relativize() ne permite sa construim o cale de la o locatie din sistem la o alta
locatie. Noua cale este relativa la cea originala.
Pachetul java.nio.file si interfata Path sunt „link aware”. Orice metoda poate detecta ce
are de facut atunci cand se intalneste un link simbolic sau ofera posibilitatea de configurare a
comportamentului metodei atunci cand se intalneste un link simbolic.

Unele sisteme de fisiere suporta link-urile hard. Link-urile hard sunt mult mai restrictive
decat link-urile simbolice:
- target-ul link-ului trebuie sa existe
- link-urile hard nu sunt in general permise directoarelor
- hard link-urile nu sunt permise pe partitii sau volume
- un link hard arata si se comporta ca un fisier regulat, de aceea este greu de gasit
- un link hard este aceeasi entitate ca un fisier original. Toate atributele sunt identice
Datorita acestor restrictii hard link-urile nu sunt folosite la fel de des precum link-urile
simbolice.

Clasa Files
Este entry point-ul primar pentru operatii pe obiecte Path. Metodele statice ale clasei citesc,
scriu sau manipuleaza fisiere si directoare reprezentate de obiecte Path. Clasa este, de
asemenea, link aware.
Obiectele Path reprezinta conceptul de locatie e unui fisier sau director. Inainte de a accesa un
fisier sau director va trebui sa accesam sistemul de fisiere pentru a determina daca acesta
exista:
- exists(Path p, LinkOption...option), care testeaza daca fisierul exista. In mod
implicit sunt urmate link-urile simbolice
- notExists(Path p, LinkOption...option), care testeaza daca fisierul nu exista.
In mod implicit sunt urmate link-urile simbolice
Exemplu:
Path p = Paths.get("D:\\Temp\\foo\\bar");
System.out.format("Path %s exists: %b%n", p,
Files.exists(p, LinkOption.NOFOLLOW_LINKS));
Cand testam existenta unui fisier avem urmatoarele posibilitati:
- fisierul este verificat daca exista
- fisierul este verificat ca nu exista
- statutul fisierului este necunoscut. Acest rezultat poate apare atunci cand programul
nu are acces la fisier
Observatie: !Files.exists(path) nu este echivalent cu Files.notExists(path). Daca ambele
returneaza false, existenta fisierului sau directorului nu poate fi determinata. In Windows,
spre exemplu, aceasta poate fi obtinuta atunci cand cerem statutul unui drive off-line, precum
CD-ROM-ul.
Pentru a verifica ca un fisier poate fi accesat avem urmatoarele metode booleene:
isReadable(Path), isWritable(Path), isExecutable(Path). Aceste operatii nu sunt
atomice, cu respectarea altor operatii pe sistemul de fisiere. De aceea rezultatul acestor
metode nu este sigur pentru ca poate fi modificat imediat dupa incheierea metodei.
Metoda isSameFile(Path, Path) testeaza daca doua cai indica catre acelasi fisier. Aceasta
este folositor in sisteme ce suporta link-uri simbolice.
Fisierele si directoarele pot fi create folosind una dintre metodele:
Files.createFile(Path), Files.createDirectory(Path). Metoda
Files.createDirectories(Path) poate fi utilizata pentru a crea directoare ce nu exista, de
sus in jos.
Putem sterge fisiere, directoare sau link-uri. Metoda Files.delete(Path) sterge fisierul sau
arunca o exceptie in caz de esec. Spre exemplu, daca fisierul nu exista arunca o
NoSuchFileException. Metoda Files.deleteIfExists(Path) sterge fisierul, dar nu
arunca exceptii. Aceasta metoda este utila in cazul firelor de executie.
Putem copia un fisier sau director prin Files.copy(Path, Path, CopyOption...). Cand
directoarele sunt copiate, fisierele continute nu sunt copiate. Copierea esueaza daca fisierul
destinatie exista, mai putin atunci cand specificam optiunea REPLACE_EXISTING. Cand
copiem un link simbolic destinatia link-ului este copiata. Daca dorim sa copiem link-ul insusi,
si nu continutul sau, specificam optiunea NOFOLLOW_LINKS sau REPLACE_EXISTING. In cazul
folosirii optiunii REPLACE_EXISTING in cazul copierii unui director nevid, copierea esueaza
cu aruncarea FileAlreadyExistsException. Optiunea COPY_ATTRIBUTES determina
copierea atributelor de fisier asociate, fisierului destinatie.
Avem la dispozitie doua metode de copiere dintr-un flux intr-un fisier: copy(InputStream
source, Path target, CopyOption...options), respeciv dintr-un fisier intr-un stream:
copy(Path source, OutputStream out).
Exemplu de folosire a metodei de copiere dintr-o pagina web intr-un fisier:
Path path = Paths.get("C:/oracle.html");
URI u = URI.create("http://www.oracle.com/");
try (InputStream in = u.toURL().openStream()) {
Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
Exemplul de dinainte poate fi extins pentru a scrie un fisier intr-un socket sau alte tipuri de
fluxuri.
Putem muta un fisier sau director prin utilizarea metodei: Files.move(Path, Path,
CopyOption...). La mutare trebuie sa tinem cont de urmatoarele:
- daca destinatia este un director si directorul este vid, mutarea reuseste daca este setat
REPLACE_EXISTING
- daca directorul destinatie nu exista, mutarea reuseste. Aceasta este de fapt o
redenumire a directorului
- daca directorul exista si nu este vid este aruncata o DirectoryNotEmptyException
- daca sursa este un fisier si destinatia este un director care exista, REPLACE_EXISTING
este setat, mutarea va redenumi fisierul la numele directorului
Pentru a muta un director ce contine fisiere intr-un alt director trebuie sa copiem recursiv
continutul directorului si apoi sa stergem vechiul director.
Putem efectua mutarea ca pe o operatie atomica folosind ATOMIC_MOVE. Daca sistemul de
fisiere nu suporta mutarea atomica se va arunca o exceptie. Cu mutarea atomica putem muta
un fisier intr-un director si avem garantia ca un proces ce urmareste directorul acceseaza
fisierul intreg.
Clasa DirectoryStream furnizeaza un mecanism pentru a itera peste toate intrarile dintr-un
director. Clasa se scaleaza pentru a suporta foarte multe directoare. Exceptia
DirectoryIteratorException este aruncata daca apar eori I/O pe timpul iterarii. Exceptia
PatternSyntaxException este aruncata daca pattern-ul furnizat ca al doilea argument este
invalid.
public class ListDirectory {
public static void main(String[] args) {
Path dir = Paths.get("c:/chestii");
String filter = "*";
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter))
{
for (Path file : stream) {
System.out.println(file.getFileName());
}
} catch (PatternSyntaxException | DirectoryIteratorException | IOException
x) {
//IOException can never be thrown by the iteration.
//In this snippet, it can only be thrown by newDirectoryStream.
System.err.println(x);
}
}
}
Daca avem un fisier de dimensiuni mici si dorim sa-i citim intregul continut intr-un singur pas
putem folosi metodele Files.readAllBytes(Path) sau Files.readAllLines(Path,
Charset). Aceste metode gestioneaza intreaga operatiune precum inchiderea si deschiderea
stream-ului.
Putem folosi una dintre metodele de scriere pentru a scrie octeti sau linii intr-un fisier:
write(Path, byte[], OpenOption...), respectiv write(Path, Iterable<? extends
CharSequence>, Charset, OpenOption...).

Channel-uri si byte buffer-i


Streamurile citesc un caracter o data, in timp ce channel-urile citesc o data un buffer. Interfata
ByteChannel furnizeaza functionalitatea de baza pentru scriere si citire. Un
SeekableByteChannel este un ByteChannel ce are capabilitatea sa mentina o pozitie in
canal si sa o schimbe.
Capabilitatea de miscare la diferite puncte ale fisierului si apoi citirea de la sau scrierea la
acea locatie face posibil accesul random la acel fisier. Avem doua metode de scriere si citire
in canal:
- newByteChannel(Path, OpenOperation...), ce returneaza un
SeekableByteChannel. Putem converti acest rezultat la un FileChannel, ce
furnizeaza acces la facilitati mai avansate precum: maparea unei regiuni a fisierului
direct in memorie pentru acces rapid, blocarea unei regiuni a fisieruluiastfel incat alte
procese sa nu o poata accesa, citirea sau scrierea octetilor de la o pozitie absoluta fara
a afecta pozitia curenta a canalului
- newByteChannel(Path, Set<? Extends OpenOption>, FileAttribute<?>...)
Exemplu:
public class ByteChannelCopyTest {
public static void main(String[] args) {
try (FileChannel fcIn = new FileInputStream("file.in").getChannel();
FileChannel fcOut = new FileOutputStream("file.out")
.getChannel()) {
System.out.println("File size: " + fcIn.size());
// Create a buffer to read into
ByteBuffer buff = ByteBuffer.allocate((int) fcIn.size());
System.out.println("Bytes remaining: " + buff.remaining());
System.out.println("Bytes read: " + fcIn.read(buff));
buff.position(0);
System.out.println("Buffer: " + buff);
System.out.println("Bytes remaining: " + buff.remaining());
System.out.println("Wrote: " + fcOut.write(buff) + "
bytes");
} catch (FileNotFoundException f) {
System.out.println("File not found: " + f);
} catch (IOException e) {
System.out.println("IOException: " + e);
}
}
}
Fisierele cu acces aleator permit acces non-secvential la continutul lor. Pentru a accesa un
fisier aleator trebuie sa deschidem fisierul, sa ne pozitionam pe o anumita locatie si sa citim
sau scrie acolo. Toata aceasta functionalitate este furnizata de interfata
SeekableByteChannel.
Dupa cum am mai spus aceasta innterfata extinde canalul cu notiunea de pozitie curenta.
Metodele ne permit sa setam sau sa interogam pozitia:
- position(), returneaza pozitia curenta a canalului
- position(long), seteaza pozitia canalului
- read(ByteBuffer), citeste octeti in buffer de la canal
- write(ByteBuffer), scrie octeti din buffer la canal
- truncate(long), truncheaza fisierul conectat la canal
Metoda Files.newBufferedReader(Path, Charset) deschide un fisier pentru citire,
returnand un BufferedReader, utilizat pentru citirea textului dintr-un fisier intr-o maniera
eficienta.
Metoda Files.newBufferedWriter(Path, Charset) deschide un fisier pentru scriere,
returnand un BufferedWriter, utilizat pentru scrierea textului intr-un fisier intr-o maniera
eficienta.
NIO2 suporta si urmatoarele metode pentru a deschide stream-uri de octeti:
InputStream in = Files.newInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
line = reader.readLine();
Pentru a crea un fisier, adauga intr-un fisier putem utiliza urmatoarea secventa de cod:
Path logfile = ...;
String s = ...;
byte data[] = s.getBytes();
OutputStream out =
new BufferedOutputStream(file.newOutputStream(CREATE, APPEND);
out.write(data, 0, data.length);

Gestiunea Metadatelor
Daca un program necesita mai multe atribute ale fisierului in acelasi timp este ineficient sa
folosim metode ce returneaza un singur atribut. Accesul repetat la sistemul de fisiere pentru a
obtine un singur atribut poate afecta performanta. De aceea clasa Files furnizeaza doua
metode de obtinere a atributelor unui fisier intr-o singura operatie:
- readAttributes(Path, String, LinkOption...)
- readAttributes(Path, Class<A>, LinkOption...)
Exemplu:
public class FileAttributes {

public static void main(String[] args) {


DosFileAttributes attrs = null;

Path file = Paths.get("file.in");


try {
attrs = Files.readAttributes(file, DosFileAttributes.class);
} catch (IOException e) {
System.out.println("Exception reading attributes of the file:
" + e);
}
System.out.println(file);
System.out.println("Creation time: " + toDate(attrs.creationTime()));
System.out
.println("Last Modified: " +
toDate(attrs.lastModifiedTime()));
System.out.println("Last Access: " +
toDate(attrs.lastAccessTime()));
if (!attrs.isDirectory()) {
System.out.println("Size (K Bytes):" + (attrs.size() / 1024));
}
System.out.println("DOS File information:");
System.out.format(
"Archive: %b Hidden: %b ReadOnly: %b System File: %b%n",
attrs.isArchive(), attrs.isHidden(), attrs.isReadOnly(),
attrs.isSystem());
}

// Utility method to print a better formatted time stamp


public static String toDate(FileTime ft) {
return DateFormat.getInstance().format(new Date(ft.toMillis()))
.toString();
}
}
DOS poate modifica atribute dupa ce fisierul a fost creat prin metoda setAttribute().
Cu NIO2 putem crea fisiere si directoare pe sisteme POSIX (Portable Operating System
Interface) cu setari initiale de permisiune. Aceasta rezolva o problema in programare atunci
este creat un fisier, si anume ca permisiunea pe fisier poate fi modificata inainte ca
urmatoarea executie sa seteze permisiunea.
Permisiunea poate fi setata doar in sisteme ce implementeaza POSIX precum MacOS, Linux
si Solaris. Windows bazat pe DOS nu implementeaza POSIX. Fisierele si directoarele DOS
nu au permisiuni ci atribute de fisier.
Putem determina daca sistemul suporta POSIX programat prin cautarea atributelor suportate
de fisier.
public class PosixFileTest {
public static void main(String[] args) {
boolean unixFS = false;
Set<String> views = FileSystems.getDefault()
.supportedFileAttributeViews();
for (String s : views) {
System.out.println(s);
if (s.equals("posix")) {
unixFS = true;
}
}
if (!unixFS) {
System.out
.println("This filesystem does not support Posix
permissions.");
System.exit(-1);
}
}
}

Operatii recursive
Clasa Files furnizeaza o metoda de parcurgere a arborelui de fisiere pentru operatii recursive
precum copierea si stergerea.
Interfata FileVisitor contine metode ce sunt invocate pe fiecare nod vizitat dintr-o
arborescenta de fisiere:
- preVisitDirectory(Path, BasicFileAttributes), invocata pe un director inainte
ca intrarile in director sa fie vizitate
- visitFile(Path, BasicFileAttributes), invocata pentru un fisier intr-un director
- postVisitDirectory(Path, BasicFileAttributes), invocata dupa ce toate
intrarile din director si descendentii lor au fost vizitati
- visitFileFailed(Path, BasicFileAttributes), invocata pentru un fisier ce nu
poate fi vizitat
Rezultatul returnat de fiecare dintre metode determina actiunile ce urmeaza a se lua dupa ce
un nod a fost atins (pre sau post). Aceste actiuni sunt enumerate in clasa FileVisitResult:
- CONTINUE, continua cu nodul urmator
- SKIP_SIBLINGS, continua fara vizitarea fratilor acestui fisier sau director
- SKIP_SUBTREE, continua fara vizitarea intrarilor din acest director
- TERMINATE
Exista o clasa, SimpleFileVisitor, ce implementeaza metodele din FileVisitor cu tipul
returnat FileVisitResult.CONTINUE sau aruncarea unei IOException.
Pornind de la primul nod director si continuand cu fiecare subdirector intalnit metoda
preVisitDirectory() este invocata pe clasa trimisa metodei walkFileTree().
Presupunand ca rezultatul returnat este FileVisitResult.CONTINUE, urmatorul nod este
explorat.
Observatie: traversarea arborescentei de fisiere este in adancime cu FileVisitor invocat
pentru fiecare fisier intalnit. Traversarea arborescentei se incheie cand toate fisierele
accesibile din arborescenta au fost vizitate sau o metode de vizitare returneaza TERMINATE.
Cand o metoda de vizitare se termina din cauza unei I OException, o eroare sau o exceptie
runtime, traversarea se termina si eroarea sau exceptia este propagata apelentului metodei.
Cand este intalnit un fisier in arborescenta, metoda walkFileTree() incearca sa-i citeasca
BasicFileAttributes. Daca fisierul nu este director metoda visitFile() este invocata cu
atributele de fisier. Daca atributele de fisier nu pot fi citite metoda visitFileFailed() este
invocata cu o exceptie I/O.
Dupa vizitarea tuturor descendentilor unui nod metoda postVisitDirectory() este invocata
pe fiecare director.
In exemplul urmator, clasa PrintTree implementeaza fiecare metoda din FileVisitor si
afiseaza tipul, numele si marimea directorului si fisierului la fiecare nod.
public class PrintTree implements FileVisitor<Path> {

// Print information about the root path


@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes
attr) {
System.out.print("preVisitDirectory: ");
if (attr.isDirectory()) {
System.out.format("Directory: %s ", dir);
} else {
System.out.format("Other: %s ", dir);
}
System.out.println("(" + attr.size() + " bytes)");
return CONTINUE;
}
// Print information about each type of file.
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
System.out.print("visitFile: ");
if (attr.isSymbolicLink()) {
System.out.format("Symbolic link: %s ", file);
} else if (attr.isRegularFile()) {
System.out.format("Regular file: %s ", file);
} else {
System.out.format("Other: %s ", file);
}
System.out.println("(" + attr.size() + " bytes)");
return CONTINUE;
}

// Print each directory visited.


@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
System.out.print("postVisitDirectory: ");
System.out.format("Directory: %s%n", dir);
return CONTINUE;
}

// If there is some error accessing the file, let the user know.
// If you don't override this method and an error occurs, an IOException
// is thrown.
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.out.print("vistiFileFailed: ");
System.err.println(exc);
return CONTINUE;
}
}

public class WalkFileTreeTest {

public static void main(String[] args) {

Path path = Paths.get("c:/chestii");


if (!Files.isDirectory(path)) {
System.out.println("c:/chestii" + " must be a directory!");
System.exit(-1);
}
try {
Files.walkFileTree(path, new PrintTree());
} catch (IOException e) {
System.out.println("Exception: " + e);
}
}
}

Gasirea fisierelor
Pentru a gasi un fisier, in mod normal, cautam un director. Putem utiliza un instrument de
cautare sau o comanda precum: dir /s *.java. Aceasta comanda va cauta recursiv in
arborescenta de directoare, incepand cu elementul in care ne aflam, toate fisierele ce contin
extensia java.
Interfata java.nio.file.PathMatcher include o metoda de cautare pentru a determina daca
obiectul Path se potriveste unui string de cautare. Orice sistem de fisiere furnizeaza un
PathMatcher ce poate fi obtinut din factory-ul FileSystems.
(FileSystems.getDefault().getPathMatcher(String syntaxAndPattern))
String-ul argument este de forma syntax:pattern, unde syntax poate fi glob sau regex.
Sintaxa glob este similara expresiilor regulate, dar mai simpla. Urmatoarele reguli sunt
utilizate pentru a interpreta pattern-ii glob:
- Caracterul * potriveste zero sau mai multe caractere unui nume fara a depasi limitele
directorului
- Caracterele ** potriveste zero sau mai multe caractere depasind limitele directorului
- ? potriveste exact un caracter al numelui
- \ este utilizat pentru caractere escape
- [] formeaza o expresie ce potriveste un singur caracter al numelui dintr-o multime de
caractere. Spre exemplu[abc] potriveste a, b sauc. – poate fi utilizat pentru a specifica
un doemniu. Spre exemplu [a-z] specifica domeniul, inclusiv capetele, de la a la z.
Putem avea si [abce-g] insemnand a, b, c, e, f sau g. ! este folosit pentru negare. Spre
exemplu [!a-c] potriveste orice caracter mai putin a, b sau c
- Intr-o expresie cu paranteze caracterele *, ? si \ se potrivesc lor insele. – se potriveste
lui insusi daca este primul caracter sau imediat dupa !
- {} formeaza un grup de subpattern-uri, unde grupul se potriveste daca orice subpattern
din grup se potriveste. , este folosita pentru a separa subpattern-urile. Grupurile nu pot
fi incuibate
- . in numele unui fisier este tratat ca un caracter obisnuit in expresia de potrivire
Cand syntax este regex componenta pattern este o expresie regulata asa cum a fost definita de
clasa Pattern.
Exemplu:
public class Finder extends SimpleFileVisitor<Path> {

private Path file;


private PathMatcher matcher;
private int numMatches;

public Finder(Path file, PathMatcher matcher) {


this.file = file;
this.matcher = matcher;
}

//Compares the glob pattern against the file or directory name.


private void find(Path file) {
Path name = file.getFileName();
if (name != null && matcher.matches(name)) {
numMatches++;
System.out.println(file);
}
}

//Prints the total number of matches to standard out.


public void done() {
System.out.println("Matched: " + numMatches);
}

//Invoke the pattern matching method on each file.


@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
find(file);
return CONTINUE;
}
//Invoke the pattern matching method on each directory.
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
{
find(dir);
return CONTINUE;
}
}

public class FindFileTest {

public static void main(String[] args) {


String r = "c:/chestii";
Path root = Paths.get(r);
if (!Files.isDirectory(root)) {
System.out.println(r + " must be a directory!");
System.exit(-1);
}

PathMatcher matcher = FileSystems.getDefault().getPathMatcher(


"glob:" + "*.doc");
Finder finder = new Finder(root, matcher);
try {
Files.walkFileTree(root, finder);
} catch (IOException e) {
System.out.println("Exception: " + e);
}
finder.done();
}
}

In clasa ce contine metoda main() avem doua argumente necesare cautarii. Primul este
radacina arborescentei in care se face cautarea si este testat pentru a fi director. Al doilea este
utilizat pentru a crea un PathMatcher cu o expresie regulata utilizand un factory
FileSystems.
Clasa Finder implementeaza interfata FileVisitor, astfel incat sa poata fi trimisa metodei
walkFileTree(). Aceasta clasa este utilizata pentru a apela metoda de potrivire pe fiecare
dintre fisierele vizitate in arborescenta.

Alte clase in NIO2


Clasa FileStore este folosita pentru a furniza operatii de utilizare ale sistemului de fisiere,
precum spatiul de disk total, utilizabil sau alocat.
O instanta a interfetei WatchService poate fi utilizata pentru a raporta modificari de
inregistrat obiectelor Path. WatchService poate fi utilizata pentru a identifica cand fisierele
sunt adaugate, sterse sau modificate intr-un director.
Exemplu:
public class DiskUsage {
static final long K = 1024;
static void printFileStore(FileStore store) throws IOException {
long total = store.getTotalSpace() / K;
long used = (store.getTotalSpace() - store.getUnallocatedSpace()) /
K;
long avail = store.getUsableSpace() / K;

String s = store.toString();
if (s.length() > 20) {
System.out.println(s);
s = "";
}
System.out.format("%-20s %12d %12d %12d\n", s, total, used, avail);
}

public static void main(String[] args) throws IOException {


System.out.format("%-20s %12s %12s %12s\n", "Filesystem", "kbytes",
"used", "avail");
FileSystem fs = FileSystems.getDefault();
for (FileStore store : fs.getFileStores()) {
printFileStore(store);
}
}
}
Si un exemplu de folosire a observatorilor:
public class WatchDir {

private final WatchService watcher;


private final Map<WatchKey, Path> keys;
private final boolean recursive;
private boolean trace = false;

// An example of a Generic method


@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>) event;
}

/**
* Register the given directory with the WatchService
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE,
ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev,
dir);
}
}
}
keys.put(key, dir);
}

/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {

@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
register(dir);
return FileVisitResult.CONTINUE;
}
});
}

/**
* Creates a WatchService and registers the given directory
*/
WatchDir(Path dir, boolean recursive) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<>();
this.recursive = recursive;

if (recursive) {
System.out.format("Scanning %s ...\n", dir);
registerAll(dir);
System.out.println("Done.");
} else {
register(dir);
}

// enable trace after initial registration


this.trace = true;
}

/**
* Process all events for keys queued to the watcher
*/
void processEvents() {
for (;;) {

// wait for key to be signalled


WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}

Path dir = keys.get(key);


if (dir == null) {
System.err.println("WatchKey not recognized!!");
continue;
}

for (WatchEvent<?> event : key.pollEvents()) {


Kind<?> kind = event.kind();

// TBD - provide example of how OVERFLOW event is


handled
if (kind == OVERFLOW) {
continue;
}

// Context for directory entry event is the file name of


entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = dir.resolve(name);

// print out event


System.out.format("%s: %s\n", event.kind().name(),
child);
// if directory is created, and watching recursively,
then
// register it and its sub-directories
if (recursive && (kind == ENTRY_CREATE)) {
try {
if (Files.isDirectory(child,
NOFOLLOW_LINKS)) {
registerAll(child);
}
} catch (IOException x) {
// ignore to keep sample readbale
}
}
}

// reset key and remove from set if directory no longer


accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);

// all directories are inaccessible


if (keys.isEmpty()) {
break;
}
}
}
}

public static void main(String[] args) throws IOException {


boolean recursive = false;

Path dir = Paths.get("c:/chestii");


new WatchDir(dir, recursive).processEvents();
}
}

In java.io.File a fost adaugata o metoda toPath() pentru a oferi compatibilitatea cu NIO2


(Path path = file.toPath()). Mai mult, putem inlocui codul existent pentru a avea o
mentenanta imbunatatita: file.delete() va fi inlocuit cu:
Path path = file.toPath();
Files.delete(path);
Invers, Path furnizeaza metoda pentru a construi un File: File file = path.toFile();

Fire de executie
Sistemele de operare moderne folosesc multitaskuri preventive pentru a aloca timp pe
procesor pentru aplicatii. Avem doua tipuri de task-uri ce pot fi programate pentru executie:
- Procesele: un proces este o zona de memorie ce contine cod si date. Un proces are un
fir de executie ce este programat sa primeasca procesorul pentru portiuni de timp.
Portiunea de timp (time slice) este de obicei masurata in milisecunde. Cand timpul a
expirat taskul este in mod fortat indepartat de pe procesor si un alt task ii ia locul
- Fire: un fir este o executie programata a unui proces. Firele pot fi concurente. Toate
firele unui proces impart aceleasi date de memorie, dar pot urmari diferite cai catre o
sectiune a codului
Pentru a executa un program cat mai repede cu putinta trebuie sa evitam blocajele
(bottleneck). Exista cateva situatii care descriu aceste blocaje:
- Conexiuni la resurse: doua sau mai multe task-uri asteapta pentru utilizarea exclusiva
a unei resurse
- Blocarea din cauza operatiilor de I/O: pe timpul acestor operatii se asteapta transferul
datelor
- Subutilizarea procesorului: o aplicatie intr-un singur fir utilizeaza un singur procesor
Chiar daca nu scriem explicit cod pentru a crea noi fire de executie, codul poate rula intr-un
mediu multifir. Trebuie asadar sa fim avertizati cum lucreaza firele si cum sa scriem cod
thread-safe. Spre exemplu, cand scriem code care ruleaza intr-un alt software (middleware
sau server de aplicatie) trebuie avuta in vedere documentatia produsului pentru a descoperii
daca firele sunt create automat. Intr-un server de aplicatie Java EE exista o componenta
numita servlet utilizata pentru manipularea cererilor HTTP. Servlet-urile trebuie sa fie
intotdeauna thread-safe deoarece serverul porneste un nou fir la fiecare cerere HTTP.

Clasa Thread
Este folosita pentru a crea si porni fire. Codul ce urmeaza a fi executat de un fir trebuie plasat
intr-o clasa ce face una din urmatoarele:
- Extinde clasa Thread (codarea este mai simpla). Va trebui sa suprascriem metoda
run().
- Implementeaza interfata Runnable (mult mai flexibil)
Exemplu:
public class ExampleThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("i:" + i);
}
}
}
Codul de executat in noul fir trebuie plasat in metoda run(). Trebuie sa evitam apelul lui
run() direct. Apelul lui run() nu va porni un nou fir, efectul fiind acelasi al apelului unei
metode obisnuite.
Dupa crearea firului, el trebuie pornit prin apelul metodei start():
public class ExtendingThreadMain {

public static void main(String[] args) {


ExampleThread t1 = new ExampleThread();
t1.start();
}
}
Cand apelam start() se programeaza pentru apel metoda run(). Un fir poate fi pornit doar
o data.
Implementand Runnable suntem obligati sa furnizam cod pentru run(). Beneficiul
implementarii lui Runnable este acela ca inca putem extinde o alta clasa.
public class ExampleRunnable implements Runnable {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i:" + i);
}
}
}
Dupa ce am creat un Runnable trebuie sa il trimitem constructorului lui Thread. Doar Thread
poate porni firul:
public class ImplementingRunnableMain {
public static void main(String[] args) {
ExampleRunnable r1 = new ExampleRunnable();
Thread t1 = new Thread(r1);
t1.start();
}
}
Campurile statice sau campurile instanta sunt potential impartite intre fire.
public class ExampleSharedRunnable implements Runnable {

private int i;

@Override
public void run() {
for (i = 0; i < 100; i++) {
System.out.println("i:" + i);
}
}
}
Obiectele care sunt referite de mai multe fire au campurile accesate concurent.
public class SharedDataMain {

public static void main(String[] args) {


ExampleSharedRunnable r1 = new ExampleSharedRunnable();
Thread t1 = new Thread(r1);
t1.start();
Thread t2 = new Thread(r1);
t2.start();
}
}
In exemplu am trimis aceasi instanta Runnable instantelor multiple Thread. Instantele
Thread impart campurile instanta Runnable. Acelasi comportament il au si campurile statice.
Datele share-uite trebuie accesate cu grija. Campurile instanta si cele statice:
- Sunt create intr-o zona de memorie cunoscuta ca spatiul heap
- Pot fi potential impartite intre orice fire
- Pot fi modificate concurent de mai multe fire. Nu exista erori de compilare sau
avertizari ale IDE-urilor. Accesul sigur al campurilor share-uite sta in grija
programatorului
Debugging-ul firelor poate fi dificil deoarece frecventa si durata in care procesorul este alocat
firului pot varia din multiple motive:
- Programatorul firului de executie este gestionat de un sistem de operare si sistemele
de operare pot utiliza diversi algoritmi de programare
- Masinile pot avea diverse viteze ale procesorului
- Alte aplicatii pot incarca sistemul
De aceea este posibil ca o aplicatie sa functioneze corect la dezvoltare si sa aiba probleme in
productie.
Unele variabile nu sunt vreodata share-uite, ele sunt in permanenta type-safe: variabilele
locale, parametrii metodelor, parametrii manipulatorilor de exceptie. De asemenea, datele
imutabile, precum obiectele string sau campurile finale, sunt thread-safe deoarece ele pot fi
citite dar nu pot fi scrise.

Operatii atomice
O operatie atomica functioneaza ca o singura operatie. Spre exemplu, o instructiune in Java
nu este intotdeauna atomica. Spre exemplu:
i++; - creaza o copie temporara a lui i, incrementeaza copia si apoi scrie noua valoare inapoi
in i.
l = 0xffff_ffff_ffff_ffff; - este o variabila de 64 de biti ce poate fi accesata folosind
doua operatii separate pe 32 de biti.
O problema posibila cu doua fire de executie ce incrementeaza acelasi camp este ca
modificarea poate fi pierduta. Sa presupunem ca doua fire de executie citesc valoarea 41
dintr-un camp, incrementeaza valoarea cu unu si scriu rezultatul inapoi in camp. Ambele fire
incheie incrementarea dar rezultatul este doar 42. Dependent de cum este implementata JVM
si de tipul de procesor utilizat, putem obtine aceasta situatie rar sau niciodata, dar trebuie avut
in vedere ca ea se poate intampla.
Daca avem un numar lung 0xffff_ffff_ffff_ffff si il incrementam cu 1, rezultatul ar
trebui sa fie 0x0000_0001_0000_0000. Din cauza operatiei ce se face in doua etape putem
avea 0x0000_0001_ffff_ffff sau 0x0000_0000_0000_0000 dependent de care biti se
modifica primii. Daca un al doilea fir are permisiunea sa citeasca un camp de 64 de biti in
timp ce este modificat de un alt fir, o valoare incorecta poate fi obtinuta.
Operatiile efectuate intr-un fir pot sa nu apara la executie in ordine daca vom observa
rezultatele dintr-un alt fir. Aceasta deoarece firele opereaza pe copii cache ale variabilelor
sharuite.
Ca sa ne asiguram de consistenta comportamentului in fire, trebuie sa sincronizam actiunile
lor, adica sa ne asiguram ca o actiune se intampla inaintea alteia, respectiv sa golim cache-ul
variabilelor share-uite.
Fiecare fir are o memorie de lucru in care isi tine copiile de lucru ale variabilelor. Cand firul
executa un program opereaza pe aceste copii. Exista cateva actiuni ce sincronizeaza memoria
de lucru a firului cu memoria principala:
- Citirea sau scrierea volatila a unei variabile
- Blocarea sau deblocarea
- Prima si ultima actiune a unui fir
- Actiuni ce pornesc un fir sau detecteaza ca un fir s-a terminat
Un camp poate fi declarat ca volatile. Scrierea sau citirea unui camp volatil va determina ca
firul sa-si sincronizeze memoria de lucru cu memoria principala. Volatil nu inseamna, insa,
atomic, adica operatia in care este implicat campul nu este thread safe.
Un fir se opreste prin incheierea metodei run().
Vom da un exemplu de folosire a campurilor volatile pentru oprirea unui fir de executie.
public class ExampleVolatileRunnable implements Runnable {

public boolean timeToQuit = false;

@Override
public void run() {
System.out.println("Thread started");
while (!timeToQuit) {
System.out.println("thread running");
}
System.out.println("Thread finishing");
}
}

public class ThreadStoppingMain {

public static void main(String[] args) {


ExampleVolatileRunnable r1 = new ExampleVolatileRunnable();
Thread t1 = new Thread(r1);
t1.start();
// ...
r1.timeToQuit = true;
}
}
Metoda main() in aplicatiile Java SE este executata intr-un fir, numit si firul principal, ce este
dreat automat de JVM. Ca orice fir si firul principal, atunci cand scrie in campul timeToQuit
este important ca scrierea sa fie vazuta de firul t1. Daca timeToQuit nu ar fi fost volatil nu
exista garantia ca scrierea s-ar fi vazut imediat.
Observatie: este foarte posibil ca si fara declararea volatila aplicatia sa functioneze perfect,
dar nu exista garantia ca aceasta s-ar fi intamplat tot timpul.
Cuvantul synchronized este utilizat pentru a crea blocuri de cod thread-safe. Un bloc
sincronizat:
- Determina firul sa scrie toate modificarile in memoria principala atunci cand s-a atins
sfarsitul firului. Acest comportament este similar cu volatile
- Este utilizat pentru gruparea blocurilor de cod in vederea utilizarii exclusive de catre
un fir. Acest comportament rezolva si problema atomicitatii.
Fie urmatorul exemplu:
public class Item {

private int id;


private String description;
private double price;

public Item() {
}

public Item(int id, String description, double price) {


this.id = id;
this.description = description;
this.price = price;
}

/**
* @return the id
*/
public int getId() {
return id;
}

/**
* @param id
* the id to set
*/
public void setId(int id) {
this.id = id;
}

/**
* @return the description
*/
public String getDescription() {
return description;
}

/**
* @param description
* the description to set
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return the price
*/
public double getPrice() {
return price;
}

/**
* @param price
* the price to set
*/
public void setPrice(double price) {
this.price = price;
}
}

public class ShoppingCart {

private List<Item> cart = new ArrayList<>();

public synchronized void addItem(Item item) {


cart.add(item);
}

public synchronized void removeItem(int index) {


cart.remove(index);
}

public synchronized void printCart() {


Iterator<Item> ii = cart.iterator();
while(ii.hasNext()) {
Item i = ii.next();
System.out.println("Item:" + i.getDescription());
}
}

// public void printCart() {


// StringBuilder sb = new StringBuilder();
// synchronized (this) {
// Iterator<Item> ii = cart.iterator();
// while (ii.hasNext()) {
// Item i = ii.next();
// sb.append("Item:");
// sb.append(i.getDescription());
// sb.append("\n");
// }
// }
// System.out.println(sb.toString());
// }
}
In obiectul ShoppingCart putem apela doar o metoda la un moment dat, deoarece toate
metodele sunt sincronizate. Doua instante ShoppingCart nu pot fi utilizate concurent.
Practic, sincronizarea se extinde intregului obiect.
Daca metodele nu ar fi fost sincronizate, apelul lui removeItem() in timp ce printCart()
itereaza prin colectia de Item poate crea rezultate impredictibile. Un iterator care esueaza va
arunca un java.util.ConcurrentModificationException, o exceptie runtime, daca
iteratorul colectiei este modificat in timp ce este utilizat (vezi metoda comentata pentru
exemplu de folosire a unui bloc sincronizat).
Sincronizarea in aplicatii multifir asigura un comportament consistent . Deoarece blocurile si
metodele sincronizate sunt folosite pentru a restrictiona o sectiune de cod pentru a fi utilizata
de un singur fir, putem afecta performanta prin ceea ce se numeste bottleneck. Blocurile
sincronizate pot fi utilizate in locul metodelor sincronizate pentru a reduce numarul de linii
utilizate exclusiv de un singur fir.
Trebuie sa utilizam sincronizarea cat se poate de putin, pentru ca reduce performanta, dar cat
este nevoie pentru a garanta consistenta.
Orice obiect in Java este asociat unui monitor, pe care un fir il poate bloca sau debloca.
Metodele sincronizate utilizeaza monitorul pentru obiectul this. Metodele statice
sincronizate utilizeaza monitorul claselor, iar blocurile sincronizate pot specifica ce monitor
de obiect sa blocheze sau sa deblocheze.
Blocurile sincronizate pot fi incuibate. Prin blocuri sincronizateputem bloca mai multe
monitoare simultan.

Intreruperea unui fir


Intreruperea reprezinta o alta modalitate pentru a cere ca un fir sa-si opreasca executia. Cand
un fir este intrerupt depinde de programator sa decida ce actiune se va produce in continuare.
Aceasta poate fi returnarea din metoda run() sau continuarea executiei codului.
Orice fir are metodele interrupt() si isInterrupted(). Iata un exemplu de folosire a lor:
public class ExampleInterruptedRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread started");
while(!Thread.interrupted()) {
// ...
}
System.out.println("Thread finishing");
}
}

public class ThreadInterruptingMain {


public static void main(String[] args) {
ExampleInterruptedRunnable r1 = new ExampleInterruptedRunnable();
Thread t1 = new Thread(r1);
t1.start();
// ...
t1.interrupt();
}
}
Intreruperea este un mod convenabil de a opri un fir. Intreruperea se poate face si asupra
firelor blocate.
Un fir isi poate opri executia pentru o perioada de timp.
public class ThreadSleepMain {

public static void main(String[] args) {


long start = System.currentTimeMillis();
try {
Thread.sleep(4000);
} catch (InterruptedException ex) {
// What to do?
}
long time = System.currentTimeMillis() - start;
System.out.println("Slept for " + time + " ms");
}
}
In exemplul anterior am oprit firul curent pentru patru secunde. Dupa patru secunde firul este
programat din nou pentru executie. Asta nu inseamna ca firul porneste imediat dupa 4
secunde. Durata de „adormire” este afectata de hardware, sistemul de operare si incarcarea
sistemului.
Daca apelam interrupt() pe un fir adormit, metoda sleep() va arunca un
InterruptedException ce trebuie manipulata. Daca intreruperea a insemnat doar
intreruperea somnului si nu executia firului atunci putem ignora exceptia. Altfel, va trebui sa
rearucam exceptia sau sa incheiem petoda run().

Alte metode ale clasei Thread


- setName(String), getName() si getId()
- isAlive(), returneaza true daca firul nu s-a incheiat
- isDaemon() si setDaemon(boolean). JVM poate sa-si incheie executie in timp ce
firele daemon ruleaza
- join(), firul curent asteapta ca alte fire sa se incheie
- Thread.currentThread(), instantele Runnable pot returna instante Thread ce se
executa in monentul curent
Si clasa Object are metode ce sunt in legatura cu firele de executie: wait(), notify() si
notifyAll(). Firele pot merge la culcare pentru o perioada nedeterminata de timp, se trezesc
doar cand obiectul cerut este disponibil, iar firul adormit este instiintat printr-o notificare.
Firele daemon sunt fire background ce sunt mai putin importante decat firele normale. Pentru
ca firul principal nu este un fir daemon, toate firele pe care le vom crea nu vor fi fire daemon.
Orice fir nondaemon ce ruleaza va retine JVM de la a se inchide, chiar daca metoda main()
s-a incheiat. Daca un fir nu poate preveni JVM de la a se inchide, atunci acesta este un fir
daemon.
Urmatoarele metode ar trebui evitate:
- setPriority(int) si getPriority(), in primul rand pentru ca e foarte posibil sa nu
aiba vreun impact
Urmatoarele metode sunt invechite si nu ar trebui folosite: destroy(), resume(),
suspend(), stop().

Deadlock
Blocarea mortala apare atunci cand doua sau mai multe fire sunt blocate definitiv, asteptand
unul dupa altul.

Alte probleme ce pot apare relativ la firele de executie sunt starvation si livelock.
Starvation descrie o situatie in care un fir este incapabil sa obtina acces la resurse share-uite si
nu este capabil sa progreseze. Aceasta se intampla atunci cand resursele impartite sunt
indisponibile pentru o lunga perioada de timp din cauza firelor „lacome”. De exemplu,
presupunem ca un obiect furnizeaza o metoda sincronizata careia ii ia mult timp pentru a se
incheia. Daca un fir invoca aceasta metoda frecvent, alte fire ce necesita acces sincronizat
frecvent la acelasi obiect vor fi deseori blocate.
Deseori un fir actioneaza ca raspuns la actiunea unui alt fir. Daca actiunea celuilalt fir este tot
un raspuns la primul apare livelock. Ca si deadlock, in livelock firele nu sunt capabile sa faca
progrese. Firele nu sunt blocate, dar sunt permanent ocupate raspunzandu-si unul altuia.
Vom examina particularitatile programarii multifir prin problema celor cinci filosofi, enuntata
astfel: cinci filosofi sunt asezati la o masa rotunda incercand sa manance avand la dispozitie 5
betigase. Un betigas este plasat intre fiecare doi filosofi. Un filosof are nevoie de doua
betigase pentru a putea minca.
Rezolvarea aplicatiei din exemplul de mai sus prin fire de executie, determina gestionarea
“starii” unui filosof printr-un fir de executie. Cele cinci fire nu pot opeara independent pentru
ca impart un numar prea mic de betigase si deci un obiect Betigas este folosit la un moment
dat de un sngur fir. In plus, fiecare filosof trebuie sa afle cand un betigas este eliberat de catre
un alt filosof, astfel incat sa-l poata folosi.
Clasa ce modeleaza starea unui betigas este:
class Betigas {

boolean betigasDetinut = false ;

public synchronized void eliberareBetigas(){


betigasDetinut = false ;
notifyAll () ; // betigasul este eliberat
}

public synchronized void luareNetigas() throws InterruptedException {

while (betigasDetinut) wait() ; // pina betigasul este eliberat


betigasDetinut = true ;
}

}
Atributul de tip boolean al clasei da starea obiectului, mai
precis daca betigasul este detinut de catre cineva sau nu.
Initiat betigasul este disponibil.
Cele doua metode simuleaza actiunile de modificare a starii: eliberareBetigas() si
luareNetigas(). Ambele sunt sincronizate, prima notifica celelalte fire despre eliberarea
betigasului, prin metoda notifyAll(), a doua blocheaza firul curent pina ce betigasul este
eliberat, prin metoda wait().
Clasa care modeleaza comportamentul unui filosof o vom numi Filosof si implementeaza
Runnable. Clasa are doua atribute, indicele betigaselor cu care maninca filosoful, i cel din
dreapta respectiv j cel din stanga. Initial fiecare filosof are un singur betigas de indice egal cu
cel al sau, din sirul de filosofi. Metoda run() va contine un ciclu infinit in care starile
filosofului se modifica. Initial firul este intrerupt pentru o perioada aleatoare cuprinsa intre 0
si 3 secunde, considerata perioada in care filosoful gandeste (de aia e filosof !). Apoi filosoful
incearca sa ia un betigas prin aplelul metodei luareBetigas(). Dupa aceasta incearca
obtinerea celui de-al doilea betigas prin apelul aceleiasi metode. Avand cele doua betigase
filosoful maninca un timp aleator curins intre 1 si 2 secunde. Perioada mesei este simulata
prin intreruperea firului pe o durata limitata. Dupa masa filosoful elibereaza ambele betigase
prin intermediul metodei eliberareBetigas(). Cum metoda luareBetigas() este
sincronizata nu este posibil ca doi filosofi sa o apeleze simultan. Deci, un singur fir este
notificat la un moment dat de eliberarea obiectului. Folosirea in acest caz a apelului lui
notify() sau notifyAll() este indiferenta.
Problema mare ce apare in cazul exemplului nostru este evitarea punctului mort. Iata un
scenariu. Presupunem ca suntem la inceputul mesei si fiecare filosof sa ia betigasul din
dreapta sa. Nu poate manca doar cu unul si de aceea incearca sa il ia pe al doilea. Fiecare va
apela la nesfarsit metoda wait(). Cum niciun filosof nu are doua betigase metoda
notifyAll() nu va fi apelata vreodata. Programul este in acest moment intr-un punct mort.
Noi am construit rezolvarea prin intreruperea initiala, aleatorie, a fiecarui fir de excutie,
perioada denumita de gandire. Este putin probabil ca fiecare filosof sa gandeasca la fel de
mult, deci punctul mort nu se va instala inca de la inceput. Dar nu este exclus sa se instaleze
pe parcurs. Vom evita instalarea punctului mort prin faptul ca ne asiguram ca la un moment
dat un filosof nu are doar un singur betigas, ci poate sa il ia si pe al doilea. In cod am evitat
blocarea prin faptul ca un filosof este obligat ca mai intai sa-si aleaga betigasul de indice
minim si dupa ce a facut rost de acesta il va lua pe celalalt, care sigur exista. Spre exemplu,
daca filosoful 0 si 1 doresc in acelasi timp sa manince (0 are nevoie de betigasele 0 si 5, iar 1
de 0 si 1) vor astepta ambii dupa betigasul 0 (de indice minim). Daca filosoful 1 va primi
betigasul 0 atunci il va lua si pe 1 care este disponibil, altfel 0 il va lua pe 5. Conform acestei
strategii 5 va fi intotdeauna disponibil ca al doilea betigas.
Vom retine obiectele Filosof intr-un sir de obiecte si vom crea pentru fiecare un fir de
executie. De asemenea, vom crea un sir de obiecte Betigas, initial fiecare filosof detine un
astfel de obiect, de indice egal cu al sau.
Codul claselor Filosof si Test este dat in continuare:
public class Test{

Filosof[] filosofi ;
Thread[] fir ;
Betigas[] betigase ;

public Test () {
filosofi = new Filosof[5] ;
fir = new Thread[5] ;
betigase = new Betigas[5] ;
}

public void porneste () {


// construieste 5 instante ale claselor Filosof si Betigas
for (int i = 0; i<5 ; i++ ) {
betigase[i] = new Betigas() ;
filosofi[i] = new Filosof (i) ;
fir[i] = new Thread ( filosofi[i] ) ;
fir[i].start() ;
}
}

public static void main (String args[]) {


Test x = new Test () ;
x.porneste();
}

class Filosof implements Runnable {

int i, j ;

public Filosof (int myNumber) {


i = j = myNumber ;
}

public void run () {


while (true) {
System.out.println("filosoful " + i + " gandeste"); //
incepe prin a gandi
try {
// timp de gandire aleator intre 0 si 3 secunde
double r = Math.random() * 3.0 ;
Thread.sleep((long)(r * 1000)) ;
// alege ca indice al betigasului stang 4 daca indicele
filosofului este 0
if(i - 1 == -1) j=4; else j=(i - 1) ;
// aici se previne punctul mort
betigase[Math.min(i,j)].luareBetigas();
System.out.println("filosoful "+i+" are betigasul
"+Math.min(i,j));
// acum incearca sa ia al doilea betigas
betigase[Math.max(i,j)].luareBetigas();
// maninca un timp intre 1 si 2 secunde
System.out.println("filosoful "+i+" are si betigasul
"+Math.max(i,j)+" si maninca");
Thread.sleep((long)(r * 2000)) ;
// dupa masa elibereaza betigasele
betigase[i].eliberareBetigas();
betigase[j].eliberareBetigas();
}
catch (InterruptedException e) {
System.out.println("filosoful " + i + " gandeste");
return ;
}
}
}
}

class Betigas {

boolean betigasDetinut = false ;

public synchronized void eliberareBetigas() {


betigasDetinut = false ;
notifyAll () ; // anunta eliberarea betigasului
}

public synchronized void luareBetigas() throws


InterruptedException {

while (betigasDetinut) wait() ; // asteapta eliberarea


betigasului
betigasDetinut = true ;
}
}
}
Un exemplu tipic de blocare este prezentat in exemplul urmator:
class A {
synchronized void met1(B b) {
String nume = Thread.currentThread().getName();
System.out.println(nume + " apelul metodei A.met1");
try {
Thread.sleep(1000);
} catch(Exception e) {
System.out.println("A este intrerupta");
}
System.out.println(nume + " incearca sa apeleze B.met2()");
b.met2();
}

synchronized void met2() {


System.out.println("apelul metodei A.met2");
}
}

class B {
synchronized void met1(A a) {
String name = Thread.currentThread().getName();
System.out.println(name + " apelul metodei B.met1");
try {
Thread.sleep(1000);
} catch(Exception e) {
System.out.println("B este intrerupta");
}
System.out.println(name + " incearca sa apeleze A.met2()");
a.met2();
}

synchronized void met2() {


System.out.println("apelul metodei A.met2");
}
}

public class Deadlock implements Runnable {


A a = new A();
B b = new B();

Deadlock() {
Thread.currentThread().setName("Firul Principal");
Thread t = new Thread(this, "Firul Concurent");
t.start();
a.met1(b);
System.out.println("inapoi in firul principal");
}

public void run() {


b.met1(a);
System.out.println("inapoi in firul concurent");
}

public static void main(String args[]) {


new Deadlock();
}
}

Concurenta
In Java SE5 s-a introdus pachetul java.util.concurrent ce contine clase utile in
programarea concurenta. Acesta cuprinde:
- Colectii concurente
- Sincronizare si blocare alternativa
- Pool-uri de fire
In pachetul java.util.concurrent.atomic avem clase ce suporta programare thread-safe
fara blocare, pe variabile singulare.
public class AtomicExample {
public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger(5);
if(ai.compareAndSet(5, 42)) {
System.out.println("Replaced 5 with 42");
}
}
}
In exemplul anterior operatia atomica asigura ca valoarea curenta este 5 si apoi este setata la
42. Pentru arhitecturile procesoarelor ce suporta compararea nativa si operatia de setare nu
este nevoie de blocare cand se executa exemplul anterior. Alte arhitecturi pot necesita unele
forme de blocare interna.
Pachetul java.util.concurrent.locks este un framework pentru blocare si asteptarea unor
conditii ce este folosit pentru sincronizare predefinita si monitoare. Una dintre facilitatile
acestui pachet este implementarea multi-reader si blocarea single writer. Astfel, un fir nu va
obtine blocarea la citire atunci cand o actiune de scriere este in derulare. Mai multe fire pot
cere concurent pot dobandi blocarea la citire, dar numai unul blocarea la scriere. Mai mult, un
fir ce a obtinut blocarea pentru scriere poate apela alte metode ce obtin blocarea la scriere fara
frica de blocare a executiei.
public class ShoppingCart {
private final ReentrantReadWriteLock rwl =
new ReentrantReadWriteLock();

public void addItem(Object o) {


rwl.writeLock().lock();
// modify shopping cart
rwl.writeLock().unlock();
}
public String getSummary() {
String s = "";
rwl.readLock().lock();
// read cart, modify s
rwl.readLock().unlock();
return s;
}
public double getTotal() {
double total = 0;
rwl.readLock().lock();
// read cart, add everything to total
rwl.readLock().unlock();
return total;
}
}
In exemplul anterior toate metodele ce sunt destinate citirii permit executia concurenta atat a
citirii singulare cat si multiple.

Colectii thread-safe
Colectiile din java.util nu sunt thread-safe. Pentru a le face thread-safe vom folosi blocuri
de cod sincronizate pentru accesul la o colectie atunci cand facem scriere, creem un wrapper
sincronizat folosind metode precum
java.util.Collections.synchronizedList(List<T>) sau utilizam colectiile din
java.util.concurrent.
Observatie: facand o colectie sincronizata aceasta nu inseamna ca elementele sale sunt
sincronizate.
Clasa ConcurrentLinkedQueue furnizeaza o coada FIFO eficient scalabila, non-blocabila si
thread-safe. Cinci implementari din java.util.concurrent suporta interfata
BlockingQueue, ce definesc versiuni de blocare pentru a pune si a lua:
LinkedBlockingQueue, ArrayBlockingQueue, SynchronousQueue,
PriorityBlockingQueue si DelayQueue.
Pe linga cozi acest pachet furnizeaza implementari de colectii destinate a fi utilizate in
contexte multifir: ConcurrentHashMap, ConcurrentSkipListSet, CopyOnWriteArrayList
si CopyOnWriteArraySet. Cand se asteapta ca mai multe fire sa acceseze o colectie data un
ConcurrentHashMap este preferat unui HashMap sincronizat, un ConcurrentSkipListMap
este prefeta unui TreeMap sincronizat. Un CopyOnWriteArraySet este preferabil unui
ArrayList sincronizat atunci cand numarul asteptat de citiri si traversari este mai mare decat
numarul update-urilor.
In pachetul java.util.concurrent avem cinci clase ce furnizeaza actiuni relative
sincronizarii. Acestea permit firelor blocarea pina cand o anumita stare este atinsa:
- Semaphore, reprezinta un tool clasic de concurenta. Acesta gestioneaza o multime de
permisiuni. Firele incearca sa obtina permisiuni si se pot bloca pina cand alte fire
elibereaza permisiunile.
- CountDownLatch, un utilitar simplu pentru blocare, cat timp se pastreaza un numar de
semnale, evenimete sau conditii. Unul sau mai multe fire se blocheaza pina cand un
cronometru se incheie. Dupa incheierea cronometrului toate firele continua.
CountDownLatch nu poate fi refolosit.
- CyclicBarrier, un punct resetabil ce permite sincronizarea pe mai multe cai si este
folositoare in programarea paralela. Creaza o numaratoare de fire. Dupa ce un numar
de fire au apelat await() pe CyclicBarrier, ele vor fi eliberate. CyclicBarrier
sunt reutilizabile
- Phaser, furnizeaza o forma mai flexibila de barrier ce poate fi folosita pentru
controlul calculelor de-a lungul firelor multiple. Firele se pot inregistra si deinregistra
afectand astfel numarul de fire din barrier
- Exchanger, permit ca doua fire sa schimbe obiecte intr-un punct de intalnire si este
folositor in diverse proiectari pipeline. Firele sunt blocate pina cand are loc
schimbarea. Este bidirectional, eficient cu memoria si o alternativa la
SynchronousQueue.
Exemplu:
public class CyclicBarrierExample {

public static void main(String[] args) {


final CyclicBarrier barrier = new CyclicBarrier(2);

new Thread() {
public void run() {
try {
System.out.println("before await - thread 1");
barrier.await();
System.out.println("after await - thread 1");
} catch (BrokenBarrierException|InterruptedException ex) {

}
}
}.start();

new Thread() {
public void run() {
try {
System.out.println("before await - thread 2");
barrier.await();
System.out.println("after await - thread 2");
} catch (BrokenBarrierException|InterruptedException ex) {

}
}
}.start();
}
}
In exemplul anterior am creat un CyclicBarrier care va determina ca doua fire sa apeleze
await() pina cand vor fi deblocate. Daca doar un fir apeleaza await(), firul va fi blocat
permanent. Clasa CyclicBarrier contine si metoda await(long timeout, TimeUnit
unit), care va bloca firul pentru o durata de timp si va arunca o TimeoutException daca
acea durata a fost atinsa.

Alternative high-level ale firelor


API-urile traditionale pentru fire sunt dificil de utilizat corect. Ca alternativa avem:
- java.util.concurrent.ExecutorService, un mecanism de nivel inalt utilizat
pentru executarea task-urilor: poate crea si reutiliza obiecte Thread
- framework-ul Fork-Join, un ExecutorService nou in Java 7
ExecutorService este folosit pentru a executa task-uri eliminand nevoia de creare si
gestionare manuala a firelor. Task-urile pot fi executate in paralel dependent de
implementarea executorului. Task-urile pot fi: java.lang.Runnable sau
java.util.concurrent.Callable.
Putem obtine instante ale ExecutorService prin Executors: ExecutorService es =
Executors.newCachedThreadPool();.
Un ExecutorService pool cahed creaza fire noi la nevoie, reutilizeaza firele (firele nu mor
dupa utilizare) si termina firele ce au fost inactive (idle) timp de 60 de secunde.
Un alt tip de ExecutorService este ilustrat in exemplu:
int count = Runtime.getRuntime().availableProcessors();
ExecutorService ex = Executors.newFixedThreadPool(count);
Acesta creaza un fixed pool, ce contine un numar fix de fire, reutilizeaza firele, stocheaza in
coada task-urile pina cand un fir devine disponibil si poate fi utilizat pentru a evita
supraincarcarea unui sistem.
Interfata Callable defineste un task trimis unui ExecutorService. Este similar lui
Runnable, dar poate returna un rezultat folosind generice si arunca o checked exception.
public interface Callable<V>{
V call() throws Exception;
}
Interfata Future este folosita pentru a obtine rezultate din apelul metodei call() a interfetei
Callable, asa dupa cum ilustreaza urmatoarea secventa de cod:
Future<V> future = es.submit(callable);// es este un ExecutorService, care
controleaza activitatea firului
try {
V result = future.get();
} catch (ExecutionException | InterruptedException ex) {}
Pentru ca apelul lui get() va determina blocarea va trebui sa facem una dintre urmatoarele
actiuni:
- sa trimitem toata treaba unui ExecutorService inainte de apelul lui get()
- trebuie sa pregatim asteptarea pentru un obiect Future care va obtine rezultatul
- folosim o metoda non-blocking precum isDone() inaintea apelului lui get() sau
get(long timeout, TimeUnit unit), care va arunca o TimeoutException daca
rezultatul nu este disponibil intr-o anumita perioada de timp
Oprirea unui ExecutorService este importanta pentru ca firele sale sunt nondaemon si va
opri JVM de la inchidere.
//Stop accepting new Callables
es.shutdown();
try {
//Block until all Callables have a chance to finish
es.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
System.out.println("Stopped waiting early");
}
Este recomandabil ca dupa inchiderea executorului sa mai aspetam un pic dand sanse ca toate
Callable-urile sa se termine.
Concurenta in I/O
Exista diferite modalitati de a masura timpul. Spre exemplu, o secventa de 5 apeluri in serie
catre servere va dura aproximativ 10 secunde daca fiecare apel dureaza 2 secunde. 5 apeluri
concurente catre servere va dura un pic peste 2 secunde daca fiecare apel dureaza 2 secunde.
Graficul ilustrativ al procesului este dat mai jos:

Fie urmatorul exemplu al unui client intr-o retea cu un singur fir:


public class SingleThreadClientMain {
public static void main(String[] args) {
String host = "localhost";
for (int port = 10000; port < 10010; port++) {
RequestResponse lookup = new RequestResponse(host, port);
try (Socket sock = new Socket(lookup.host, lookup.port);
Scanner scanner = new Scanner(sock.getInputStream());) {
lookup.response = scanner.next();
System.out.println(lookup.host + ":" + lookup.port + " "
+ lookup.response);
} catch (NoSuchElementException | IOException ex) {
System.out.println("Error talking to " + host + ":" +
port);
}
}
}
}
Exemplul prezentat incearca sa descopere care vendor ofera pretul minim pentru un articol.
Clientul comunica cu 10 servere diferite, fiecarui server trebuindu-i 2 secunde pentru a cauta
datele cerute si a le returna. Pot interveni si alte intarzieri introduse de retea. Acest client intr-
un singur fir trebuie sa astepte dupa fiecare server raspunsul inainte de a trece la urmatorul.
Timpul total va fi de aproape 20 de secunde.
In urmatorul exemplu vom da varianta de invocare asincrona:
public class MultiThreadedClientMain {
public static void main(String[] args) {
//ThreadPool used to execute Callables
ExecutorService es = Executors.newCachedThreadPool();
//A Map used to connect the the request data with the potential result
Map<RequestResponse, Future<RequestResponse>> callables = new HashMap<>();
String host = "localhost";
//loop to create and submit a bunch of Callable instances
for (int port = 10000; port < 10010; port++) {
RequestResponse lookup = new RequestResponse(host, port);
NetworkClientCallable callable = new NetworkClientCallable(lookup);
Future<RequestResponse> future = es.submit(callable);
callables.put(lookup, future);
}

//Stop accepting new Callables


es.shutdown();

try {
//Block until all Callables have a chance to finish
es.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
System.out.println("Stopped waiting early");
}

for (RequestResponse lookup : callables.keySet()) {


Future<RequestResponse> future = callables.get(lookup);
try {
lookup = future.get();
System.out.println(lookup.host + ":" + lookup.port + " " +
lookup.response);
} catch (ExecutionException | InterruptedException ex) {
//This is why the callables Map exists
//future.get() fails if the task failed
System.out.println("Error talking to " + lookup.host + ":" +
lookup.port);
}
}
}
}
In acelasi scenariu de la exemplul precedent, clientul nu asteapta ca fiecare server sa raspunda
inainte de a trece la a comunica cu urmatorul server. In acest caz timpul de executie este de
aproximativ 2 secunde.
Clasele auxiliare necesare rularii sunt date ami jos, inclusiv cele de pe partea de server:
public class RequestResponse {

public String host; //request


public int port; //request
public String response; //response

public RequestResponse(String host, int port) {


this.host = host;
this.port = port;
}

@Override
public boolean equals(Object obj) {
if (obj instanceof RequestResponse) {
RequestResponse lookup = (RequestResponse) obj;
if (host.equals(lookup.host) && port == lookup.port) {
return true;
}
}
return false;
}

public int hashCode() {


int hash = 7;
hash = 97 * hash + Objects.hashCode(this.host);
hash = 97 * hash + this.port;
return hash;
}
}

public class NetworkClientCallable implements Callable<RequestResponse> {

private RequestResponse lookup;

public NetworkClientCallable(RequestResponse lookup) {


this.lookup = lookup;
}

@Override
public RequestResponse call() throws IOException {
try (Socket sock = new Socket(lookup.host, lookup.port);
Scanner scanner = new Scanner(sock.getInputStream())) {
lookup.response = scanner.next();
return lookup;
}
}
}

public class PriceRangeServer implements Runnable {

private String price;


private ServerSocket ss;

//low inclusive
//high exclusive
public PriceRangeServer(int port, int low, int high) throws IOException {
ss = new ServerSocket(port);
ss.setSoTimeout(250);
double d = Math.random() * (high - low) + low;
price = String.format("%.2f", d);
}

public void accept() throws IOException {


System.out.println("Accepting connections on port " + ss.getLocalPort());
while (!Thread.interrupted()) {
try (Socket sock = ss.accept();
BufferedWriter bw = new BufferedWriter(new
OutputStreamWriter(sock.getOutputStream()))) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
return;
}
bw.write(price);
} catch (SocketTimeoutException ste) {
//timeout every .25 seconds to see if interrupted
}
}
System.out.println("Done accepting");
}

@Override
public void run() {
try {
accept();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

public class NetworkServerMain {

public static void main(String[] args) {


ExecutorService exSrv = Executors.newCachedThreadPool();
List<Runnable> runners = new ArrayList<>();
for (int port = 10000; port < 10010; port++) {
Runnable r;
try {
r = new PriceRangeServer(port, 20, 110);
runners.add(r);
} catch (IOException ex) {
System.out.println("Port " + port + " already in use");
}
}
for (Runnable r : runners) {
exSrv.execute(r);
}

try {
Thread.sleep(500);
} catch (InterruptedException ex) {
}
System.out.println("Press enter to quit...");
try {
System.in.read();
} catch (IOException ex) {
}
System.out.println("Quiting...");
exSrv.shutdownNow();

}
}
Sistemele moderne contin multiple procesoare. Pentru a folosi avantajul puterii de procesare
va trebui sa executam task-uri in paralel pe aceste procesoare. Situatiile aparute sunt:
- imparte si stapaneste: un task se divide in subtask-uri. Va trebui doar sa identificam
acele subtask-uri ce se pot executa in paralel
- unele probleme sunt dificil a fi executate in task-uri paralele
- serverele ce suporta clienti multipli pot folosiun task separat pentru a manipula fiecare
client
- programarea a prea multor task-uri in paralel pot avea un impact negativ de
performanta
Daca task-urile sunt orientate pe calcule mai degraba decat pe I/O, numarul task-urilor
paralele nu trebuie sa fie mai mare decat numarul de procesoare din sistem. Putem afla
numarul procesoarelor, asa dupa cum am vazut intr-un exemplu anterior.
Daca nu folosim fire de executie, doar o portiune din puterea de procesare a sistemului va fi
folosita. Spre exemplu, daca avem o cantitate mare de date de procesat si doar un fir care
proceseaza aceste date, doar un procesor va fi utilizat. Intr-un sistem cu 4 procesoare, 3 dintre
ele stau in asteptare pe timpul prelucrarii datelor.
O solutie simpla de folosire a paralelismului imparte datele ce urmeaza a fi procesate in mai
multe multimi, distribuind o multime unui procesor si un fir pentru procesarea fiecarui set.
Dupa procesare submultimile trebuie combinate intr-un mod potrivit.
Avem mai multe moduri de a imparti datele de dimensiuni mari. Spre exemplu, in cazul unui
array crearea unui nou array pe fiecare fir ce contine o copie a unei portiuni din firul original
este prea consumatoare de memorie. Solutia mai eficienta este ca fiecare array sa imparta o
referinta dintr-un singur array mare, dar accesul sa fie doar la o submultime intr-un mod non-
blocking thread-safe.
In cazul unei baze de date, impartirea pentru fiecare fir a unei submultimi de dimensiune
egala de inregistrari, poate genera probleme.
Totusi, ideal ar fi ca toate procesoarele sa fie utilizate complet pina cand task-ul este terminat,
dar:
- procesoarele pot rula cu viteza diferita
- task-uri non Java vor necesita procesorul si vor reduce timpul alocat unui fir Java pe
respectivul procesor
- datele ce urmeaza a fi analizate pot necesita variate perioade de timp pentru a fi
procesate
Pentru a tine mai multe fire ocupate:
- impartim datele de procesat intr-un numar mare de submultimi
- asignam submultimile de date unei cozi de fire ce urmeaza a fi procesate
- fiecare fir va avea mai multe submultimi asignate
Daca un fir termina toate submultimile mai repede, poate „fura” submultimile dintr-un alt fir.
Prin impartirea datelor ce urmeaza a fi procesate pina cand avem mai multe submultimi decat
fire, facilitam „work-stealing”. In work-stealing un fir ce ruleaza si isi termina treaba poate
fura o submultime de date din coada de procesare a unui alt fir. Trebuie doar sa decidem
cantitatea optima de adaugat fiecarei cozi de procesare. O supramaruntire a datelor poate duce
la o ineficienta incarcare, in timp ce o submaruntire poate conduce la o subutilizare a
procesoarelor.
In exemplul urmator avem doua task-uri separate ce pot fi executate in paralel. Initializarea
sirului cu valori aleatoare si cautarea in sir a valorii celei mai mari pot fi facute in paralel.
public class Main {
//We use a lot of memory
public static void main(String[] args) {
int[] data = new int[1024 * 1024 * 32]; //32MB
for (int i = 0; i < data.length; i++) {
data[i] = ThreadLocalRandom.current().nextInt();
}
int max = Integer.MIN_VALUE;
for (int value : data) {
if (value > max) {
max = value;
}
}
System.out.println("Max value found:" + max);
}
}
Obiectul ForkJoinTask reprezinta un task ce urmeaza a fi executat:
- un task contine cod si date de procesat, similar lui Runnable sau Callable
- un numar mare de teask-uri sunt create si procesate de un numar mic de fire dintr-un
pool Fork-Join. Un ForkJoinTask creaza mai multe instante ForkJoinTask pina cand
datele de procesat s-au impartit in mod adecvat
- dezvoltatorii folosesc subclasele: RecursiveAction, cand un task nu necesita
returnarea unui rezultat sau RecursiveTask altfel
Prin aceasta metoda acelasi sir este trimis fiecarui task, dar cu diferite valori de start si sfarsit.
Pentru utilizarea framework-ului Fork-Join trebuie sa tinem cont de urmatoarele recomandari:
- sa evitam I/O sau operatiile de blocare. Doar un fir este creat implicit pe procesor.
Operatiile de blocare ne vor tine de la a utiliza toate resursele procesorului
- solutia Fork-Join va avea performante mai mici pe un singur procesor, decat solutia
standard secventiala
- multe probleme conduc la alte probleme daca sunt executate in paralel

public class FindMaxTask extends RecursiveTask<Integer> {

private static final long serialVersionUID = -9018695924326599662L;


private final int threshold;
private final int[] myArray;
private int start;
private int end;

public FindMaxTask(int[] myArray, int start, int end, int threshold) {


this.threshold = threshold;
this.myArray = myArray;
this.start = start;
this.end = end;
}

protected Integer compute() {


if (end - start < threshold) {
int max = Integer.MIN_VALUE;
for (int i = start; i <= end; i++) {
int n = myArray[i];
if (n > max) {
max = n;
}
}
return max;
} else {
int midway = (end - start) / 2 + start;
FindMaxTask a1 = new FindMaxTask(myArray, start, midway, threshold);
a1.fork();
FindMaxTask a2 = new FindMaxTask(myArray, midway + 1, end, threshold);
return Math.max(a2.compute(), a1.join());
}
}
}

public class RandomArrayAction extends RecursiveAction {

private static final long serialVersionUID = 1L;


private final int threshold;
private final int[] myArray;
private int start;
private int end;

public RandomArrayAction(int[] myArray, int start, int end, int threshold) {


this.threshold = threshold;
this.myArray = myArray;
this.start = start;
this.end = end;
}
protected void compute() {
if (end - start < threshold) {
for (int i = start; i <= end; i++) {
myArray[i] = ThreadLocalRandom.current().nextInt();
}
} else {
int midway = (end - start) / 2 + start;
RandomArrayAction r1 = new RandomArrayAction(myArray, start, midway,
threshold);
RandomArrayAction r2 = new RandomArrayAction(myArray, midway + 1, end,
threshold);
invokeAll(r1, r2);
}
}
}

public class Main {

//We use a lot of memory


public static void main(String[] args) {
int[] data = new int[1024 * 1024 * 32]; //32MB

ForkJoinPool pool = new ForkJoinPool();

RandomArrayAction action = new RandomArrayAction(data, 0, data.length-1,


data.length/16);
pool.invoke(action);

FindMaxTask task = new FindMaxTask(data, 0, data.length-1,


data.length/16);
Integer result = pool.invoke(task);
System.out.println("Max value found:" + result);

}
}

JPA (Java Persistence API)


JPA este un framework lightweight ce foloseste POJO (Plain Old Java Objects) pentru a
persista obiecte Java ce reprezinta date relationale. JPA 1.0 a inceput ca parte a specificatiilor
EJB 3.0 pentru a standardiza un model pentru ORM (object-relational mapping). JPA 2.0
(JSR-317) imbunatateste specificatiile originale.
POJO face parte, de asemenea, din specificatiile EJB 3.0. Orice obiect normal este un obiect
POJO (altfel spus un obiect non-enterprise).
Beneficiile utilizarii JPA:
- Nu trebuie sa cream obiecte complexe de acces la date (DAO)
- API-ul este folosit pentru a gestiona tranzactii
- Codul de interactiune cu baza de date este standard, indiferent de vendorul bazei de
date relationale
- Putem evita SQL si in schimb putem folosi un query language, orientat pe obiect
- Putem folosi JPA pentru persistenta aplicatiilor desktop
JDBC a fost primul mecanism pe care dezvoltatorii Java l-au folosit pentru a persista date.
JPA este un framework de mapare ce pastreaza abilitatea de a manipula baza de date direct.
Conceptele fundamentale ale JPA sunt:
- Entitate, este utilizata pentru a reprezenta un tabel relational intr-un obiect Java
- Unitate de persistenta, defineste multimea tuturor claselor ce au legatura cu aplicatia si
care sunt mapate unei singure baze de date
- Context persistent, este o multime de instanta de entitati in care este o unica instanta a
entitatii pentru orice identificator de entitate persistenta
- Entity manager, face munca de creare, citire si scriere a entitatilor

Componentele JPA

Persistenta este mecanismul utilizat de aplicatii pentru a pastra datele, date ce altfel ar fi
pierdute la inchiderea aplicatiei sau a calculatorului, intr-un context persistent, precum o baza
de date.
JPA stabileste relationari statice ale modelului persistent, prin definirea componentelor
entitate. API-ul defineste clasa entitate ca un echivalent al unei tabele din baza de date, pe
partea de business a aplicatiei. O instanta a entitatii este definita ca un obiect, echivalent al
unei linii din tabela bazei de date.
JPA stabileste relationarea dinamica a modelului persistent prin definirea unui obiect entity
manager. Acesta este insarcinat cu sincronizarea datelor continute intr-o instanta entitate cu
datele continute in linia echivalenta din baza de date. Spre exemplu, daca pe timpul executiei
aplicatiei un camp al instantei entitate este modificat, entity managerul modifica linia
echivalenta din baza de date.
JPA furnizeaza programatorilor Java facilitatea de mapare obiect/relatie, pentru a gestiona
modelul relational al bazelor de date implicate in aplicatiile Java. Persistenta in Java consta
din:
- JPA
- Limbajul de interogare (JPQL)
- Java Persistence Criteria API
- Metadatele de mapari obiect/relatie
In figura anterioara avem:
- Entity, obiectul persistent ce reprezinta o inregistrare din tabela bazei de date. Este un
POJO cu anotatii
- Interfata EntityManager, furnizeaza API-ul de interactiune cu entitatea
- EntityManagerFactory este folosit pentru a crea o instanta de EntityManager.
Un obiect Java poate cuprinde date partiale dintr-o tabela sau date din mai multe tabele.
Framework-urile ORM gestioneaza maparea intre tabelele bazei de date relationale si
obiectele Java astfel incat programatorii sa codeze cat mai putin. EclipseLink si Hibernate
sunt exemple de soft-uri ORM.
Observatie: EclipseLink este o versiune continuata a TopLink, pe care Oracle a donat-o
Eclipse-ului.

Entitati
Asadar, o entitate reprezinta o structura de date prin intermediul careia se asigura persitenta.
O entitate este asociata unui tabel al unei baze de date relationale, iar fiecare instanta a
entitatii corespunde unei linii din tabela. Campul unei clase entitate corespunde unei coloane
din tabela bazei de date.
O entitate:
- Este un POJO creata utilizand cuvantul new
- Suporta mostenire si polimorfism
- Este serializata si poate fi utilizata ca un obiect detached
Caracteristicile JPA ale entitatilor includ:
- Persistenta, entitatile create pot fi persistate intr-o baza de date, dar ele sunt tratate ca
obiecte
- Identitatea, fiecare entitate are un unic obiect identitate care este de obicei asociat unei
chei in baza de date, de obicei cheia primara
- Tranzactii, JPA necesita un context tranzactional pentru operatii ce efectueaza
schimbari in baza de date
- Granularitate, entitatile pot fi oricat de fine-grained sau coarse-grained precum doreste
dezvoltatorul, dar reprezinta o singura linie dintr-un tabel
Starea de persistenta a unei entitati este reprezentata prin intermediul acestor campuri sau prin
intermediul proprietatilor persistente (vezi in paragrafele urmatoare diferenta). Toate acestea
folosesc anotatii pentru maparea entitatilor si datelelor modelului relational.
O clasa entitate trebuie sa indeplineasca urmatoarele cerinte:
- Clasa trebuie sa fie anotata cu anotatia javax.persistence.Entity
- Clasa trebuie sa aiba un constructor public sau protected, fara argumente, dar clasa
mai poate avea si alti contructori
- Clasa nu trebuie declarata final, la fel metodele sau instantele clasei
- Daca o instanta a clasei este trimisa ca parametru al unei metode, precum in cazul
interfetelor remote session bean, clasa trebuie sa implementeze interfata
Serializable
- Clasele entitate pot extinde atat clase entitate cat si non-entitate, iar clasele non-
entitate pot extinde clase entitate
- Variabilele instanta trebuie sa fie declarate private, protected sau package-friendly
si pot fi accesate direct doar prin intermediul metodelor claselor entitate. Clientii
trebuie sa acceseze starea entitatii prin accesori sau metode business
- Campul cheie primara sau metoda getter ce corespunde coloanei cheii primare va fi
anotata cu @Id
- Clasele entitate nu definesc metoda finalize().
Setarile predefinite ale unei clase entitate pot fi suprascrise, astfel:
- Suprascrierea numelui tabelei bazei de date. In mod predefinit numele clasei entitate
este numele tabelei (scris cu majuscula). Pentru a modifica acest comportament
folosim anotatia @Table cu elementul name. Ex:
@Entity
@Table(name=”Cust”) //Cust este numele tabelei din baza de date
public class Client {
- Suprascrierea numelui coloanei tabelei bazei de date. In mod predefinit numele
campului sau proprietatii clasei entitate este numele coloanei tabelei (scris cu litera
mica). Pentru a modifica acest comportament folosim anotatia @Column cu elementul
name. Ex:
@Column(name=”cname”)
private String clientName;
- Avem mai multe strategii de generare a cheii primare. Cel mai facil mod pentru a face
aceasta este apelarea autogenerarii de catre container, folosind anotatia cu valoarea
predefinita @GeneratedValue. Ex:
@Id
@GeneratedValue
private int id;
- Excluderea unui camp sau proprietate de la a fi facuta persistenta de catre container
prin folosirea anotatiei @Transient
Starea persistenta a unei entitati poate fi accesata prin intermediul variabilelor instanta sau al
proprietatilor. Proprietatile sau campurile clasei trebuie sa fie de urmatoarele tipuri Java:
- Tipuri primitive
- String
- Alte tipuri serializabile cum ar fi:
o Clasele wrapper ale tipurilor primitive
o java.math.BigInteger
o java.math.BigDecimal
o java.util.Date
o java.util.Calendar
o java.sql.Date
o java.sql.Time
o java.sql.TimeStamp
o tipuri utilizator serializabile
o byte[]
o Byte[]
o char[]
o Char[]
- Tipuri enumerare
- Alte entitati sau colectii de entitati
- Alte clase ce pot fi incorporate
Observatie: Putem folosi anotatia @Lob pentru a persista un camp sau o proprietate ca un
obiect mare intr-o baza de date ce suporta acest tip de obiect. Acest tip poate fi caracter sau
binar.
Starile din ciclul de viata al unei entitati sunt:
- New, se obtine in urma folosirii operatotului new. In acest caz nu este nicio linie din
tabela in corespondenta cu cheia primara a entitatii, din nivelul persistent asociat
- Managed, exista o linie in corespondenta din tabela si datele sunt tinute sincronizate,
de catre persistence provider, cu datele din entitate
- Detached, la fel ca mai sus doar ca datele din entitate nu sunt tinute sincronizate
- Removed, reprezinta o stergere in asteptare a datelor, din linia corespondenta din baza
de date
Acest proces este ilustrat grafic dupa cum urmeaza:

Entity managerul gestioneaza instantele entitatilor pe parcursul ciclului de viata. Fiecare


entity manager este asociat unui context persistent. Un context persistent este o multime de
instante in care pentru fiecare persistent entity identity avem o singura instanta entitate.
Persistent entity identity este o valoare unica folosita de furnizorul de persistenta pentru a
mapa instanta entitatii cu linia corespunzatoare din tabela.
Printre alte operatii ale entity managerului avem:
- flush(), forteaza sincronizarile bazei de cate cu entitatile, in contextul persistent
- refresh(), reface instantele entitate din contextul persistent
- find(), cauta o instanta entitate prin executarea unei interogari pe cheia primara in
baza de date
- contains(), returneaza true daca instanta entitatii se afla in contextul persistent.
Aceasta inseamna ca instanta entitatii este “managed”
Contextul persistent poate avea urmatoarele domenii:
- domeniul transaction, care se intinde pe durata unei tranzactii
- domeniul extended, care se intinde pe durata mai multor tranzactii.
Contextul persistent nu se creaza direct de catre utilizator, ci se foloseste cel pe care
containerul il aloca entity managerului cand este creat. Folosirea unui context persistent
transaction duce la obtinerea unui entity manager transaction. Acelasi lucru se intampla si
pentru extended. Ex:
@PersistenceContext(type=Extended)
private EntityManager entityManager;
Putem efectua urmatoarele operatii asupra unei tabele folosind instante entitate, intr-un
domeniu transaction:
- crea o noua intrare. Pentru aceasta trebuie sa urmam pasii:
o sa cream o noua instanta a clasei entitate ce corespunde tabelei
o sa folosim metoda persist() a obiectului entity manager pentru a crea o
intrare in tabel
- sterge o intrare. Pentru aceasta trebuie sa apelam metoda remove() a entity
managerului, ce are ca argument o instanta managed
- cauta o intrare. Pentru aceasta utilizam metoda find() a entity managerului. Metoda
returneaza o instanta managed
- modifica o intrare. Pentru aceasta trebuie sa urmam pasii:
o trebuie sa folosim un setter pentru a modifica valoarea unui camp sau
proprietati a instantei entitate
o daca instanta entitate este in starea managed atunci acest pas nu mai este
necesar, altfel va trebui sa apelam merge() din entity manager avand ca atribut
instanta detached. Ex:
// item este o instanta detached; item1 este o instanta managed
Item item1 = entityManager.merge(item);
item1.setProp(newProp);
sau
item.setProp(newprop); // item este instanta detached
Item item1 = entityManager.merge(item); // item1 este managed
In continuare descriem aceleasi operatii intr-un context extended. Principalele diferente intre
un entity manager cu domeniul transaction si unul cu domeniul extended sunt:
- in entity manager de domeniu transaction toate instantele managed devin detached
dupa ce tranzactia se incheie
- in entity manager de domeniu extended toate instantele managed raman managed pe
tot timpul de existenta al entity managerului
Pentru a desfasura clasele entitate trebuie sa cream o unitate de persistenta. O unitate de
persistenta este o grupare logica a tuturor elementelor necesare containerului pentru a
gestiona persistenta unei aplicatii. Printre aceste elemente avem:
- toate clasele entitate managed, ce definesc ce urmeaza a fi persistat
- maparea, ce defineste legatura intre clasele managed si tabelele bazei de date
- entity manager factory si entity managers
- informatii de configurare pentru entity manager factory si entity managers
- un fisier persistence.xml
O unitate de persistenta nu este o unitate de desfasurare de sine statatoare.
Pentru desfasurarea pe un server componentele unitatii de persistenta trebuie sa fie plasate
intr-un EAR, EJB-JAR, WAR sau intr-un JAR al aplicatiei client.
Ca locatie, componetele unitatii persistente sunt localizate astfel:
- toate clasele entitate in directoare ce mapeaza structura claselor managed. In cazul
nostru in directorul ejbModule
- maparea, este data de anotatii in clasele entitate. Ca alternativa putem folosi
descriptori XML
- persistence.xml, in directorul META-INF
Entitatile pot folosi campuri persistente, proprietati persistente sau o combinatie a celor doua.
Daca anotatiile de mapare sunt aplicate variabilelor de instanta ale entitatii, atunci entitatea
utilizeaza campuri persistente. Daca anotatiile sunt aplicate getterilor, atunci entitatea
foloseste proprietati persistente.
Toate campurile neanotate cu javax.persistence.Transient sau nemarcate ca tranzitorii
vor fi persistente in baza de date. Anotarea de mapare trebuie sa insoteasca variabilele
instanta.
Daca entitatea foloseste proprietati persistente, atunci entitatea trebuie sa urmareasca
conventia componentelor JavaBean. Intr-un JavaBean proprietatile folosesc getteri si setteri
ce au numele format cu numele atributelor clasei. Pentru proprietatea proprietate, vom
avea metodele getProprietate() si setProprietate(), cu o singura exceptie daca
proprietatea este boolean atunci getterul va fi isProprietate().
Anotatiile de mapare pentru proprietatile persistente vor fi aplicate getterilor. Acestea nu pot
fi aplicate campurilor sau proprietatilor anotate @Transient sau marcate tranzitoriu.
Daca campurile sau proprietatile persistente sunt colectii, atunci acestea trebuie sa fie de unul
dintre urmatoarele tipuri colectie Java:
- java.util.Collection
- java.util.Set
- java.util.List
- java.util.Map
In general, toate aceste interfete sunt generice.
Daca un camp sau proprietate a unei entitati consta dintr-o colectie va fi anotata cu
javax.persistence.ElementCollection. Aceasta anotatie are doua elemente
targetClass si fetch. Primul specifica numele clasei si este utilizat optional daca campul
sau proprietatea este definita folosind tipurile generice. Al doilea este la randul sau optional si
este folosit pentru a specifica daca colectia este returnata lazily sau eagerly. Specificarea celor
doua valori se face folosind constantele javax.persistence.FetchType.LAZY sau EAGER.
Predefinit este lazily.
Fie urmatorul exemplu:
@Entity
public class Persoana {
...
@ElementCollection(fetch=EAGER)
protected Set<String> nume = new HashSet();
...
}
Am definit o entitate ce are un camp persistent, nume, ce reprezinta o colectie de String-uri
ce vor fi aduse eagerly. Elementul targetClass al anotatiei nu este necesar pentru ca folosim
tipul generic.
Una dintre colectiile frecvent folosite este java.util.Map. Un Map consta dintr-o cheie si o
valoare. Cand folosim Map trebuie sa tinem cont de urmatoarele reguli:
- cheia sau valoarea pot fi de un tip de baza Java, o clasa incorporata sau o entitate
- cand valoarea este o clasa incorporata sau un tip de baza folosim anotatia
@ElementCollection
- daca valoarea este o entitate folosim anotatia @OneToMany sau @ManyToMany
- tipul Map se foloseste doar intr-una dintre partile relationarii bidirectionale
Cand tipul cheii unui Map este un tip de baza Java vom folosi anotatia
javax.persistence.MapKeyColumn pentru a seta maparea cheii. Predefinit, atributul name al
acestei anotatii este PROPERTY-NAME_KEY. Spre exemplu, daca campul relationat referit este
image, atributul name predefinit este IMAGE_KEY.
Daca tipul cheii unui Map este entitate, utilizam anotatia
javax.persistence.MapKeyJoinColumn. Daca avem nevoie de mai multe coloane pentru a
seta maparea vom folosi anotatia javax.persistence.MapKeyJoinColumns pentru a include
multiple anotatii javax.persistence.MapKeyJoinColumn. Daca anotatia
javax.persistence.MapKeyJoinColumn este prezenta atunci numele coloanei mapate este
in mod predefinit PROPERTY-NAME_KEY. Spre exemplu, daca campul relationat referit este
angajat, atributul name predefinit este ANGAJAT_KEY.
Daca nu folosim tipurile generice Java in campul relationat, atunci va trebui ca clasa cheie sa
fie setata explicit prin anotatia javax.persistence.MapKeyClass.
Daca cheia unui Map este cheie primara sau in cazul valorii unui camp persistent/proprietate al
unui Map atunci folosim anotatia javax.persistence.MapKey. Cele doua anotatii
javax.persistence.MapKey si javax.persistence.MapKeyClass nu pot fi folosite
simultan.
Daca valoarea unui Map este un tip de baza Java sau o clasa incorporata, va fi mapata ca o
colectie. Daca nu folosim tipul generic, elementul targetClass al anotatiei
@ElementCollection trebuie sa fie setat la tipul valorii.
Daca valoarea lui Map este o entitate si parte a unei relationari M:N sau 1:N unidirectionale,
atunci va fi mapata unui tabel jonctionat. O relationare unidirectionala 1:N ce utilizeaza Map
trebuie sa fie mapata folosind anotatia @JoinColumn.
Daca entitatea este parte a unei relationari bidirectionale 1:N/M:N, ea va fi mapata in tabelul
entitatii ce reprezinta valoarea Map-ului. Daca nu folosim tipuri generice atunci atributul
targetEntity al anotatiilor @OneToMany si @ManyToMany va trebui setat la tipul valorii.
Java furnizeaza un mecanism pentru validarea datelor aplicatiei.
Bean Validation este integrat in containerele EE. Mecanismul de validare poate fi aplicat
claselor entitate persistente, claselor incorporate si superclaselor mapate. Java face validari
automate entitatilor cu campuri persistente sau proprietati anotate pentru validare, imediat
dupa evenimentele ciclului de viata si anume PrePersistent, PreUpdate si PreRemove.
Java furnizeaza anotatii predefinite pentru validare dar da posibilitatea si utilizatorului sa-si
creeze propriile avalidari. Fiecare constrangere folosita pentru validare este asociata cu cel
putin o clasa validator ce valideaza valoarea campului sau proprietatuu supuse constrangerii.
Constrangerile de validare se aplica prin anotatii campurilor sau getterilor proprietatilor.
Toate constrangerile predefinite, definite in tabelul anterior, au in corespondenta anotatia:
NumeConstrangere.List pentru gruparea mai multor constrangeri de acelasi tip asociate
aceluiasi camp sau proprietate. Spre exemplu:
@Pattern.List({
@Pattern(regexp="..."),
@Pattern(regexp="...")
})

Cheile primare in entitati


Fiecare entitate are un obiect unic pe post de identificator, cunoscut sub numele de cheie
primara. Cheia primara poate fi simpla sau compusa. Cheile primare simple utilizeaza
anotatia javax.persistence.Id pentru a marca campul cheie primara. Cheile primare
compuse, adica formate din mai multe atribute, trebuie definite intr-o clasa cheie primara.
Cheile primare compuse sunt notate folosind anotatiile javax.persistence.EmbeddedId si
javax.persistence.IdClass.
Cheia primara sau proprietatea sau campul unei chei primare compuse trebuie sa fie de unul
dintre urmatoarele tipuri Java:
- tip primitiv
- tip wrapper al tipului primitiv
- java.lang.String
- java.util.Date
- java.sql.Date
- java.math.BigDecimal
- java.math.BigInteger
Tipurile reale nu trebuie a fi folosite ca tipuri pentru cheile primare. Daca folosim chei
primare generate atunci singurele tipuri folosite sunt cele intregi.
Furnizorul de persistenta este responsabil de modul de generare a cheilor:
- AUTO, se alege strategia cea mai buna pentru generarea cheii
- TABLE, se foloseste tabela bazei de date pentru generarea cheii
- SEQUENCE, IDENTITY, se foloseste o secventa sau coloana identitate
O clasa primary key trebuie sa indeplineasca urmatoarele cerinte:
- modificatorul de acces al clasei trebuie sa fie public
- proprietatile trebuie sa public
- clasa trebuie sa aiba un constructor public predefinit
- clasa trebuie sa implementeze metodele hashCode() si equals()
- clasa trebuie sa fie serializabila
- cheia primara compusa trebuie sa fie reprezentata si mapata mai multor campuri sau
proprietati ale clasei entitate sau trebuie sa fie reprezentata si mapata ca o clasa
incorporata
- daca clasa este mapata mai multor campuri sau proprietati, numele si tipul campurilor
cheie primara sau al proprietatilor cheii primare din clasa cheie primara trebuie sa
corespunda celor din clasa entitate

Relationarea entitatilor
Primul pas in modelarea asocierii la nivelul datelor este definirea relationarii ca o multime de
proprietati. Aceste proprietati pot fi apoi utilizate pentru a implementa relationarea obiectelor
prin clasele entitate.
Urmatoarele patru proprietati sunt folosite pentru a descrie asocierea intre obiecte:
- Cardinalitatea unei relatii, care specifica numarul de relationari intre doua entitati
relationate, poate fi de urmatoarele tipuri: unu-unu, unu-multi, multi-unu, multi-multi.
- Directia unei relatii, care determina navigabilitatea si vizibilitatea, poate fi:
o Bidirectionala, fiecare entitate poate vedea cealalta entitate din relatie. Putem
astfel include cod in oricare dintre entitati pentru a naviga catre cealalta
entitate in vederea obtinerii de informatii si servicii de la aceasta. In JPA
natura bidirectionala a unei relatii este specificata prin utilizarea unei anotatii
de cardinalitate in ambele clase entitate situate de cele doua parti ale relatiei
o Unidirectionala, doar o singura entitate dintre cele doua implicate in relatie
poate vedea cealalta entitate. Natura unidirectionala a relatiei este specificata
prin folosirea uneia dintre anotatiile de cardinalitate intr-una dintre entitati.
- Proprietarul relatiei, specifica partea de owning a relationarii, care la randul sau
contine maparea fizica. Cealalta parte a relatiei se numeste inverse side.
- Tipul de propagare a operatiei (cascading type), se refera la propagarea efectului unei
operatii entitatilor asociate. Se exprima prin termenii: All, Persist, Merge, Remove,
Refresh, None
Intr-o relatie bidirectionala fiecare entitate are un camp sau o proprietate ce refera la cealalta
entitate din relatie. Prin acest camp codul entitatii poate accesa obiectul relationat.
Relatiile bidirectionale trebuie sa urmareasca regulile:
- inverse side trebuie sa refere catre owning side utilizand elementul mappedBy al uneia
dintre anotatiile: @OneToOne, @OneToMany sau @ManyToMany. Valoarea acestui
element este campul sau proprietatea din entitatea ce joaca rol de owner al relatiei
- partea “multi” a unei relatii multi-unu, intr-o relationare bidirectionala, nu trebuie sa
defineasca elementul mappedBy. Aceasta parte este considerata intotdeauna owner
- intr-o relatie unu-unu bidirectioanala owning side corespunde partii ce contine cheia
straina corespunzatoare
- intr-o relatie multi-multi ambele parti pot fi owning side
Intr-o relatie unidirectionala doar o singura entitate are un camp sau o proprietate ce refera la
cealalta entitate.
Directionalitatea este necesara pentru a putea naviga intre relatii.
Entitatile ce utilizeaza relatiile au deseori dependente relativ la existenta celeilalte entitati din
relatie. Tipul enumerare javax.persistence.CascadeType defineste operatiile in cascada,
ca valoare a elementului cascade al anotatiilor de relationare. Valorile acestei enumerari sunt
date in tabelul urmator:

Cand entitatea destinatie dintr-o relatie unu-unu sau unu-multi este stearsa din relatie, este
deseori recomandabil sa stergem si entitatea. Asemenea entitati se numesc orfane si atributul
orphanRemoval poate fi folosit pentru a specifica entitatile orfane ce ar trebui sterse. Acest
atribut se foloseste in anotatiile @OneToOne si @OneToMany si are o valoare booleana, implicit
false.

Implementarea asocierii unidirectionale unu-unu


O astfel de asociere poate fi cea din schema de mai jos:
Procesul de realizare a acestei asocieri intre doua clase entitate urmeaza pasii:
1. definirea celor doua clase entitate
2. identificarea clasei owner
3. in acea entitate cream un camp sau o proprietate pentru a reprezenta relationarea
entitatii cu entitatea tinta (target)
4. anotarea acestuia prin @OneToOne
5. pentru ca este o asociere unidirectionala nu este necesara o alta modificare in entitatea
tinta
Portiunea de cod implicata in aceasta relationare este data mai jos:
@Entity
public class Customer {
@Id
private int id;
@OneToOne
private Record custRecord;
..//
}
Respectiv, clasa target:
@Entity
public class Record {
@Id
private int recId;
..//
}
Strategia ilustrata anterior este cea generica. Ea poate fi modificata astfel:
- putem suprascrie numele predefinit al coloanei de jonctiune prin adaugarea anotatiei
@JoinColumn anotatiei @OneToOne
- daca cele doua tabele impart aceleasi valori ale cheii primare putem folosi anotatia
@PrimaryKeyJoinColumn in loc de @JoinColumn
- daca cele doua tabele sunt jonctionate utilizand multiple coloane vom folosi anotatia
@JoinColumns in loc de @JoinColumn

Implementarea asocierii bidirectionale unu-unu


Pe baza exemplului anterior com crea o proprietate si in target prin care putem vedea entitatea
proprietar. Pentru aceasta pina la pasul 4, inclusiv, procedam ca mai inainte. Vom modifica
pasul 5 si adauga pasul 6:
5. in entitatea inversa cream un camp sau o proprietate ce reprezinta relationarea cu
entitatea owning
6. anotam aceasta proprietate prin @OneToOne si setam valoarea atributul mappedBy la
valoarea campului sau proprietatii din entitatea owning
rezultatul este descris de codul urmator:
@Entity
public class Record {
@Id
private int recId;
@OneToOne(mappedBy="custRecord")
private Customer customer;
..//
}
Si in acest caz comportamentul predefinit poate fi modificat prin folosirea anotatiilor
@JoinColumns, @JoinColumn sau @PrimaryKeyJoinColumn.

Implementarea asocierii bidirectionale unu-la-multi/multi-la-unu


Putem exemplifica aceasta asociere prin:

Observatie: in acest tip de asociere entitatea owning este intotdeauna entitatea din partea
multi a relatiei.
Urmatorii pasi descriu procesul de definire a acestui tip de asocieriere intre doua clase
entitate:
1. definirea celor doua clase entitate
2. identificarea clasei owner (cea de pe partea “multi”)
3. in acea entitate cream un camp sau o proprietate pentru a reprezenta relationarea
acestei entitati cu o entitatea tinta
4. anotarea acestuia prin @ManyToOne
5. in cealalata entitate a asocierii cream o proprietate sau un camp ce reprezinta
relationarea
6. anotarea acestuia prin @OneToMany
7. Setam valoarea atributului mappedBy la valoarea proprietatii sau campului din
entitatea owning
Un exemplu de codare este dat in cele ce urmeaza:
@Entity
public class Customer {
@Id
private int id;
@OneToMany(mappedBy=”customer”)
private Collection <Order> orders;
..//
}

@Entity
public class Order {
@Id
private int orderId;
@ManyToOne
private Customer customer;
..//
}

Implementarea asocierii bidirectionale multi-la-multi


Putem exemplifica aceasta asociere prin:
Urmatorii pasi descriu procesul de definire a acestui tip de asocieriere intre doua clase
entitate:
1. definirea celor doua clase entitate
2. identificarea clasei owner
3. in acea entitate cream un camp sau o proprietate pentru a reprezenta relationarea
acestei entitati cu o entitatea tinta
4. anotarea acestuia prin @ManyToMany
5. in cealalata entitate a asocierii cream o proprietate sau un camp ce reprezinta
relationarea
6. anotarea acestuia prin @ManyToMany
7. Setam valoarea atributului mappedBy la valoarea proprietatii sau campului din
entitatea owning
Un exemplu de codare este dat in cele ce urmeaza:
@Entity
public class Worker {
@Id
private int id;
@ManyToMany
private Set <Project> projects;
..//
}

@Entity
public class Project {
@Id
private int pId
@ManyToMany(mappedBy=”projects”)
private Set <Worker> workers;
..//
}
Anotatiile pentru maparea asocierii au anumite atribute optionale. Printre acestea:
- fetch, care seteaza instructiuni catre furnizorul de persistenta referitoare la momentul
incarcarii in memorie a elementelor de asociere. Valorile posibile sunt
o EAGER, indica furnizorului de persistenta sa incarce tinta asocierii in momentul
in care entitatea este incarcata din baza de date
o LAZY, indica furnizorului de persistenta faptul ca incarcarea tintei asocierii sa
se faca in momentul accesului
- cascade, specifica momentul din ciclul de viata al unei entitati in care au loc
modificari asupra relationarilor. In mod predefinit nu apar modificari. Alte valori
posibile sunt indicate in urmatorul tabel
Clase incorporate in entitati
Clasele incorporate sunt utilizate pentru a reprezenta starea unei entitati, dar nu sunt
persistente. Instantele claselor incorporate folosesc identitatea entitatii careia ii apartin. Intr-o
entitate putem avea clase incorporate singulare sau colectii ale acestora.
Clasele incorporate folosesc aceleasi reguli ca si clasele entitate, dar sunt anotate cu
javax.persistence.Embeddable in loc de @Entity.
Entitatile ce folosesc clasele incorporate ca parte a starii de persistenta anoteaza, optional,
campurile sau proprietatile incorporate cu javax.persistence.Embedded.
La randul lor clasele incorporate pot folosi alte clase incorporate pentru a-si defini starea.
Clasele incorporate pot contine relationari catre alte entitati sau colectii de entitati.
Exemplu:
@Embeddable
public class DataNasterii {
int zi;
String luna;
int an;
}
Si clasa ce o contine:
@Entity
public class Persoana {
@Id
protected long id
String nume;
@Embedded
DataNasterii dataNasterii;
...
}

Mostenirea entitatilor
Entitatile suporta mostenirea, asocierea polimorfica si interogarile polimorfice. Clasele
entitate pot extinde clase non-entiate, iar clasele non-entitate pot extinde clasele entitate.
Clasele entitate pot fi abstracte.
O clasa abstracta poate fi declarata entitate prin anotarea cu @Entity. Entitatile abstracte sunt
ca entitatile obisnuite, dar nu pot fi instantiate.
Entitatile abstracte pot fi interogate ca niste entitati obisnuite. Daca intr-o interogare apare o
clasa abstracta, atunci interogarea opereaza pe toate subclasele ce extind entitatea abstracta.
Entitatile pot mosteni superclase ce contin o stare persistenta si informatii mapate, dar nu sunt
entitati. In acest caz superclasele nu sunt decorate cu anotatia @Entity si nu sunt mapate, ca o
entitate, de catre Java persistence provider. Aceste superclase se folosesc de obicei atunci
cand avem stari sau informatii mapate in comun de catre mai multe entitati. Ele se numesc
superclase mapate (mapped superclasses) si sunt anotate cu
javax.persistence.MappedSuperclass.
Superclasele mapate nu pot fi interogate si nu pot fi folosite in operatiile din EntityQuery
sau Query. Nu pot fi, de asemenea, relationate, dar pot fi abstracte. Ele nu au niciun tabel in
corespondenta in baza de date.
Pe linga entitati si superclase mapate, enitatile pot avea si clase non-entitate pe post de
superclase. Si acestea pot fi abstracte. Starea unei superclase non-entitate nu este persistenta
si orice stare mostenita, dintr-o superclasa non-entitate, de catre o entitate nu este persistenta.
Aceste superclase nu pot fi utilizate in operatii ale EntityQuery sau Query. De asemenea, nu
pot fi relationate.
Putem configura modul in care JPA mapeaza entitatile mostenite cu baza de date prin
decorarea clasei radacina a ierarhiei cu javax.persistence.Inheritance. avem
posibilitatea sa alegem dintre:
- O singura tabela pe intreaga ierarhie de clase
- O tabela pe fiecare entitate concreta
- Strategia „join”, in care campurile sau proprietatile unei subclase sunt mapate unei
tabele diferite dacat cea a clasei parinte
Strategiile pot fi configurate prin setarea valoarii elementului strategy din anotatia
@Inheritance cu o valoare din enumerarea:
public enum InheritanceType {
SINGLE_TABLE,
JOINED,
TABLE_PER_CLASS
};
Valoarea implicita este SINGLE_TABLE.
Discutam pe rand fiecare dintre strategii:
- Un singur tabel, adica toate clasele din ierarhie sunt mapate unui singur tabel din baza
de date. Acest tabel are o coloana discriminator ce contine o valoare ce identifica
subclasa caruia ii corespunde instanta liniei curente. Coloana discriminator poate fi
specificata prin utilizarea anotatiei javax.persistence.DiscriminatorColumn in
radacina ierarhiei de clase entitate. Elementele acestei anotatii sunt ilustrate in tabelul
urmator
Tipul enumerare javax.persistence.DiscriminatorType este folosit pentru a seta
tipul coloanei discriminator in baza de date. Valorea elementului
discriminatorType, a anotatiei @DiscriminatorColumn poate fi, asadar:
public enum DiscriminatorType {
STRING,
CHAR,
INTEGER
};
Daca anotatia @DiscriminatorColumn nu este explicit specificata in entitatea
radacina atunci valoarea predefinita este String, iar numele predefinit al coloanei este
DTYPE.
Anotatia javax.persistence.DiscriminatorValue poate fi folosita pentru a seta
valoarea introdusa in coloana discriminator, pentru fiecare entitate din ierarhia de
clase. Doar clasele din ierarhie pot fi decorate cu aceasta anotatie. Daca anotatia nu
este specificata intr-o entitate, JPA va furniza o valoare, dependenta de implementare,
predefinita. Daca valoarea elementului discriminatorType al anotatiei
@DiscriminatorColumn este String, atunci valoarea din tabela este numele entitatii.
Aceasta strategie suporta polimorfismul atat la nivelul relatiilor cat si al interogarilor.
- Un tabel pentru fiecare clasa, adica fiecare clasa este mapata unei tabele separate din
baza de date. Toate campurile sau proprietatile din clasa, inclusiv campurile sau
proprietatile mostenite sunt mapate ca coloane in tabelul clasei, in baza de date. In
general, aceasta strategie nu suporta polimorfismul la nivelul relationarilor iar
interogarile polimorfice au la baza clauza UNION. In mod predefinit GlassFish-ul nu
suporta aceasta strategie
- Subclasa jonctionata, adica clasa radacina este mapata unui tabel din baza de date iar
fiecare subclasa are mapat un tabel separat, care contine doar acele campuri specifice
subclasei. Asadar, tabelul subclasei nu contine coloane pentru campurile sau
proprietatile mostenite. In plus, tabelul subclasei are o coloana sau mai multe coloane
ce reprezinta cheia primara, ce este cheie straina pentru cheia primare a tabelei
superclasa. Aceasta strategie furnizeaza suport pentru relatii polimorfice, dar sunt
necesare una sau mai multe jonctiuni atunci cand instantiem subclasele entitate.
Acelasi situatie se intalneste si in cazul interogarilor. Unii dintre furnizorii de JPA,
incluzand serverul GlassFish, impun o coloana discriminator ce corespunde entitatii
radacina. Daca nu folosim crearea automata a tabelei trebuie sa ne asiguram ca tabela
este setata corect pentru coloana discriminator predefinita sau folosim anotatia
@DiscriminatorColumn pentru a defini o astfel de coloana.
Gestiunea entitatilor
Entitatile sunt gestionate printr-un entity manager reprezentat de o instanta a interfetei
javax.persistence.EntityManager. Fiecare entity manager este asociat unui context
persistent, adica instante ale unora dintre entitatilor ce exista mapate dintr-o baza de date.
Contextul persistent defineste domeniul sub care instantele entitatilor sunt create, salvate sau
sterse. EntityManager defineste metodele utilizate pentru a interactiona cu contextul
persistent.
In cazul unui entity manager gestionat de container, o instanta a EntityManager este automat
propagata de catre container catre toate componentele aplicatiei ce utilizeaza instanta in
cadrul unei tranzactii JTA.
Tranzactiile JTA implica deseori apeluri peste mai multe componente. Pentru a executa o
tranzactie JTA aceste componente au nevoie de acces catre un singur context persistent.
Aceasta apare atunci cand EntityManager este injectat in componentele aplicatiei, prin
anotatia javax.persistence.PersistenceContext. Acest context persistent este propagat
automat impreuna cu tanzactia curenta JTA, iar referintele lui EntityManager sunt mapate
aceluiasi context persistent ca si tranzactia. Prin propagarea automata a contextului persistent
componenetele aplicatiei nu au nevoie sa-si trimita referintele catre instantele
EntityManager in cadrul unei tranzactii. Containerul Java EE gestioneaza ciclul de viata
entity managerilor gestionati de container.
Obtinerea unei instante se face prin constructia sintactica:
@PersistenceContext
EntityManager em;
In cazul unui entity manager gestionat de aplicatie, contextul persistent nu este propagat catre
componentele aplicatiei, iar ciclul de viata al instantelor EntityManager este gestionat de
aplicatie. Acest gen de management al entitatilor este utilizat atunci cand aplicatiile au nevoie
sa acceseze un context ce nu este propagat cu tranzactia JTA de-a lungul instantelor
EntityManager. In acest caz EntityManager creaza un nou context persistent, izolat. O alta
utilizare a acestui tip de gestiune este atunci cand nu putem injecta direct instante ale
EntityManager pentru ca acestea nu sunt thread-safe. In schimb EntityManagerFactory
este.
Aplicatiile creaza instante ale EntityManager folosind metoda createEntityManager() din
javax.persistence.EntityManagerFactory. Obtinerea unei instante a
EntityManagerFactory se face prin injectarea sa in componenta aplicatiei utilizand anotatia
javax.persistence.PersistenceUnit.
@PersistenceUnit
EntityManagerFactory emf;

EntityManager em = emf.createEntityManager();
Gestiunea entitatilor de catre aplicatie nu propaga automat contextul tranzactiei. Aplicatiile
trebuie sa ceara accesul de la managerul de tranzactie si sa adauge informatiile de demarcatie
ale tranzactiei atunci cand executa operatiile entitatii. Interfata
javax.transaction.UserTransaction defineste metodele pentru inceputul, commit-ul sau
roll back-ul unei tranzactii. Injectam o instanta a interfetei UserTransaction prin crearea
unei variabile de instanta anotate cu @Resource.
@Resource
UserTransaction utx;
Pentru a incepe o tranzactie apelam metoda begin() din UserTransaction. Cand toate
operatiile pe entitati sunt incheiate apelam commit() din aceeasi interfata. Metoda
rollback(), din aceeasi interfata, ne permite revenirea la starea de dinaintea inceperii
tranzactiei.
Iata un cadru clasic de folosire a gestiunii de catre aplicatie a entity managerului:
@PersistenceContext
EntityManagerFactory emf;
EntityManager em;

@Resource
UserTransaction utx;
...
em = emf.createEntityManager();
try {
utx.begin();
em.persist(Entitatea1);
em.merge(Entitatea2);
em.remove(Entitatea3);
utx.commit();
} catch (Exception e) {
utx.rollback();
}

Operatii uzuale folosind EntityManager-ul


Gasirea entitatilor se face folosind metoda find() din EntityManager ce are doua atribute,
entitatea in care se face cautarea si valoarea cautata.
O instanta a unei entitati se poate afla in una dintre urmatoarele stari:
- New, in care instantele entitatii nu au inca o identitate persistenta si nu sunt asociate
inca unui context persistent
- Managed, au o identitate persistenta si sunt asociate unui context persistent
- Detached, au o identitate persistenta dar nu au fost inca asociate unui context
persistent
- Removed, au o identitate persistenta si sunt asociate unui context persistent, dar sunt
programate pentru stergere din baza de date
Noile instante entitate devin managed si deci persistente fie prin invocarea metodei
persist() sau printr-o operatie in cascada persist, invocata de entitatile relationate ale
entitatii curente, care au elementul cascade setat la valoarea PERSIST sau ALL in anotatia
relationarii. Aceasta inseamna ca datele entitatii sunt stocate in baza de date cand tranzactia
asociata cu operatia persist este incheiata. Daca entitatea este deja managed atunci operatia
persist este ignorata. Daca operatia persist este apelata unei instante removed, entitatea devine
managed. Daca entitatea este detached atunci operatia va arunca un
IllegalArgumentException si tranzactia va esua. Operatia de persistenta va fi propagata
tuturor entitatilor relationate daca acestea au elementul cascade setat corespunzator.
Stergerea entitatilor se face prin apelul metodei remove() sau printr-o operatie de stergere in
cascada, invocata din entitatile relationate ce au elementul cascade setat la valoarea REMOVE
sau ALL. Daca metoda remove() este invocata pe o entiate aflata in starea new, operatia este
ignorata. Daca este invocata pe o entitate avand starea detached se va arunca un
IllegalArgumentException si tranzactia va esua. Daca va fi invocata pe o entitate removed,
operatia va fi ignorata.
Starea entitatilor persistente este sincronizata cu baza de date atunci cand tranzactia in care se
afla aceste entitati este comisa. Daca gestionam o relatie bidirectionala atunci datele vor fi
persistente bazandu-ne pe partea owning a relatiei. Pentru a forta sincronizarea entitatilor cu
baza de date invocam metoda flush() in instanta EntityManager.

Unitati de persistenta
O unitate de persistenta (persistence unit) defineste multimea tuturor claselor entitate
gestionate de instantele EntityManager intr-o aplicatie. Unitatile de persistenta sunt definite
de fisierul de configurare persistence.xml. Un exemplu de astfel de fisier este cel creat in
aplicatia din cursul anterior:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="PrimulEJB">
<jta-data-source>jdbc/account</jta-data-source>
<class>entitati.Customer</class>
<class>entitati.SavingsAccount</class>
</persistence-unit>
</persistence>
Fisierul descrie o unitate persistenta numita PrimulEJB ce foloseste o sursa de date JTA
jdbc/account. Elementele class specifica clasele persitente si anume: clase entitate, clase
incorporate si superclase mapate.
Unitatile persistente pot fi impachetate intr-un fisier WAR sau EJB JAR, sau pot constitui un
JAR ce este inclus intr-un WAR sau EAR.

Folosirea JPA intr-o aplicatie Java SE


1. Cream baza de date pe serverul de baze de date
2. Adaugam bibliotecile EclipseLink si conectorul la BD
3. Definim entitatile in aplicatie
4. Cream persistence.xml si-l configuram: definim o unitate de persistenta si un tip de
tranzactie, furnizam nume claselor entitate, definim proprietatile conexiunii JDBC
5. Cream instante ale EntityManagerFactory si EntityManager
6. Scriem cod pentru a efectua persistenta entitatilor folosind instanta entity manager

Tranzactii si JPA
O tranzactie este un mecanism ce manipuleaza un grup de operatii ca si cum ar fi o singura
operatie. Intr-o trnzactie toate operatiile se executa sau niciuna. Operatiile implicate intr-o
tranzactie se pot baza pe mai multe baze de date.
Un exemplu tipic de utilizare a unei tranzactii este urmatorul: o aplicatie client face o cerere
de serviciu ce poate implica operatii multiple de citire si scriere pe o baza de date. Daca orice
invocare este esuata, orice stare ce este scrisa trebuie facuta rolled back.
Concret, fie un transfer interbancar ce se executa prin intermediul unei aplicatii. Operatia de
transfer cere serverului sa faca urmatoarele invocari:
- Metoda debit dintr-un cont al primei banci
- Metoda credit al altui cont al celei de-a doua banci
Daca invocarea pe cea de-a doua banca esueaza, aplicatia trebuie sa faca roll back contului
din prima banca, la valoarea de dinaintea apelului.
O tranzactie este formal definita ca o multime de proprietati incluse in acronimul ACID:
- Atomicitate: o tranzactie este efectuata integral sau deloc. In caz de esec operatiile si
procedurile se considera neefectuate si toate datele revin la starea anterioara
tranzactiei
- Consistenta: o tranzactie transforma un sistem dintr-o stare consistenta intr-o alta stare
consistenta
- Izolare: fiecare tranzactie se efectueaza independent de alte tranzactii ce se efectueaza
in acelasi timp
- Durabilitate: tranzactiile efectuate cu succes devin permanente chiar daca sistemul
esueaza
O aplicatie trebuie sa converseze cu un obiect pe care il gestioneaza si trebuie sa faca multiple
invocari pe instanta obiectului. Conversatia poate fi caracterizata de unul sau mai multe dintre
urmatoarele:
- Datele sunt cache-uite in memorie sau scrise in baza de date in timpul sau dupa fiecare
invocare succesiva
- Datele sunt scrise in baza de date la sfarsitul conversatiei
- Aplicatia client cere ca obiectul sa mentina un context in memory intre fiecare
invocare; fiecare invocare succesiva foloseste datele ce sunt intretinute in memorie
- La sfarsitul conversatiei aplicatia client necesita capabilitatea de a opri toate operatiile
de scriere asupra bazei de date aparute pe timpul sau la sfarsitul conversatiei
Daca tranzactia a fost executata cu succes modificarile pastrate in memorie sunt comise
(persistate).
Operatiile pe entitate sunt tranzactionale, adica este necesar sa fie parte a unei tranzactii.
Avem doua modele tranzactionale suportate de JPA:
- resource-local, reprezinta tranzactiile native suportate de driver-ele JDBC in unitatea
de persistenta
- JTA, sunt parte a server-ului Java EE
Interfata EntityTransaction suporta tranzactii resource-local, si este obtinuta din
EntityManager prin apelul metodei getTransaction(). Metode obisnuite ale unei tranzactii
includ:
- begin(), pornesc un nou context tranzactional
- commit(), incheie contextul tranzactional curent si scrie orice modificari nescrise inca
in baza de date
- rollback(), readuce tranzactia la starea initiala
- setRollbackOnly(), seteaza tranzactia actuala astfel incat la incheiere aceasta sa
faca rollback. Una dintre intrebuintari este stergerea unei tranzactii care dureaza prea
mult
- getRollbackOnly(), returneaza un boolean ce indica daca tranzactia curenta este
marcata pentru rollback
- isActive(), returneaza un boolean ce indica daca resursa tranzactiei este in procesare
interfata EntityManager defineste metodele ce gestioneaza entitatile:
- persistenta unei entitati: preia o entitate ce nu are o reprezentare persistenta in baza de
date si o stocheaza. Dupa persistenta entitatea este in starea managed. Orice alte
urmatoare modificari pe acesta entitate sunt de asemenea persistate
- gasirea unei entitati: daca entitatea este localizata in contextul persistent aceasta este
returnata de metoda find si gestionata de entity manager
- stergerea unei entitati
- modificarea unei entitati: localizarea unei entitati existente si efectuarea modificarilor
ce apoi sunt persistate
Aceste operatii formeaza CRUD (create, read, update, delete) pe baza de date.
Persistarea unei entitati este operatia de scriere a entitatii in baza de date. Este echivalenta
clauzei INSERT din SQL.
public class Test {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("NIO2");
EntityManager em = emf.createEntityManager();

Broker brk = new Broker ();


brk.setBrokerId(3);
brk.setAddress("lunga 9");
brk.setBrokername("ion");

em.getTransaction().begin();
em.persist (brk);
em.getTransaction().commit();
em.close();
emf.close();
}
}
In cod, EntityManagerFactory este obtinut folosind clasa Persistence si numele
configuratiei unitatii de persistenta. Din factory obtinem EntityManager-ul.
Pentru a localiza o entitate in baza de date, EntityManager-ul va localiza o linie in baza de
date folosindu-se de cheia primara.
Metoda find() utilizeaza clasa entitate, trimisa ca prim argument, pentru a determina ce tip
sa foloseasca pentru a cheia primara si pentru tipul returnat. Astfel, o extra conversie nu mai
este necesara. Entitatea returnata se afla in starea managed. Daca nu exista entitatea ceruta se
returneaza null. Cel de-al doilea argument al lui find() este Object. Metoda este
echivalenta cu clauza SELECT din SQL.
Broker emp = em.find (Broker.class, 110);
Dupa ce o entitate se afla in starea managed putem face modificari obiectului entitate fara
persistarea explicita a sa. Acest proces este echivalentul clauzei UPDATE in SQL. Pentru a
updata o entitate ce nu este in starea managed va trebui sa folosim metoda merge() pentru a o
aduce in aceasta stare.
em.getTransaction().begin();
emp.setBrokername(emp.getBrokername()+" Ionescu");
em.getTransaction().commit();
Stergerea unei entitati din baza de date este echivalenta cu clauza DELETE din SQL.
Stergerea entitatii trebuie facuta pe o entitate in starea managed. Dupa remove() obiectul
Java este in memorie.
em.getTransaction().begin();
em.remove(emp);
em.getTransaction().commit();
O entitate in starea managed necesita un context tranzactional. Exista situatii in care dorim sa
„unmanage” o entitate pentru a face schimbarile inaintea de returnarea entitatii la starea
managed.
In exemplu, entitatea a fost detached prin entity manager. Dupa aceasta operarea pe ea se face
ca si pentru un obiect Java oarecare. Dupa ce am facut modificarile si au trecut validarile
entitatea poate fi din nou facuta managed.
em.detach(emp);
emp.setBrokername("Jones");
// ... in some other part of the code
em.getTransaction().begin();
emp = em.merge(emp);
emp.setAddress(emp.getAddress() + "2");
em.getTransaction().commit();
Pentru a putea rula aplicatiile din acest curs va trebui sa creem un Java Project clasic si din
Project>Properties sa setam valorile ca mai jos:
Facet-ul JPA apare in urma setarii:
Fisierul persistence.xml va trebui sa contina:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="NIO2" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

<class>model.Broker</class>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/brokertool"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password"
value="root"/>
<property name="javax.persistence.jdbc.driver"
value="com.mysql.jdbc.Driver"/>
</properties>
</persistence-unit>
</persistence>
Iar setarile pe proiect vor fi:
Limbajul Java Persistence Query
Defineste interogari ale entitatilor si starea lor persistenta. Limbajul de interogare utilizeaza
scheme abstracte de persitenta a entitatilor, incluzand relationarea, pentru modelul de date si
defineste operatori si expresii bazate pe modelul de date. Domeniul unei interogari cuprinde
schemele abstracte ale entitatilor ce coexista in acelasi persistence unit. Query language
utilizeaza sintaxa asemanatoare cu SQL pentru a selecta obiecte sau valori bazandu-se pe
tipurile entitate ale schemei abstracte si a relationarii dintre ele.
Avem urmatorul dictionar de termeni frecvent utilizati in cele ce urmeaza:
- Abstract schema, reprezinta o abstractie a schemei persistente, adica entitatile
persistente, starea lor si relationarile, peste care interogarile opereaza. Query language
traduce interogarile, peste aceasta abstractizare a schemei persistente, in interogari ce
sunt executate peste schema bazei de date de unde entitatile sunt mapate
- Abstract schema type, reprezinta tipul la care proprietatea persistenta a unei entitati se
evalueaza in schema abstracta. Astfel, fiecare camp persistent sau proprietate dintr-o
entitate are un camp de stare corespondent, de acelasi tip, in abstract schema. Tipul
schemei abstracte al unei entitati este derivat dintr-o clasa entitate iar informatiile de
metadate sunt furnizate de anotatii
- Backus-Naur Form (BNF), reprezinta o notatie ce descrie sintaxa unui limbaj de nivel
inalt. Toate diagramele, in cele ce urmeaza vor fi BNF.
- Navigation, reprezinta traversarea unei relationari intr-o expresie query language.
Operatorul de navigare este punctul
- Path expression, este o expresie ce navigheaza catre starea unei entitati sau camp al
relatiei
- State field, un camp persistent al unei entitati
- Relationship field, reprezinta un camp persistent al unei relationari dintr-o entitate al
carei tip este un tip in schema abstracta al entitatii relationate

Crearea interogarilor utilizand JPQL


Interogarile pot fi dinamice sau statice (anotate cu @NamedQuery sau @NamedNativeQuery)
Metodele EntityManager.createQuery() si EntityManager.createNamedQuery() sunt
utilizate pentru a interoga datele utilizand JPQL.
Metoda createQuery() este utilizata pentru a crea interogari dinamice, ceea ce inseamna
interogari definite direct in codul aplicatiei.
Metoda createNamedQuery() este utilizata pentru a crea interogari statice sau interogari
definite in metadata utilizand anotatia javax.persistence.NamedQuery. Elementul name
specifica numele interogarii, ce va fi folosit impreuna cu metoda createNamedQuery().
Parametrii denumiti (named parameter) sunt parametri ai interogarii prefixati de :. Parametrii
denumiti intr-o interogare sunt legati de un argument prin metoda:
javax.persistence.Query.setParameter(String name, Object value)
Parametrii denumiti sunt case-sensitive si pot fi folositi atat de interogarile dinamice cat si de
cele statice.
Exemplu:
public void salut(String name) {
List<Customer> l= em.createQuery("SELECT c FROM Customer c WHERE
c.name LIKE :custName").setParameter("custName", name)
.setMaxResults(10)
.getResultList();
for (Customer object: l) {
System.out.println(object.getName());
}
}
Parametrii pozitionali sunt prefixati de ? urmat de un numar ce identifica pozitia parametrului
in interogare. Metoda
Query.setParameter(integer position, Object value)
este folosita pentru a seta valorile acestor parametri.
Parametrii de intrare sunt numerotati incepand cu 1, sunt case-sensitive si pot fi utilizati atat
de interogarile dinamice cat si de cele statice.
Interogarile dinamice nu sunt altceva decat string-uri. Named query sunt statice si
nemodificabile. Ele sunt mai eficiente deoarece furnizorul de persistenta poate translata
string-ul JPQL in SQL de indata ce aplicatia porneste, in loc sa fie pornita de fiecare data
cand interogarea este executata.
Exemplu pentru folosirea parametrilor pozitionali si de creare a interogarilor statice.
In directorul ejbModule>META-INF din PrimulEJB am editat fisierul orm.xml (deschis in
eclipse si ales tabul Design). Click dreapta pe entity-mappings am ales Add Child si de acolo
named-query. Atributul name are valoarea full iar query este SELECT c FROM Customer c
WHERE c.name LIKE ?1. Am folosit aceasta in:
public void salut(String name) {
List<Customer> l= em.createNamedQuery("full")
.setParameter(1, name)
.getResultList();
for (Iterator<Customer> iterator = l.iterator();
iterator.hasNext();) {
Customer object = iterator.next();
System.out.println(object.getName());
}
}
Anotatia @NamedQuery poate fi plasata in definitia clasei entitatii. Anotatia defineste numele
interogarii si textul acesteia.
@Entity
@Table(name = "EMPLOYEE")
@NamedQueries({
@NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employee
e"),
@NamedQuery(name = "Employee.findByEmpId", query = "SELECT e FROM
Employee e WHERE e.empId = :empId"),
@NamedQuery(name = "Employee.findByFirstname", query = "SELECT e FROM
Employee e WHERE e.firstname = :firstname"),
@NamedQuery(name = "Employee.findByLastname", query = "SELECT e FROM
Employee e WHERE e.lastname = :lastname"),
@NamedQuery(name = "Employee.findByBirthdate", query = "SELECT e FROM
Employee e WHERE e.birthdate = :birthdate"),
@NamedQuery(name = "Employee.findBySalary", query = "SELECT e FROM
Employee e WHERE e.salary = :salary")})
public class Employee implements Serializable {
Daca avem de definit mai mult de un named query intr-o clasa, le vom plasa intr-o anotatie
@NamedQueries, ce accepta un sir de @NamedQuery

Sintaxa Query Language


JPQL suporta:
- Rezultatul poate fi de un singur tip sau de mai multe tipuri
- Sortare si grupare
- Functii agregat, expresii cu conditii si subinterogari
- Sintaxa cu jonctiuni
- Interogari ce permit stergeri sau update
- Captarea rezultatelor in clase nepersistente
Interfetele Query si TypedQuery pot fi folosite pentru a scrie interogari.
O interogare select are sase clauze: SELECT, FROM, WHERE, GROUP BY, HAVING si ORDER BY.
Dintre acestea doar SELECT si FROM sunt obligatorii. Dam in continuare sintaxa BNF pentru o
interogare select folosind query language:
QL_statement ::= select_clause from_clause
[where_clause][groupby_clause][having_clause][orderby_clause]
Clauza SELECT defineste tipurile de obiecte sau valorile returnate de interogare.
Clauza FROM defineste domeniul interogarii prin declararea unuia sau mai multor identificatori
de variabila, care pot fi referiti in clauzele SELECT si WHERE. Un identificator de variabila
reprezinta unul dintre urmatoarele elemente:
- Numele schemei abstracte al unei entitati
- Un element al unei colectii de relationare
- Un element al unei relatii cu o singura valoare
Clauza WHERE este o expresie conditionala ce restrictioneaza obiectele sau valorile returnate
de o cerere.
Clauza GROUP BY grupeaza rezultatele interogarii pe baza unui set de proprietati
Clauza HAVING este utilizata impreuna cu GROUP BY pentru a restrictiona rezultatele unei
interogari in raport cu o expresie conditionala.
Clauza ORDER BY sorteaza obiecteel sau valorile returnate de o interogare.
Instructiunile de modificare si stergere furnizeaza operatii peste multimi de entitati. Aceste
instructiuni au urmatoarea sintaxa:
update_statement :: = update_clause [where_clause]
delete_statement :: = delete_clause [where_clause]
Clauzele determina tipul entitatilor ce vor fi modificate sau sterse.
Exemple:
SELECT c
FROM Customer c
Returneaza toti clientii. Clauza FROM declara un identificator de variabila numit c, omitand
cuvantul cheie AS. Daca acesta este inclus, clauza va fi scrisa ca FROM Customer AS c.
Elementul Customer este numele schemei abstracte al entitatii Customer.
Pentru a obtine clientii distincti ce satisfac o anumita conditie:
SELECT DISTINCT c
FROM Customer c
WHERE c.name = ?1
Cuvantul rezervat DISTINCT elimina valorile duplicat. In aceasta interogare am folosit un
parametru de intrare ?1.
Acelasi stil de interogare, dar folosind parametri denumiti este prezentata mai jos:
SELECT DISTINCT c
FROM Customer c
WHERE c.name = :name1 OR c.name = :name2
Interogari ce navigheaza intre entitati relationate
In query language o expresie poate naviga intre entitati relationate. Aceasta este prima
diferenta intre JPQL si SQL. In acest caz interogarile navigheaza intre entitatile relationate in
timp ce SQL jonctioneaza tabele.
Exemplu:
List<Customer> l= em.createQuery(
"SELECT c FROM Customer c , IN(c.savingsaccounts) s")
.setMaxResults(10)
.getResultList();
Returneaza toti clientii care au un cont de economii. Clauza FROM declara doi identificatori de
variabila c si s. Variabila c reprezinta entitatea Customer, iar s entitatea relationata
SavingsAccount. Ordinea declararii este stricta, intai c si apoi s. Cuvantul rezervat IN
semnifica ca savingsaccounts este o colectie de entitati relationate. Expresia
c.savingsaccounts navigheaza de la un Customer la SavingsAccount-ul relationat.
Operatorul punct din expresie este operatorul de navigare. Clauza din exemplul anterior poate
fi rescrisa echivalent:
SELECT c FROM Customer c WHERE c.savingsaccounts IS NOT EMPTY
Navigarea catre un camp al unei relationari se face utilizand clauza JOIN.
SELECT c FROM Customer c JOIN c.savingsaccounts s WHERE s.balance=6
In aceasta interogare returnam toti clientii care au in cont 6 unitati monetare.
In continuare dam un exemplu in care folosim un parametru de intrare:
SELECT c FROM Customer c , IN (c.savingsaccounts) AS s WHERE s.balance =
:balance
Interogarea returneaza clientii care au un anumit nivel al economiilor, nivel introdus ca
paarmetru.
Expresiile pot naviga catre entitati relationate si nu catre campuri persistente. Pentru a accesa
un camp persistent o expresie utilizeaza punctul ca delimitator.
Observatie: O specificare de forma c.savingsaccounts.balance este ilegala pentru ca
savingsaccounts este o colectie.
Orice clauza WHERE trebuie sa specifice o expresie conditionala. Vom da un exemplu de
folosire a cuvantului cheie LIKE in construirea unei expresii conditionale:
SELECT c FROM Customer c WHERE c.name LIKE 'sil%'
Interogarea returneaza toti clientii al caror nume incepe cu sil. LIKE foloseste caractere
wildcard pentru a cauta stringuri care se potrivesc unui anumit sablon. In cazul nostru am
folosit %.
Expresia IS NULL poate fi folosita pentru a verifica daca o relationare a fost stabilita intre
doua entitati.
Diagrame BNF in Java EE 6 Tutorial, paginile 585-617

Folosirea Criteria API pentru a crea Queries


Criteria API este folosit pentru a defini interogari ale entitatilor, cu ajutorul obiectelor.
Criteria queries sunt definite folosind API Java, sunt typesafe si sunt portabile. Similar lui
JPQL, Criteria API este bazat pe schema abstracta a entitatilor persistente, relationarii si
obiectelor incluse. Criteria API permite dezvoltatorilor sa gaseasca, sa modifice si sa stearga
entitatile persistente invocand operatii pe entitati prin JPA.
Criteria API:
- Standardizeaza multe dintre facilitatile de programare ce exista in produsele de
persistenta
- Aplica best practices de programare ale modelelor
- Folosesc la maxim facilitatile de programare ale limbajului Java
Pasii de baza in vederea crearii unei interogari Criteria sunt:
1. utilizam instanta EntityManager pentru a crea un obiect CriteriaBuilder. Aceasta
se obtine prin apelul metodei getCriteriaBuilder() din instanta EntityManager.
2. cream un obiect interogare prin crearea unei instante a interfetei CriteriaQuery.
Acesta se obtine prin apelul metodei createQuery() din instanta CriteriaBuilder.
Parametrul metodei este tipul returnat de interogare si este specificat pentru a crea o
interogare typesafe. Acest obiect va fi modificat cu detaliile unei interogari concrete
3. setam query root-ul prin apelul metodei from() din obiectul CriteriaQuery
4. setam tipul rezultatului prin apelul metodei select() din CriteriaQuery
5. pregatim interogarea pentru executie prin crearea unei instante TypedQuery<T>
specificand tipul rezultatului interogarii
6. executam interogarea prin apelul metodei getResultList() din obiectul
TypedQuery<T>.
Pentru un obiect CriteriaQuery, entitatea radacina a interogarii, de la care pornesc toate
navigarile se numeste query root. Aceasta este similara clauzei FROM dintr-o interogare
JPQL.
Vom considera urmatoarea linie de cod care va returna rezultatele unei interogari simple:
List<Customer> l= em.createQuery("SELECT c FROM Customer
c").getResultList();
Transpunem aceasta interogare intr-una echivalenta creata cu API-ul Criteria urmarind pasii
descrisi anterior:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
Root<Customer> cust = cq.from(Customer.class);
cq.select(cust);
TypedQuery<Customer> q = em.createQuery(cq);
List<Customer> l = q.getResultList();
API-ul Metamodel este folosit pentru a crea un metamodel al entitatilor in unitatea de
persistenta. Pentru fiecare clasa entitate dintr-un pachet, clasa metamodel este creata cu un
nume identic, dar urmat de underscore. Atributele clasei metamodel corespund campurilor
persistente sau proprietatilor clasei entitate.
Clasa metamodel si atributele sale sunt utilizate in interogarile Criteria pentru a referi clasele
entitate gestionate si starea lor de persitenta si relationare.
Clasele metamodel ce corespund claselor entitate sunt de urmatorul tip:
javax.persistence.metamodel.EntityType<T>. Clasele metamodel sunt de obicei
generate de anotatii fie la rulare fie la dezvoltare. Dezvoltatorii ce utilizeaza interogari
Criteria pot genera clase metamodel statice folosind apelul metodei getModel() pe obiectul
query root sau obtinand o instanta a interfetei Metamodel si apoi trimitand tipul entitate
metodei entity(). Ambele cazuri sunt prezentate mai jos;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Customer.class);
Root<Customer> cust = cq.from(Customer.class);
EntityType<Customer> Customer_ = cust.getModel();
sau
Metamodel m = em.getMetamodel();
EntityType<Customer> Customer_ = m.entity(Customer.class);
Sintaxa de baza a interogarilor Criteria consta intr-o clauza SELECT, una FROM si o clauza
optionala WHERE, similar cu JPQL. Interogarile Criteria seteaza aceste clauze prin utilizarea
unor obiecte Java astfel incat, interogarile pot fi create typesafe.
Interfata javax.persistence.criteria.CriteriaBuilder este folosita pentru a construi:
- interogari Criteria
- selectii
- expresii
- predicate
Interogarile Criteria sunt construite prin obtinerea unei instante a interfetei:
javax.persistence.criteria.CriteriaQuery. Obiectele CriteriaQuery definesc o
interogare particulara ce navigheaza peste una sau mai multe entitati. CriteriaQuery este
typesafe.
Pentru un anume obiect CriteriaQuery entitatea radacina a interogarii, adica entitatea de la
care pornesc toate navigarile se numeste query root. Este similara, ca semnificatie, clauzei
FROM din JPQL. Interogarile Criteria pot avea mai multe query roots, in cazul in care
navigam din mai multe entitati.
CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
Root<Customer> cust1 = cq.from(Customer.class);
Root<Customer> cust2 = cq.from(Customer.class)
Putem obtine un Root si dintr-un EntityType.
Root<Customer> cust = cq.from(Customer _);
Pentru interogari ce navigheaza catre entitati relationate interogarea trebuie sa definesca o
jonctiune catre entitatea relationata prin apelul uneia dintre metodele From.join() din
obiectul query root. Aceasta metoda este similara JOIN-ului din JPQL.
Destinatia jonctiunii utilizeaza clasa metamodel de tip EntityType<T> pentru a specifica
campul persistent sau proprietatea entitatii jonctionate.
Metoda join() returneaza un obiect Join<X, Y> unde X este entitatea sursa si Y este
entitatea destinatie.
Iata un exemplu de jonctionare intre entitatea Customer si SavingsAccount.
Metamodel m = em.getMetamodel();
EntityType<Customer> Customer_ = m.entity(Customer.class);

CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
Root<Customer> cust = cq.from(Customer.class);

Join<Customer, SavingsAccount> x = (Join<Customer, SavingsAccount>)


cust.join(Customer_.getList("savingsaccounts"));
Jonctiunile pot fi inlantuite pentru a naviga catre entitatile destinatie fara a avea o instanta
Join<X, Y> pentru fiecare jonctiune.
Obiectele Path sunt utilizate in clauzele SELECT si WHERE ale unei interogari Criteria si
pot fi entitati query root, entitati join sau alte obiecte. Metoda Path.get() este folosita
pentru a naviga catre atributele unei entitati a unei interogari. Argumentul metodei este
atributul corespondent din clasa metamodel. Atributul poate fi single-valued, specificat prin
anotatia @SingularAttribute in clasa metamodel sau un atribut collection-valued specificat
printr-una dintre anotatiile @CollectionAttribute, @SetAttribute, @ListAttribute sau
@MapAttribute.
Vom da un exemplu prin care am selectat un camp al unei entitati folosind un metamodel.
public List<AllCustomer> salut(String name) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
Root<Customer> cust = cq.from(Customer.class);

Metamodel m = em.getMetamodel();
EntityType<Customer> Customer_ = m.entity(Customer.class);

SingularAttribute<Customer, String> custName =


Customer_.getDeclaredSingularAttribute("name", String.class);

cust.get(custName);

TypedQuery<Customer> q = em.createQuery(cq);
List<Customer> l = q.getResultList();

List<AllCustomer> lac =new ArrayList<AllCustomer>();


for (Iterator<Customer> iterator = l.iterator();
iterator.hasNext();) {
Customer customer = (Customer) iterator.next();
lac.add(conv.fromEntity(customer));
}

return lac;
}
Rezultatele unei interogari pot fi restrictionate in acord cu conditiile setate de apelul metodei
CriteriaQuery.where(). Apelul acestei metode este analog cu clauza WHERE din JPQL.
Metoda where() evalueaza instantele interfetei Expression pentru a restrictiona rezultatele
in acord cu conditiile expresiilor. Instantele Expression sunt create utilizand metodele
definite in interfetele Expression si CriteriaBuilder.
Obiectele Expression sunt folosite in clauzele SELECT, WHERE sau HAVING. Metodele
conditionale utilizate in obiectele Expression sunt descrise in tabelul urmator:

In interfata CriteriaBuilder avem si alte metode pentru crearea expresiilor. Aceste metode
corespund operatorilor si functtilor atritmetice, string, data, timp. Tabelul urmator ilustreaza
aceste metode.

Predicatele formate din conditii multiple pot fi specificate utilizand urmatoarele metode din
CriteriaBuilder.
Urmatorul exemplu urmareste crearea unui predicat si folosirea lui pentru a selecta din tabele
customer acele nume care incep cu sil.
public List<AllCustomer> salut(String name) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
Root<Customer> cust = cq.from(Customer.class);

Metamodel m = em.getMetamodel();
EntityType<Customer> Customer_ = m.entity(Customer.class);

SingularAttribute<Customer, String> custName =


Customer_.getDeclaredSingularAttribute("name", String.class);

Path<String> pa=cust.get(custName);
Predicate p = cb.like(pa,"sil%");
cq=cq.where(p);

TypedQuery<Customer> q = em.createQuery(cq);
List<Customer> l = q.getResultList();

List<AllCustomer> lac =new ArrayList<AllCustomer>();

for (Iterator<Customer> iterator = l.iterator();


iterator.hasNext();) {
Customer customer = (Customer) iterator.next();
lac.add(conv.fromEntity(customer));
}

return lac;
}

Pentru interogari ce returneaza mai mult decat un singur rezultat avem uneori nevoie sa
organizam iesirea. In interfata CriteriaQuery avem metoda orderBy() folosita pentru a
ordona rezultatele in raport cu atributele entitatii. Argumentul metodei este un obiect Order,
creat prin apelul uneia dintre metodele: CriteriaBuilder.asc() sau
CriteriaBuilder.desc(). Prima dintre metode este folosita pentru a ordona rezultatele
crescator, dupa expresia parametru, iar cea de-a doua pentru ordonarea descrescatoare.
Metodei orderBy() ii putem trimite mai multe obiecte Order, precedenta fiind determinata
de ordinea aparitiei in lista.
Metoda CriteriaQuery.groupBy() partitioneaza rezultatele interogarii in grupuri. Formarea
acestor grupuri este data de argumentul metodei.
Metoda CriteriaQuery.having()este folosita impreuna cu groupBy() pentru a filtra
grupurile. having() are ca parametru o expresie conditionala.
Pentru a pregati un query pentru executie cream un obiect TypedQuery<T> cu tipul
rezultatului interogarii transmis de obiectul CriteriaQuery lui
EntityManager.createQuery(). Interogarile sunt executate folosind setSingleResult()
sau getResultList() pe obiectul TypedQuery<T>.

String-Based Criteria API


Reprezinta interogari ce folosesc stringuri in loc de obiectele metamodel pentru a specifica
atribute atunci cand traversam o ierarhie de date. Interogarile string-based sunt construite
similar interogarilor metamodel, pot fi statice sau dinamice.
Interogarile metamodel sunt recomandate in folosirea interogarilor. Avantajul interogarilor
string-based este acela ca nu trebuie sa mai cream un metamodel. Marele dezavantaj este
pierderea importantei proprietati de type safety, ceea ce va duce la erori la rulare
(nedetectabila la rulare).
Pentru a crea o interogare string-based specificam numele atributelor claselor entitate direct
ca stringuri, nu ca atribute ale clasei metamodel.
Spre exemplu avem urmatoarul apel simplificat:
cq.where(cb.equal(cust.get("name"), "ion"));
Si aceste interogari se executa similar cu cele de la Criteria API.

Localizare
Localizarea este procesul de adaptare a software-ului unei anumite regiuni sau limbi prin
adaugarea unor componente specifice locale si traducere de text.
In plus fata de modificarea limbii, elementele culturale precum datele, numerele, valuta, etc
trebuie sa fie adaptate.
Scopul este de a crea un design pentru localizare astfel incat sa nu avem nevoie de modificari
de cod.
Localizarea specifica doua elemente:
- Limba: alpha-2 sau alpha-3 ISO 639 (intotdeauna litere mici)
- Tara: ISO 3166 alpha-2 sau codul numeric UN M.49 (intotdeauna majuscule)
Clasa ResourceBundle izoleaza datele local – specifice. Clasa returneaza perechi
cheie/valoare, stocate in fisier clasa sau .properties.
Pasii utilizati pentru folosirea localizarii sunt:
- Crearea unui fisier bundle pentru fiecare locatie
- Apelarea unui locatii specifice pentru aplicatie
Design-ul pentru localizare incepe prin design-ul aplicatiei astfel incat toate textele, sunetele
sau imaginile sa fie inlocuite la rulare cu elementele potrivite regiunii si culturii dorite.
Fiecare cheie din fisierul de proprietati identifica o componenta specifica a aplicatiei.
Conventia de nume pentru fisierul de proprietati este:
MessageBundle_xx_YY.properties, unde xx este codul de limba, iar YY codul de
tara.
Dupa ce a fost creat resource bundle-ul trebuie sa-l incarcam in aplicatie. Mai intai cream un
obiect Locale care specifica limba si tara. Apoi cream resource bundle-ul prin specificarea
numelui fisierului de baza al bundle-ului si localizarea curenta.
Crearea lui Locale se poate face in mai multe moduri:
- Clasa Locale include constante default pentru anumite tari
- Daca constanta nu este disponibila putem defini codurile de tara si de limba pentru a
defini localizarea
- In cele din urma putem folosi metoda getDefault() pentru a obtine locatia default
In aplicatie in loc de text folosim resource bundle-ul, respectiv cheia din fisier
corespunzatoare componetei curente.
Pentru a schimba localizarea vom incarca bundle-ul corespunzator localizarii folosind metoda
getBundle().
Fie urmatorul exemplu care ilustreaza localizarea:
public class SampleApp {

PrintWriter pw = new PrintWriter(System.out, true);


BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Locale usLocale = Locale.US;
Locale frLocale = Locale.FRANCE;
Locale zhLocale = new Locale("zh", "CN");
Locale ruLocale = new Locale("ru", "RU");
Locale currentLocale = Locale.getDefault();
ResourceBundle messages = ResourceBundle.getBundle("MessagesBundle",
currentLocale);
NumberFormat currency;
Double money = new Double(1000000.00);
Date today = new Date();
DateFormat df;

public static void main(String[] args) {


SampleApp ui = new SampleApp();
ui.run();
}

public void run() {


String line = "";

while (!(line.equals("q"))) {
this.printMenu();
try {
line = this.br.readLine();
} catch (Exception e) {
e.printStackTrace();
}

switch (line) {
case "1":
setEnglish();
break;
case "2":
setFrench();
break;
case "3":
setChinese();
break;
case "4":
setRussian();
break;
case "5":
showDate();
break;
case "6":
showMoney();
break;
}
}
}

public void printMenu() {


pw.println("=== Localization App ===");
pw.println("1. " + messages.getString("menu1"));
pw.println("2. " + messages.getString("menu2"));
pw.println("3. " + messages.getString("menu3"));
pw.println("4. " + messages.getString("menu4"));
pw.println("5. " + messages.getString("menu5"));
pw.println("6. " + messages.getString("menu6"));
pw.println("q. " + messages.getString("menuq"));
System.out.print(messages.getString("menucommand") + " ");
}

public void setEnglish() {


currentLocale = usLocale;
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
}

public void setFrench() {


currentLocale = frLocale;
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
}

public void setChinese() {


currentLocale = zhLocale;
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
}

public void setRussian() {


currentLocale = ruLocale;
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
}

public void showDate() {


df = DateFormat.getDateInstance(DateFormat.DEFAULT, currentLocale);
pw.println(df.format(today) + " " + currentLocale.toString());

public void showMoney() {


currency = NumberFormat.getCurrencyInstance(currentLocale);
pw.println(currency.format(money) + " " + currentLocale.toString());
}
}
Cu urmatoarele fisiere de proprietati:
- MessagesBundle_fr_FR.properties

menu1 = Positionner sur Anglais


menu2 = Positionner sur Fran\u00e7ais
menu3 = Positionner sur Chinois
menu4 = Positionner sur Russe
menu5 = Montrer la date
menu6 = Montrer moi la monnaie!
menuq = Saisir q pour quitter
menucommand = Saisir une commande:

- MessagesBundle_ru_RU.properties

menu1 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439
menu2 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0439
menu3 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u043a\u0438\u0442\u0430\u0439\u0441\u043a\u0438\u0439
menu4 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u0440\u0443\u0441\u0441\u043a\u0438\u0439
menu5 = \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0434\u0430\u0442\u0443
menu6 = \u041f\u043e\u043a\u0430\u0436\u0438\u0442\u0435 \u043c\u043d\u0435
\u0434\u0435\u043d\u044c\u0433\u0438!
menuq = \u0412\u0432\u0435\u0434\u0438\u0442\u0435 q
\u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0439\u0442\u0438
menucommand = \u0412\u0432\u0435\u0441\u0442\u0438
\u043a\u043e\u043c\u0430\u043d\u0434\u0443:

- MessagesBundle_zh_CN.properties

menu1=\u8bbe\u7f6e\u6210\u82f1\u6587
menu2=\u8bbe\u7f6e\u6210\u6cd5\u6587
menu3=\u8bbe\u7f6e\u6210\u4e2d\u6587
menu4=\u8bbe\u7f6e\u6210\u4fc4\u6587
menu5=\u663e\u793a\u65e5\u671f
menu6=\u663e\u793a\u91d1\u989d!
menuq=\u6309q\u952e\u9000\u51fa
menucommand=\u8f93\u5165\u547d\u4ee4:

- MessagesBundle.properties

menu1 = Set to English


menu2 = Set to French
menu3 = Set to Chinese
menu4 = Set to Russian
menu5 = Show the Date
menu6 = Show me the money!
menuq = Enter q to quit
menucommand = Enter a command:

Formatul datei si al valutei


Numerele pot fi localizate si afisate in format local. Clasele de formatere includ:
DateFormat si NumberFormat. O aplicatie poate afisa date si valuta formatate.
Formatarea unei date cuprinde:
- Obtinerea obiectului DateFormat bazat pe un Locale
- Apelul metodei format() ce trimite data de formatat
Constantele din DateFormat sunt: SHORT, MEDIUM, LONG, FULL.
SimpleDateFormat este o subclasa a lui DateFormat ce permite un control mai bun
asupra datelor de intrare.
Formatarea valutei urmareste:
- Obtinerea unei instante din NumberFormat
- Trimiterea unui Double metodei format()

public class DateApplication {


PrintWriter pw = new PrintWriter(System.out, true);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

Locale ruLocale = new Locale("ru", "RU");


Locale currentLocale = Locale.US;

ResourceBundle messages = ResourceBundle.getBundle("MessagesBundle",


Locale.US);

Date today = new Date();


DateFormat df;
SimpleDateFormat sdf;

public static void main(String[] args) {


DateApplication dateApp = new DateApplication();
dateApp.run();
}

public void run() {


String line = "";

while (!(line.equals("q"))) {
this.printMenu();
try {
line = this.br.readLine();
} catch (Exception e) {
e.printStackTrace();
}

switch (line) {
case "1":
this.setEnglish();
break;
case "2":
this.setFrench();
break;
case "3":
this.setChinese();
break;
case "4":
this.setRussian();
break;
}
}
}

public void printMenu() {


pw.println("=== Date App ===");
df = DateFormat.getDateInstance(DateFormat.DEFAULT, currentLocale);
pw.println(messages.getString("date1") + " " + df.format(today));
df = DateFormat.getDateInstance(DateFormat.LONG, currentLocale);
pw.println(messages.getString("date2") + " " + df.format(today));
df = DateFormat.getDateInstance(DateFormat.SHORT, currentLocale);
pw.println(messages.getString("date3") + " " + df.format(today));
df = DateFormat.getDateInstance(DateFormat.FULL, currentLocale);
pw.println(messages.getString("date4") + " " + df.format(today));
df = DateFormat.getTimeInstance(DateFormat.FULL, currentLocale);
pw.println(messages.getString("date5") + " " + df.format(today));
sdf = new SimpleDateFormat("EEEE", currentLocale);
pw.println(messages.getString("date6") + " " + sdf.format(today));
sdf = new SimpleDateFormat("EEEE MMMM d, y G kk:mm:ss zzzz",
currentLocale);
pw.println(messages.getString("date7") + " " + sdf.format(today));
pw.println("\n--- Choose Language Option ---");
pw.println("1. " + messages.getString("menu1"));
pw.println("2. " + messages.getString("menu2"));
pw.println("3. " + messages.getString("menu3"));
pw.println("4. " + messages.getString("menu4"));
pw.println("q. " + messages.getString("menuq"));
System.out.print(messages.getString("menucommand") + " ");
}

public void setEnglish() {


currentLocale = Locale.US;
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
}

public void setFrench() {


currentLocale = Locale.FRANCE;
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
}

public void setChinese() {


currentLocale = Locale.SIMPLIFIED_CHINESE;
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
}
public void setRussian() {
currentLocale = ruLocale;
this.messages = ResourceBundle.getBundle("MessagesBundle",
currentLocale);
}
}
menu1 = Positionner sur Anglais
menu2 = Positionner sur Fran\u00e7ais
menu3 = Positionner sur Chinois
menu4 = Positionner sur Russe
menu5 = Montrer la date
menu6 = Montrer moi la monnaie!
menuq = Saisir q pour quitter
menucommand = Saisir une commande:
date1 = La date par d\u00e9faut est:
date2 = La date au format long est:
date3 = La date au format court est:
date4 = La date compl\u00e8te est:
date5 = L'heure compl\u00e8te est:
date6 = Le jour de la semaine est:
date7 = Mon r\u00e9glage de la date et de l'heure est:

menu1 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439
menu2 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0439
menu3 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u043a\u0438\u0442\u0430\u0439\u0441\u043a\u0438\u0439
menu4 = \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c
\u0440\u0443\u0441\u0441\u043a\u0438\u0439
menu5 = \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0434\u0430\u0442\u0443
menu6 = \u041f\u043e\u043a\u0430\u0436\u0438\u0442\u0435 \u043c\u043d\u0435
\u0434\u0435\u043d\u044c\u0433\u0438!
menuq = \u0412\u0432\u0435\u0434\u0438\u0442\u0435 q
\u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0439\u0442\u0438
menucommand = \u0412\u0432\u0435\u0441\u0442\u0438
\u043a\u043e\u043c\u0430\u043d\u0434\u0443:
date1 = \u0414\u0430\u0442\u0430 \u043f\u043e-
\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e:
date2 = \u0414\u043b\u0438\u043d\u043d\u0430\u044f \u0434\u0430\u0442\u0430:
date3 = \u041a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u0434\u0430\u0442\u0430:
date4 = \u041f\u043e\u043b\u043d\u0430\u044f \u0434\u0430\u0442\u0430:
date5 = \u041f\u043e\u043b\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f:
date6 = \u0414\u0435\u043d\u044c \u043d\u0435\u0434\u0435\u043b\u0438:
date7 = \u041c\u043e\u0438 \u043b\u0438\u0447\u043d\u044b\u0435
\u0434\u0435\u043d\u044c \u0438 \u0432\u0440\u0435\u043c\u044f:

menu1=\u8bbe\u7f6e\u6210\u82f1\u6587
menu2=\u8bbe\u7f6e\u6210\u6cd5\u6587
menu3=\u8bbe\u7f6e\u6210\u4e2d\u6587
menu4=\u8bbe\u7f6e\u6210\u4fc4\u6587
menu5=\u663e\u793a\u65e5\u671f
menu6=\u663e\u793a\u91d1\u989d!
menuq=\u6309q\u952e\u9000\u51fa
menucommand=\u8f93\u5165\u547d\u4ee4:
date1=\u7f3a\u7701\u65e5\u671f:
date2=\u957f\u65e5\u671f:
date3=\u77ed\u65e5\u671f:
date4=\u5168\u79f0\u65e5\u671f:
date5=\u5168\u79f0\u65f6\u95f4:
date6=\u661f\u671f:
date7=\u5ba2\u6237\u65e5\u671f\u548c\u65f6\u95f4:

menu1 = Set to English


menu2 = Set to French
menu3 = Set to Chinese
menu4 = Set to Russian
menu5 = Show the Date
menu6 = Show me the money!
menuq = Enter q to quit
menucommand = Enter a command:
date1 = Default Date is:
date2 = Long Date is:
date3 = Short Date is:
date4 = Full Date is:
date5 = Full Time is:
date6 = Day of week is:
date7 = My custom day and time is:

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