0% au considerat acest document util (0 voturi)
36 vizualizări136 pagini

Orientarea Pe Obiecte - Part - 1 - 2 - 3 - 4

Documentul prezintă noțiuni de bază despre orientarea pe obiecte în Java, inclusiv definiția clasei, a obiectului, moștenirea, constructorii și metodele. De asemenea, se explică implementarea moștenirii între clase prin utilizarea cuvântului cheie 'extends' și se dă un exemplu concret de moștenire între clasele Poligon și Dreptunghi.

Încărcat de

Alex
Drepturi de autor
© © All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
36 vizualizări136 pagini

Orientarea Pe Obiecte - Part - 1 - 2 - 3 - 4

Documentul prezintă noțiuni de bază despre orientarea pe obiecte în Java, inclusiv definiția clasei, a obiectului, moștenirea, constructorii și metodele. De asemenea, se explică implementarea moștenirii între clase prin utilizarea cuvântului cheie 'extends' și se dă un exemplu concret de moștenire între clasele Poligon și Dreptunghi.

Încărcat de

Alex
Drepturi de autor
© © All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd

2.

Orientarea pe obiecte

Sl. dr. ing. Raul Robu

2023-2024, Semestrul 1
CUPRINS
2.1 Noţiuni generale
2.2 Moştenirea
2.3 Variabile şi metode statice. Secvenţe de cod statice. Importuri statice
2.4 Constructori
2.4.1 Notiuni generale
2.4.2 Șablonul de proiectare Singleton
2.4.2 Legarea dinamică şi constructorii
2.5 Modificatori de acces
2.6 Distrugerea obiectelor
2.7 Mostenire si polimorfism
2.8 Enumerări
2.9 Serializarea obiectelor
2.10 Generarea documentației
2.11 Redefinirea metodelor
2.12 Clase generice
2.13 Metode generice
2.14 Clase imbricate
2.14.1 Noţiuni generale
2.14.2 Clasă interioară membră
2.14.3 Clasă interioară locală
2
CUPRINS

2.14.4 Clasă interioară anonimă


2.14.5 Clase interioare statice
2.14.6 Facilitați pentru operarea cu clase imbricate (Java 11)
2.15 Clase abstracte
2.16 Interfeţe
2.16.1 Noţiuni generale
2.16.2 Metode implicite, metode statice (Java 8) și metode private în interfețe (Java 9)
2.16.3 Interfețe funcționale (Java 8)
2.17 Expresii Lambda
2.18 Referințe de metode
2.19 Stream API
2.20 Clasa Optional
2.21 Inferența tipului la varibilele locale (local variable type inference)
2.22 Tipul înregistrare (record)
2.23 Design patterns (Factory, Abstract Factory, Facade, Command, Model View Controller)

3
2.1 Noțiuni generale

 Un program este format din una sau mai multe clase.

 O clasă reprezintă descrierea unei mulțimi de obiecte care au aceeași structură şi


același comportament. Ca urmare, într-o clasă vom găsi definițiile datelor şi ale
operațiilor ce caracterizează obiectele clasei respective

 Clasa reprezintă un tipar după care se creează un obiect prin instanţiere

 Declararea unei clase este similară cu declararea unui nou tip de date

 Majoritatea conceptelor legate de orientarea pe obiecte sunt similare cu cele din


C++:
 Încapsularea – accesul la variabilele membre ale unui obiect se poate realiza doar cu
ajutorul metodelor obiectului
 Supraîncărcarea – mai multe metode pot avea acelaşi nume dar o signatură diferită
(numărul şi tipul parametrilor diferă de la o metodă la alta)
 Moştenirea - constă în extinderea comportamentului unei clase existente prin definirea
unei clase noi, care moşteneşte conţinutul primei clase, adăugând la acesta elementele
specifice ei
 Legarea dinamică - asocierea unui apel de metodă cu funcţia ce trebuie apelată se
numeşte "legare" ("Binding"). Legarea se poate face la compilare (“legare timpurie” sau
statică) sau la execuţie ("legare târzie" sau "legare dinamică"). În Java pentru metodele
finale sau statice legarea este timpurie iar pentru celelate legarea este dinamică

4
 Orice construcţie are un tip stabilit la momentul compilării

 Nu există tipuri implicite pentru metode şi nici posibilitatea ca la apelul unei


metode să fie omişi anumiţi parametri (“strong typed”)

 Polimorfismul este abilitatea unui obiect de a lua mai multe forme. Una din cele
mai frecvente utilizări ale polimorfismului în POO este când o referinţă a unei clase
de bază este utilizată pentru a referi un obiect al clasei derivate

 O funcţie sau operator este supraîncărcart dacă execută operaţii diferite în


contexte diferite (de exemplu operatorul + care poate fi adunare sau concatenare
de şiruri)

 O clasă poate fi extinsă prin redefinirea unor metode şi / sau adăugarea unor
metode noi

 O metodă care redefinește o alta din clasa de bază are același nume, același tip
de rezultat şi aceeași listă de parametri

 În cazul în care o clasă derivată are variabile membre cu același nume ca şi clasa
de bază nu este vorba de o redefinire, cei doi parametri coexista şi pot si referiți
distinct

 final – blochează redefinirea. O metodă finală nu mai poate fi redefinită, iar o


clasă finală nu mai poate fi extinsă
5
 Moştenirea
 Permisă doar moştenirea simplă, spre deosebire de C++
 Clasa moştenită – superclasă sau supraclasă sau clasă de bază
 Clasa care realizează extinderea- subclasă sau clasă derivată

 Relaţia de moştenire între clase este o relaţie de forma ”is A” (este un fel de)
adică “subclasa este un fel de superclasă”. Dacă considerăm clasa de bază Vapor
şi clasa drivată VasDeCroaziera, putem spune că vasul de croazieră este tot un tip
de vapor

 Agregarea şi compoziția sunt relații de asociere între clase de forma “has A” (are
o). Agregarea indică o asociere slabă între clase iar compoziția o asociere
puternică între clase (belongs-to sau part-of)

 În cazul agregării distrugerea obiectului principal nu implică distrugerea obiectelor


de legătură, acestea continuă să existe (legătură slabă).

 În cazul compozitei distrugerea obiectului principal implică distrugerea obiectului


de legătură, clasa obiectului principal fiind cea care creează instanța obiectului de
legătură (legătură puternică).

 O firmă are mai multe departamente şi mai mulţi angajați. Închiderea firmei duce la
desființarea departamentelor, deci acestea au o legătură puternică cu firma
(compoziție). Pe de altă parte angajații continuă să existe şi se pot angaja la alte
companii (legătură slabă - agregare).

 În anumite situații o legătură poate fi considerată puternică sau slabă în funcție de


context sau în funcție de modul în care este privită acea legătură 6
 Un constructor este o metodă care același nume cu clasa şi este apelată implicit la
crearea obiectului

 Un obiect reprezintă încapsularea unor date asupra cărora nu se pot executa


decât anumite operaţii numite metode

 Signatura unei metode – reprezintă tipul returnat de metoda, numele acesteia şi


lista sa de parametri

 Interfaţă unui obiect –modul în care un obiect este cunoscut din exterior

 În momentul în care o clasă este necesară (pentru că se instanţiază un obiect de


tipul respectiv sau pentru că este apelată o metodă statică din clasa respectivă)
aceasta se va încărca în mașina virtuală. Încărcarea este realizată de către un
obiect numit „class loader".

 Clasele formează o ierarhie, care are la rădăcină clasa Object

 Rădăcina ierarhiei de clase, clasa Object conţine o serie de metode care vor fi
moştenite de orice clasă definită în Java. Unele din aceste metode pot fi redefinite,
altele nu pot fi redefinite fiind finale
7
 Definiţia clasei Object este conţinută în biblioteca [Link] şi va fi încărcată de pe
maşina pe care se execută programul care o utilizează.

 Metodele finale ale clasei Object sunt următoarele:

 public final Class getClass ()


 public final void notify () throws IllegallMonitorStateException
 public final void notifvAll () throws IllegalMonitorStateException
 public final void wait (long timeout) throws InterruptedException
 public final void wait() throws InterruptedException

 Metodele clasei Object care pot fi redefinite sunt următoarele:

 public boolean equals (Object obj)


 public int hashCode()
 public String toString ()
 protected void finalize() throws Throwable
 protected Object clone() throws CloneNotSupportedException

8
2.2 Moştenirea
 Extinderea comportamentului unei clase existente prin definirea unei clase noi, care
moşteneşte conţinutul primei clase, adăugând la acesta elementele specifice ei
 Clasa moştenită se numeşte clasă de bază, supraclasă sau superclasă, iar clasa care
realizează extinderea se numeşte subclasă, clasă derivată, sau clasă descendentă.
 Relaţia de moştenire între clase este o relaţie de forma ”is A” (este un fel de). Dacă
considerăm clasa de bază Poligon şi clasa derivată Dreptunghi, putem spune că
dreptunghiul este un fel de poligon
 Implementarea moştenirii se realizează cu ajutorul cuvântului cheie extends şi nu
este permisă moştenirea multiplă
class Nume_subclasa extends Nume_supraclasa{
//conţinut specific subclasei
}

package [Link];

class Poligon {
protected double[] laturi;
public Poligon(int n) {
laturi = new double[n];
}
public double perimetru( ) {
double s=0;
for(int i=0;i<[Link];i++) s+=laturi[i];
return s;
}
9
}
final class Dreptunghi extends Poligon {
public Dreptunghi(double L, double h){
super(4);
laturi[0] = laturi[2] = L;
laturi[1] =laturi[3] = h;
}
public double aria( ){
return laturi[0]*laturi[1];
}
}

class MainApp {
public static void main(String []args) {
Dreptunghi d=new Dreptunghi(3, 2);
[Link]("Perimetrul dreptunghiului este "+[Link]());
[Link]("Aria dreptunghiului este "+[Link]());
}
}

 Clasa Dreptunghi este finală, așadar nu mai poate fi extinsă

 Nefiind permisă moştenirea multiplă, clasa Dreptunghi nu poate extinde şi alte clase

 Se pot crea şi alte clase derivate din clasa Poligon precum Triunghi, Patrat, Romb, etc
10
Clase derivate din clasa Object
în mod explicit:

class Chitara extends Object{


String culoare;
int nr_corzi;
}
în mod implicit:
class Chitara{
String culoare;
int nr_corzi;
}

Chitara c; //variabilă initializata cu null


//…
c=new Chitara();
sau
Chitara c=new Chitara();

Accesul la câmpurile obiectului creat, în interiorul pachetului

[Link]="neagra";
c.nr_corzi=12; 11
2.3 Variabile şi metode statice. Secvenţe de cod statice.
Importuri statice
 Variabilele statice ale unei clase sunt variabilele globale ale clasei, adică variabile a căror valoare
poate să fie referită de orice obiect din clasa respectivă

 Pentru o variabilă membru statică se alocă memorie o singură dată, indiferent de numărul de
obiecte de acel tip

 Variabilele şi metodele declarate static se consideră că aparţin clasei nu obiectelor clasei


respective, de aceea ele se accesează de obicei utilizând numele clasei:
NumeClasa.nume_variabila_statica
NumeClasa.nume_metodă_statica()

 Variabilele statice se mai numesc variabile clasă (class variables)

 Variabilele locale NU pot fi statice

 O metodă statică poate să refere doar variabile sau metode statice, dar poate să fie apelată de
orice metodă a clasei
 Metodele statice sunt similare funcţiilor obişnuite din limbajul C
Class Aplicatie{
static final int VERSIUNE=3; //constanta
static int numarObiecte;

} 12
 VERSIUNE este similară unei constante întregi şi globale din limbajul C
 numarObiecte poate fi actualizată de orice obiect creat pentru a contoriza numărul total de obiecte
de tip Aplicatie
 exemplul de mai jos arată că pentru o variabilă membru statică se alocă memorie o singură dată
indiferent de numărul de obiecte de acel tip
package [Link];

class DespreStatic {
int x;
static int y;
DespreStatic(){
x=0;
y=0;
}
}
class MainApp1{
public static void main(String args[]){
DespreStatic t1=new DespreStatic();
DespreStatic t2=new DespreStatic();
t1.x=10;
t1.y=10;

t2.x=20;
t2.y=20;
[Link]("t1.x="+t1.x+" t1.y="+t1.y);
[Link]("t2.x="+t2.x+" t2.y="+t2.y);

[Link](); //modul de acces recomandat


[Link]("t1.x="+t1.x+" DespreStatic.y="+DespreStatic.y);
[Link]("t2.x="+t2.x+" DespreStatic.y="+DespreStatic.y);
} 13
}
Secvenţe de cod statice
static{
//secventa de cod
}
 Secvenţe de cod static se pot declara numai în afara metodelor
 Aceste secvenţe se execută în momentul în care se referă clasa care le conţine
package [Link];

class Floare {
protected int numarPetale;
protected String nume;
Floare(int numarPetale,String nume){
[Link] =numarPetale;
[Link]=nume;
}
public void afiseaza(){
[Link](nume+" are "+numarPetale+ " petale ");
}
static{
[Link]("Zona statica");
}
}
class MainApp2{
static{
Floare g=new Floare(7,"Laleaua");
[Link]();
}
public static void main(String args[]){}
14
}
Importuri statice
 Importurile statice permit accesul la variabilele statice ale unei clase fără a mai fi necesară
specificarea numelui clasei

 De exemplu pentru apelul metodelor statice random(), sqrt(), pow() din clasa Math este necesară
specificarea numelui clasei
package [Link];

class MainApp3 {
public static void main(String[] args) {
[Link]([Link]());
[Link]([Link](4));
[Link]([Link](2, 4));
}
}

 Dacă se folosește import static metodele pot să fie apelate direct, fără nume clasă
package [Link];

import static [Link];


import static [Link];
import static [Link];

class MainApp4 {
public static void main(String[] args) {
[Link](random());
[Link](sqrt(4));
[Link](pow(2, 4));
}
} 15
2.4 Constructori
2.4.1 Noţiuni generale

 Metode speciale apelate la crearea obiectelor

 Au același nume ca şi numele claselor în care se găsesc şi nu returnează nimic

 Un constructor nu poate fi apelat din alte metode, cu excepția altui constructor

 În mod implicit prima instrucțiune dintr-un constructor este apelul constructorului fără
parametri ai superclasei. Singura abatere de la această regulă are loc atunci când
prima instrucțiune din constructor este un apel explicit la alt constructor al clasei sau
la un constructor al supraclasei.

 Dacă într-o clasă nu se definește nici un constructor, atunci este furnizat automat un
constructor fără argumente (numit şi constructor implicit) al cărui corp conţine numai
un apel al constructorului implicit al superclasei

 Dacă într-o clasă se defineşte cel puţin un constructor, constructorul implicit fără
argumente nu mai este furnizat automat. Constructorii nu pot avea atributele abstract,
native, static, synchronized sau final
16
 În general modificatorul de acces al constructorului este public, dar sunt situații de
excepție în care constructorul este privat (design pattern-ul singleton)
class Chitara {
private String culoare;
private int nr_corzi;
public Chitara(){
culoare="Neagra";
nr_corzi=6;
}
public Chitara(String culoare,int nr_corzi){
[Link]=culoare;
this.nr_corzi=nr_corzi;
}
}

SAU:
class Chitara {
private String culoare;
private int nr_corzi;
public Chitara(String culoare,int nr_corzi){
[Link]=culoare;
this.nr_corzi=nr_corzi;
}
public Chitara(){
this ("Neagra",6);
}
}

 Apelul constructorului supraclasei se poate face în felul următor:


super(lista_de_parametrii); 17
2.4.2 Șablonul de proiectare Singleton
 Singleton este un șablon de proiectare (design pattern) care este utilizat pentru a
restricţiona numărul de instanţieri ale unei clase la un singur obiect

 La baza pattern-ului Singleton stă o metodă ce permite crearea unei noi instanţe a
clasei dacă aceasta nu există deja. Dacă instanţa există deja, atunci întoarce o
referinţă către obiectul existent. Pentru a asigura o singură instanţiere a clasei,
constructorul trebuie făcut private

 Uneori este important să avem doar un singur obiect pentru o clasă. De exemplu,
într-un sistem ar trebui să existe un singur manager de ferestre (sau doar un sistem
de fişiere). De obicei, singletons sunt folosite pentru managementul centralizat al
resurselor interne sau externe

 Crearea unui Singleton presupune crearea unui clase cu:


 Un constructor privat care nu va permite crearea de obiecte din afara clasei
 O metodă statică de obicei numită getInstance() care returnează un obiect de tip
Singleton
 O variabilă membră privată şi statică de tip Singleton care se poate instanţia
când clasa este încărcată (early instantiation) sau la cerere atunci când crearea
instanţei este cerută (lazy instantiation)
18
package [Link].singleton_early;//early instantiation

class Singleton {
//creaza un obiect al clasei Singleton
private static Singleton instance = new Singleton();

//Constuctorul este privat deci nu se poate apela din alta clasa pentru instantiere
private Singleton(){}

//Returneaza singurul obiect disponibil


public static Singleton getInstance(){
return instance;
}

public void showMessage(){


[Link]("Ok!");
}
}

class MainApp {
public static void main(String[] args) {

//Instantierea de mai jos da eroare. Constructorul privat nu este este vizibil.


//Singleton object = new Singleton();

//Preia singurul obiect disponibil


Singleton object = [Link]();

//Afiseaza mesajul
[Link]();
}
}
19
package [Link].singleton_lazy;//lazy instantiation

class Singleton {
//creaza un obiect al clasei Singleton
private static Singleton instance;

//Constuctorul este privat deci nu se poate apela din alta clasa pentru instantiere
private Singleton(){}

//Returneaza singurul obiect disponibil


public static Singleton getInstance(){
if (instance==null)
instance=new Singleton();
return instance;
}

public void showMessage(){


[Link]("Ok!");
}
}

class MainApp {
public static void main(String[] args) {

//Instantierea de mai jos da eroare. Constructorul privat nu este este vizibil.


//Singleton object = new Singleton();

//Preia singurul obiect disponibil


Singleton object = [Link]();

//Afiseaza mesajul
[Link]();
} 20
}
2.4.3 Legarea dinamică şi constructorii
 Deoarece în Java la apelul metodelor non-statice se aplică legarea dinamică, pe de o
parte, şi ținând cont de modul în care se apelează constructorii într-o ierarhie de
clase, pe de altă parte, trebuie să avem grijă cum proiectăm constructorii dacă
aceștia apelează la rândul lor metode ale claselor respective.
 Fişierul [Link]

package [Link].legarea_dinamica;

class SuperClasa {
protected int a;
private int x;

public SuperClasa() {
a = 2;
x = calcul();
}
public int calcul() {
return 2 * a;
}
@Override
public String toString() {
return a+", "+x;
}
} 21
 Fişierul [Link]
package [Link].legarea_dinamica;

class SubClasa extends SuperClasa{


private int y;
public SubClasa() {
y = calcul();
}
@Override
public int calcul() {
return 3 * a;
}
@Override
public String toString() {
return [Link]() + ", "+y;
}
}

 Fişierul [Link]
package [Link].legarea_dinamica;

class MainApp {
public static void main(String[] a) {
SubClasa ob=new SubClasa();
[Link](ob);
}
} 22
 Utilizarea adnotaţiei @Override nu este obligatorie dar este recomandată pentru că
aceasta asigură că metoda de dedesubtul ei redefinește o altă metodă din clasa de
bază
 La instanţierea obiectului ob în programul principal se apelează constructorul fără
parametri din Subclasa.
 Prima linie din acesta, este una implicită care realizează apel la constructorul fără
parametri din SuperClasa. Astfel înainte de a se executa y = calcul(); se vor executa
liniile de cod din constructorul superclasei.
 Astfel a primește valoarea 2, iar x va primi valoarea returnată de metoda calcul().
Întrucât există două metode calcul, una în clasa de bază şi una în clasa derivată
(care o redefinește pe cea din clasa de bază) se pune problema care din cele două
metode se va apela în linia x=calcul(); Valorile variabilelor x şi y depind de care din
metodele de calcul se vor executa la apel. Legarea dinamică este asocierea dintre un
apel de metodă şi metoda care se va executa efectiv la rulare.
 Apelul metodei calcul() din constructorul clasei de bază determină execuția metodei
calcul din clasa obiectului în curs de instanţiere, aceasta este metoda calcul din
SubClasa şi în acest fel x va primi valoarea 6.
 Constructorul din SuperClasa se încheie, execuția codului continuă cu liniile de cod
din constructorul clasei derivate, mai exact cu instrucțiunea y = calcul(); Şi în acest
caz se va apela metoda calcul din clasa obiectului în curs de instanţiere, deci din
SubClasa, y primind valoarea 6.
 În acest fel ieşirea programului va fi
23
2.5 Modificatori de acces
 Principiul încapsulării presupune ca variabilele membre ale claselor să fie ascunse şi modificarea
lor să se poată face numai cu ajutorul metodelor definite în cadrul claselor respective, de
exemplu:
package capitolul2.modificatori_acces;

class Chitara {
private String culoare;
private int nr_corzi;
public Chitara(String culoare,int nr_corzi){
[Link]=culoare;
this.nr_corzi=nr_corzi;
}
public Chitara() {
this ("Neagra",6);
}
public String getCuloare () {
return culoare;
}
public void setCuloare (String culoare) {
[Link]=culoare;
}
//…
}
 În IntelliJ getterele şi setterele se generează apăsând Alt + Insert atunci când cursorul se află în
interiorul clasei de interes şi alegând comanda Generate getter and setter
 În Eclipse metodele de tip get set pot fi generate alegând opţiunea de meniu Source şi apoi 24
Generate Getters and Setters… atunci când cursorul se află în interiorul clasei de interes
 public
 se poate aplica constructorilor, metodelor, variabilelor membre și
claselor
 o clasă publică poate fi importată într-un alt pachet decât cel în care
este declarată și utilizată acolo
 o clasă care nu are atributul public este vizibilă doar la nivelul
pachetului în care se află
 o clasă publică trebuie amplasată într-un fișier java cu aceeași
denumire ca şi clasa
 private
 se poate aplica constructorilor, variabilelor membre, metodelor şi
claselor interioare
 restrânge nivelul de vizibilitate al elementelor care au acest atribut
doar la nivelul clasei
 protected
 se poate aplica constructorilor, variabilelor membre, metodelor şi
claselor interioare
 metodele si variabilele membre protected sunt accesibile din metodele
clasei şi din metodele subclaselor
 un obiect al unei clase care conține variabile si metode protected nu
dispune de acces la acestea din afara pachetului (accesul este permis
doar dacă obiectul se găsește în același pachet cu clasa) 25
package capitolul2.modificatori_acces;

class Test {
protected String camp_protected;
}
class MainApp1 {
public static void main(String []args) {
Test t=new Test();
t.camp_protected="acces permis";
}
}

 Modificatorul de acces implicit (package)


 Când nu se specifică un modificator de acces, accesul este limitat la
nivelul pachetului. Variabilele membre sau metodele pentru care nu se
specifică un modificator de acces sunt accesibile din alte clase ale
aceluiași pachet
 În exemplul de mai jos clasa Test și clasa MainApp sunt în același
pachet (exemplu2)

26
package capitolul2.modificatori_acces;

public class Testul {


String camp;
}

class MainApp2 {
public static void main(String []args) {
Testul t=new Testul();
[Link]="Acces permis";
}
}

 Pentru ca o clasă să poată fi utilizată într-un alt pachet decât cel în


care este definită ea trebuie să fie publică şi apoi trebuie importată. În
continuare, clasa OClasa din pachetul
capitolul2.modificatori_acces.test, importă clasa Testul pentru a putea
declara obiecte de tip Testul. Accesul la variabilele membre implicite
nu este permis în acest caz

27
package capitolul2.modificatori_acces.test;

import capitolul2.modificatori_acces.Testul;

class OClasa {
void metoda() {
Testul t=new Testul();
//[Link]="Acces interzis";

}
}

 În ceea ce privește vizibilitatea pe relație de moștenire, variabilele


membre şi metodele private ale unei clase de bază nu sunt vizibile în
clasele derivate, cele protected şi public sunt vizibile, iar cele fără
modificator de acces sunt vizibile doar dacă cele 2 clase sunt în
același pachet

28
2.6 Distrugerea obiectelor
 Nu cade în sarcina programatorului, ci în sarcina unei componente a maşinii virtuale
numita Garbage Collector

 Dacă spre un anumit obiect nu mai există nici o referinţă externă, în nici o funcţie
activă, acel obiect devine candidat la eliminarea din memorie

 Înainte de a elimina un obiect din memorie este apelată metoda finalize


protected void finalize() throws Throwable;

 În metoda finalize pot fi scrise instrucţiuni care să reactiveze obiectul, sau poate fi
afişat un mesaj care precizează că obiectul urmează să fie distrus

 La crearea unui obiect se apelează o ierarhie de constructori începând cu


constructorul clasei Object, iar la distrugerea lui se apelează un lanţ de metode
finalize

 Dacă există un cod care trebuie să se execute când un obiect nu mai este util atunci
fie se scrie codul într-o metodă care se apelează în mod explicit, fie se apelează în
mod explicit Garbage Collector, pentru că altfel nu există garanția că obiectul va fi
distrus până se încheie programul 29
 Exemplul de mai jos arată cum se poate utiliza metoda finalize

package [Link];

class Test {
private int x;

public Test(int x) {
this.x = x;
}

public int getX() {


return x;
}

@Override
protected void finalize() throws Throwable {
[Link]("Se elibereaza memoria ocupata de obiectul "
+ "care are x cu valoarea "+this.x);
}
}

30
package [Link];

public class MainApp {


public static void main(String[] args) {
Test t1 = new Test(1);
t1 = null;

Test t2 = new Test(2);


Test t3 = new Test(3);
t2 = t3;

[Link]("[Link]() -> "+[Link]());


[Link]();
}
}

31
 Apelul metodei statice [Link]() determină rularea Garbage Collector în mașina
virtuală java. Fără acest apel garbage collector nu rulează până programul își încheie
execuția

 Rularea garbage collector determină eliberarea zonelor de memorie alocate spre


care nu mai sunt referințe active.

 În exemplul precedent, atribuirea t1=null determină pierderea referinței către zona de


memorie rezervată pentru obiectul t1 (care are x cu valoarea 1). De asemenea
atribuirea t2=t3 face ca referința către zona de memorie spre care indica t2 (cea care
conține x cu valoare 2) să se piardă

 garbage collector eliberează memoria ocupată de obiectele menționate mai sus şi


execută pentru fiecare obiect dezalocat metoda finallize

32
2.7 Moştenire şi polimorfism
 Polimorfismul este abilitatea unui obiect de a lua mai multe forme. Una din cele mai
frecvente utilizări ale polimorfismului în POO este când o referință a unei clase de
bază este utilizată pentru a referi un obiect al clasei derivate

 Relația de moștenire dintre clase, oferă suport pentru polimorfism

 Relația de moștenire între clase este o relație de forma ”is A” (este un fel de) adică
“subclasa este un fel de superclasă”. Dacă considerăm clasa de bază Vapor şi clasa
derivată VasDeCroaziera, putem spune că un vas de croazieră este tot un tip de
vapor. Datorită acestui lucru putem face o referință a clasei de bază Vapor să refere
un obiect al clasei derivate VasDeCroaziera

Vapor v=new VasDeCroazaiera();

 Polimorfismul mai poate fi implementat folosind interfeţele şi clasele care le


implementează, acest lucru va fi discutat în subcapitolul dedicat interfeţelor

33
package [Link];

class Vapor{
private String denumire;
private int nr_membrii_echipaj;
public Vapor(String denumire, int nr_membrii_echipaj) {
[Link] = denumire;
this.nr_membrii_echipaj = nr_membrii_echipaj;
}
public String getDenumire() {
return denumire;
}
@Override
public String toString() {
return denumire + ", "+ nr_membrii_echipaj;
}
}
class VasDeCroaziera extends Vapor{
private int nr_restaurante;
private int nr_piscine;
VasDeCroaziera(String denumire,int nr_membrii_echipaj,int nr_restaurante,int nr_piscine){
super(denumire,nr_membrii_echipaj);
this.nr_restaurante=nr_restaurante;
this.nr_piscine=nr_piscine;
}
public int getNr_restaurante() {
return nr_restaurante;
}
34
@Override
public String toString() {
return [Link]()+", "+ nr_restaurante+ ", " + nr_piscine;
}
}

class MainApp {
public static void main(String[] args) {
Vapor a=new Vapor("Pescarus",10);
[Link](a);

VasDeCroaziera b=new VasDeCroaziera("Harmony of the Seas", 100, 10, 7);


[Link](b);

a=b;
[Link]([Link]());

b=(VasDeCroaziera)a;
[Link]([Link]());
}
}

 În cazul în care un obiect primeşte o referinţă la un obiect al subclasei, nu este


necesară o conversie explicită prin cast 35
 Conversia explicită prin cast este necesară dacă un obiect primește o referință a
superclasei sale
 Conversia prin operație de cast de la un obiect al superclasei la un obiect al subclasei
este posibilă doar dacă în momentul execuției superclasa referă un obiect al
subclasei.

 Dacă obiectul referit nu este de tipul corespunzător se va genera o excepţie


ClassCastException

 După ce un obiect al superclasei a obţinut ca valoare o referinţă la un obiect al


subclasei, în cazul în care în clasa de bază există o metoda redefinită de clasa
derivată, atunci cea din clasa derivată va fi cea apelată

 Utilizarea mecanismului de conversie pe bază de cast se poate face numai între o


superclasa şi subclasele sale (sau subclasele subclaselor sale)

 În general, deoarece Object este rădăcina ierarhiei de clase se pot executa conversii
precum în secvența următoare
Object o;
Ceva c=new Ceva();

o=c;

c=(Ceva) o; 36
 Pentru a asigura corectitudinea castului se recomandă verificarea apartenenţei
obiectului la clasa respectivă cu ajutorul operatorului instanceof

Vapor c=new Vapor("Pescarus",10);


if (c instanceof Vapor)
[Link]("c este instanta Vapor");
else
[Link]("c nu este instanta Vapor");

if (c instanceof VasDeCroaziera)
[Link]("c este instanta VasDeCroaziera");
else
[Link]("c nu este instanta VasDeCroaziera");

37
 Operatorul instanceof de regulă este utilizat într-un if prin care se verifică dacă un obiect este o
instanță a unei anumite clase. Dacă obiectul este o instanță a acelei clase, pentru a putea avea
acces la metodele clasei, în varianta clasică de utilizare a lui instanceof se impune o conversie de
tip (vezi primul if din exemplul de mai jos).

 În versiunile Java 12, 14 şi 15 a fost întrodus ca şi o caracteristică cu previzualizare (preview


feature) operatorul pattern matching instanceof, iar începând cu versiunea Java 16 (martie, 2021)
acest operator a fost introdus ca şi o caracteristică permanentă

 Utilizând operatorul pattern matching instanceof se poate declara un obiect care va prelua
caracteristicile obiectului verificat, obiect care poate fi utilizat în acel if. În acest fel nu mai este
necesară operația de cast (vezi al doilea if din exemplul de mai jos).

Vapor d=new VasDeCroaziera("Seadream", 95, 5, 4);

if (d instanceof VasDeCroaziera) {
[Link]([Link]()
+" are "+((VasDeCroaziera)d).getNr_restaurante()+" restaurante");
}

if (d instanceof VasDeCroaziera x) {
[Link]([Link]()
+" are "+x.getNr_restaurante()+" restaurante");
}
38
2.8 Enumerări
 Enumerările se folosesc pentru variabile care au o listă finită de valori cunoscută la momentul
compilării

 Tipul enumerare deși NU trebuie instanţiat folosind new, are capabilități similare unei clase (poate
avea constructor, metode, variabile membre, etc).

 Enumerările nu pot extinde alte clase şi nici nu pot fi extinse (pentru că extind deja clasa
[Link] şi nu este permisă moștenirea multiplă) dar pot implementa interfețe

 Declararea unei enumerări se face cu ajutorul cuvântului cheie enum.

 Enumerarea trebuie sa înceapă cu o listă de constante şi apoi pot să urmeze metode, variabile sau
constructori

 Potrivit convenției de nume din Java este important sa se denumească constantele cu toate literele
mari

 Fiecare constantă enum reprezintă un obiect care are atributele public static final şi poate fi accesat
utilizând numele enumerării

 Tipurile enumerare pot fi utilizate în switch

39
 Inclusiv funcția main() poate fi scrisă într-o enumerare

 Metoda toString() din clasa Object este redefinită în clasa [Link] astfel încât returnează
numele constantei

 Alte metode importante din clasa [Link] sunt values(), ordinal() şi valueOf()
.
 Metoda values() returnează un vector cu toate valorile pe care le poate lua o enumerare

 Metoda ordinal() returnează poziția pe care se află o anumită constantă în enumerare

 Metoda valueOf() returnează constanta corespunzătoare string-ului specificat ca şi parametru de


intrare dacă aceasta există (dacă nu există metoda aruncă excepția
[Link])

 Enumerările pot conține un constructor privat care se va executa separat pentru fiecare constantă

 Următoarele două exemple ilustrează cum se poate utiliza tipul enumerare

40
package capitolul2.enumerari1;
import [Link];

enum Anotimp{
PRIMAVARA,
VARA,
TOAMNA,
IARNA
}
class MainApp {
public static void main(String[] args) {
Anotimp a=[Link];
[Link](a);

boolean ok=false;
Scanner scanner=new Scanner([Link]);
do {
try {
[Link]("Introduceti anotimpul preferat:");
String s=[Link]();
a=[Link]([Link]());
ok=true;
}
catch(IllegalArgumentException e) {
[Link](e);
}
}while(!ok);
41
[Link]();
String cuvant=switch(a) {
case PRIMAVARA->"ghiocei";
case VARA->"soare";
case TOAMNA->"culoare";
case IARNA->"zapada";
default->throw new IllegalStateException();
};
[Link]("Anotimpul introdus este: "+a);
[Link](a+"->"+cuvant+"\n");

[Link]("Anotimpurile anului sunt:");

Anotimp [] anotimpuri=[Link]();
for (Anotimp b:anotimpuri) {
[Link](b);
}

[Link]("Anotimpul introdus este pe pozitia "+[Link]()+" in enumerare");


}
}

42
 În exemplul următor tipul enumerare are pe lângă constante şi un constructor, o variabilă membră şi
o metodă. Constructorul unei enumerărări trebuie să aibă modificatorul de acces private

package capitolul2.enumerari2;
enum Anotimp{
PRIMAVARA,
VARA,
TOAMNA,
IARNA;

private int a=7;

private Anotimp() {
[Link]("Contructor apelat pentru fiecare constanta");
}

void metoda() {
[Link]("Metoda in enumerare. a="+a);
}
}

class MainApp {
public static void main(String[] args) {
Anotimp a=[Link];
[Link](a);
[Link]();
}
} 43
2.9 Serializarea obiectelor
 Serializarea obiectelor presupune descompunerea acestora într-o înșiruire de octeți
și salvarea lor într-un flux de date. Procesul opus, de recompunere a obiectelor din
șirul de octeți se numește deserializare

 Pot să fie serializate obiectele oricărei clase care implementează interfața Serilizable

 O clasă care implementează interfața Serilizable poate avea variabile membre


obiecte ale altor clase, care la rândul lor trebuie să implementeze interfața Serilizable
pentru a putea fi salvate

 Câmpurile ale căror valoare nu se dorește să fie serializată trebuie să fie precedate
de cuvântul cheie transient

 Serializarea / deserializarea obiectelor se poate realiza cu ajutorul claselor


ObjectOutputStream / ObjectInputStream iar a tipurilor primitive cu ajutorul claselor
DataOutputStream / DataInputStream

 Metoda writeObject() din clasa ObjectOutputStream realizează serializarea, iar


metoda readObject() din clasa ObjectInputStream() realizează deserializarea
44
 Exemplul de mai jos serializează o colecţie de obiecte de tip List în fișierul [Link]

 Fișierul [Link]:
package [Link];

import [Link];

class Persoana implements Serializable{


private String nume;
private transient int varsta;
private Adresa adresa;

public Persoana(String nume, int varsta,Adresa adresa) {


[Link] = nume;
[Link] = varsta;
[Link]=adresa;
}
@Override
public String toString() {
return nume + " " + varsta+" "+adresa;
}
}

45
 Fișierul [Link]:
package [Link];

import [Link];

class Adresa implements Serializable{


private String localitate;
private String strada;
private int nr;

public Adresa(String localitate, String strada, int nr) {


[Link] = localitate;
[Link] = strada;
[Link] = nr;
}
@Override
public String toString() {
return localitate + ", " + strada + ", " + nr;
}
}

46
 Fișierul [Link]:

package [Link];

import [Link];
import [Link];
import [Link];
import [Link];
import [Link];

class MainApp {
static void scrie(Object o, String fis) {
try {
FileOutputStream f = new FileOutputStream(fis);
ObjectOutputStream oos = new ObjectOutputStream(f);
[Link](o);
[Link]();
[Link]();
}
catch (IOException e) {
[Link]();
}
}

47
static Object citeste(String fis) {
try {
FileInputStream f = new FileInputStream(fis);
ObjectInputStream ois = new ObjectInputStream(f);
Object o=[Link]();
[Link]();
[Link]();
return o;
}
catch (IOException | ClassNotFoundException e) {
[Link]();
}
return null;
}

public static void main(String[]args) {


List<Persoana> persoane = new ArrayList<Persoana>();
[Link](new Persoana("Ion", 20, new Adresa("Timisoara", "Parvan", 1)));
[Link](new Persoana("Ana", 21, new Adresa("Timisoara", "Republicii", 1)));

scrie(persoane, "[Link]");//varsta nu a fost serializata, utilizare transient

List<Persoana> q;
q = (List<Persoana>) citeste("[Link]");
for (Persoana p : q)
[Link](p);
}
48
}
 Câmpul varsta fiind precedat de cuvântul cheie transient nu va fi serializat, își va
pierde valoarea

 Pentru ca obiectele clasei Persoana să poată să fie serializate este necesar ca


aceasta să implementeze interfața Serilizable. Clasa Persoana are un câmp de tip
Adresa. Pentru ca acel câmp să poată fi serializat este necesar ca și clasa Adresa să
implementeze interfața Serilizable. În caz contrar se va produce
NotSerializableException

49
2.10 Generarea documentației

 javadoc este generatorul de documentaţie al proiectelor şi este inclus în JDK


(Java Development Kit)

 javadoc generază documentaţia în format html, similar cu documentația Java API,


aceasta putând fi urcată pe un server

 Documentaţia se introduce în proiect utilizând comentarii de documentaţie (doc


comments) care sunt în formatul de mai jos. Acestea se amplasează imediat
deasupra claselor, metodelor, constructorilor, interfeţelor, etc pe care le
documentează
/**
*
*/

 În prima parte a comentariului de documentație se introduce o descriere generală iar


în a doua parte se introduc tag-uri prin care se poate specifică cine este autorul
secvenței documentate, versiunea acesteia, data dezvoltării, etc. În cazul
documentării metodelor se pot introduce tag-uri prin care se precizează ce reprezintă
parametrii de intrare, ce reprezintă valoarea returnată, respectiv în ce situații aruncă
metoda anumite excepții.

50
 Exemplu de clasă documentată, fişierul [Link]:
package [Link];
/**
* Patratul este un patrulater cu laturile egale si cu unghiurile drepte.
* @author student
* @version 1
* @since 2023
*/
public class Patrat {
private final int a;
/**
* Constructorul clasei Patrat
* @param a latura patratului
*/
public Patrat(int a) {
super();
this.a = a;
}
/**
*Getter care da acces de citire a variabilei membre care contine dimensiunea
*laturii patratului.
* @return Latura patratului
*/
public int getA() {
return a;
51
}
/**
* Calculeaza perimetrul patratului.
* @return Returneaza perimetrul patratului obtinut inmultind
* latura patratului cu patru.
*/
public int perimetrul() {
return 4*a;
}
/**Calculeaza aria patratului
* @return Returneaza aria patratului, calculata facand produsul a doua laturi.
*/
public int aria() {
return a*a;
}
/**
* Diagonala patratului este ipotenuza unui triunghi dreptunghic isoscel ale carui
* catete sunt egale cu latura patratului.
* @return Dimensiunea diagonalei patratului calculata cu teorema lui Pitagora.
*/
public double diagonala() {
return a*[Link](2);
}
}

52
 Clasa MainApp în fişierul [Link]:

package [Link];

class MainApp {
public static void main(String[] args) {
Patrat p1=new Patrat(2);
[Link]("Patratul cu latura " + [Link]()
+ " are perimetrul "+[Link]()
+ ", aria "+[Link]()
+" si diagonala "+[Link]("%.2f", [Link]()));
}
}

 În exemplul precedent s-au utilizat tag-urile @author, @version, @since, @param şi


@return

 Documentația introdusă va fi afișată în IDE-uri precum IntelliJ sau Eclipse atunci


când se vor declara şi instanţia obiecte de tip Patrat sau se vor apela metodele
documentate

 Generarea documentaţiei se poate face din linie de comandă (poate fi utilizat


terminalul din IntelliJ sau din Eclipse) sau cu ajutorul optiunilor de meniu pentru
generare. În IntelliJ se alege opţiunea de meniu Tools > Generate JavaDoc…, iar
în Eclipse se alege Project > Generate Javadoc… 53
 În linie de comandă documentația aferentă clasei Patrat poate fi generată printr-o
comandă precum cea de mai jos

javadoc -d d:\doc src/capitolul2/documentatie/[Link]

 Comanda va genera documentația clasei Patrat în directorul d:\doc. Comanda a fost


rulata fiind poziţionaţi în directorul proiectului

 Documentaţia generată poate fi vizualizată în browser accesând fişierul [Link]

54
2.11 Redefinirea metodelor
 dacă într-o clasă derivată se defineşte o metodă cu acelaşi nume şi signatură cu o
metodă din clasa de bază, spunem că metoda din clasa derivată o redefineşte pe cea
din clasa de bază (overriding)

 Din subclasă pot fi apelate ambele metode:


 exemplu() – apelează metoda din clasa derivată
 [Link]() –apelează metoda din clasa de bază

package [Link];
class ClasaDeBaza {
int camp;
public ClasaDeBaza(){
camp=1;
}
void afisareCamp(){
[Link]("Executie din supraclasa "+camp);
}
final void fa_ceva() {
[Link]("Metoda finala, nu poate fi redefinita"
+" in clasele derivate");
}
}

55
class ClasaDerivata extends ClasaDeBaza{
int camp;
void setCamp(int camp){
[Link]=camp;
}
@Override
void afisareCamp(){
[Link]("Executie din subclasa "+camp);
}
void afis(){
[Link]();
afisareCamp();
[Link]("Camp din clasa derivata="+[Link]
+"\nCamp din superclasa="+[Link]);
}
//void fa_ceva() {
//[Link]("Eroare de compilare, nu este permisa redefinirea"
+" metodelor finale");
//}
}
class Test{
public static void main(String args[]){
ClasaDerivata f=new ClasaDerivata();
[Link](2);
[Link]();
}
} 56
 În exemplul precedent metoda void afisareCamp() din ClasaDerivata redefinește
metoda void afisareCamp() din ClasaDeBaza

 Utilizarea adnotației @Override nu este obligatorie, dar asigură că se face


redefinire. Dacă se folosește adnotația @Override şi nu se face redefinire se produce
eroare de compilare

 Metoda fa_ceva() din ClasaDeBaza este metoda finala, deci nu poate fi redefinita.
Încercarea de a crea o metodă în ClasaDerivata cu aceeași signatură produce eroare
de compilare

 Din clasa derivată pot sa fie accesate cele 2 metode afisareCamp() (se folosește
super pentru a o accesa pe cea din clasa de bază).

57
2.12 Clase generice
 O clasă generică este o clasă care are parametri generici, enumerați între
parantezele unghiulare care însoțesc definiția clasei.

 Valorile din interiorul parantezelor ungiulare reprezintă tipuri de date care vor fi
stabilite când se declară un obiect de tipul clasei respective.

 Operatorul <> este cunoscut sub denumirea operatorul diamond


package capitolul2.generice1;

class Persoana<T,S>{
T nume;
S varsta;
public Persoana(T nume, S varsta) {
[Link] = nume;
[Link] = varsta;
}
@Override
public String toString() {
return nume + ", " + varsta;
}
}

58
class MainApp {
public static void main(String[] args) {
String nume1="Popescu Ion";
int varsta1=23;
Persoana<String, Integer> p1=new Persoana<String, Integer>(nume1, varsta1);
[Link](p1);

StringBuilder nume2=new StringBuilder("Popescu Ion");


byte varsta2=23;
Persoana<StringBuilder,Byte> p2=new Persoana<StringBuilder,Byte>(nume2, varsta2);
[Link](p2);
}
}

59
2.13 Metode generice
 Metodele generice sunt metode care pot fi apelate cu argumente de diferite tipuri

 Metodele generice au operatorul diamond înaintea tipului returnat. În interiorul


acestuia se enumeră tipurile generice separate prin virgule

package capitolul2.generice2;

public class MainApp {


public static <T> void afiseazaVector(T[] vector ) {
for(T v : vector) {
[Link](v+" ");
}
[Link]();
}

public static void main(String[] args) {


Integer[] v1 = { 1, 2, 3, 4, 5 };
Character [] c={'t','e','s','t'};

afiseazaVector(v1);
afiseazaVector(c);
}
} 60
2.14 Clase imbricate

2.14.1 Noţiuni generale

 O clasă poate fi declarată în interiorul altei clase. Scopul este de a grupa clasele într-
un singur loc, obținând astfel un cod mai ușor de citit şi întreținut

 Clasa interioară poate fi de mai multe tipuri:

 Clasă interioară non-statică


 Clasa interioară membră – se declară direct în clasa exterioară (în afara
oricărei metode) şi are acces la variabilele membre ale acesteia chiar private
fiind.
 Clasa interioară locală – se declara într-un bloc de cod care de obicei este o
metodă
 Clasa interioară anonimă – permite declararea unei clase si instanţierea unui
obiect în același timp. De obicei se plasează în alte metode, dar expresia
clasei anonime poate fi plasata şi direct în clasa exterioară

 Clasă interioara statică – se declara direct în clasa exterioară (în afara oricărei
metode) şi are acces la membri statici ai clasei exterioare, chiar privați fiind 61
2.14.2 Clasă interioară membră
 Clasa interioară membră se declară direct în clasa exterioară (în afara oricărei
metode) şi are acces la variabilele membre ale acesteia chiar private fiind
 Clasa interioară membru poate fi însoțită de modificatorii de acces private, protected,
public
 Într-o a treia clasă se poate declara obiect de tipul clasei interioare (dacă modificatorii
de acces asigura vizibilitate către aceasta) cu ajutorul unui obiect al clasei exterioare

 Fișierul [Link]
package capitolul2.clase_imbricate1;

public class ClasaExterioara {//Outer class


private int a=3;

class ClasaInterioara{//Inner class


private int b=4;
public void afisare(){
[Link]("a="+a+", b="+b);
}
}
} 62
 Fișierul [Link]

package clase_imbricate;

public class MainApp {


public static void main(String[] args) {
ClasaExterioara o1=new ClasaExterioara();
[Link] o2=[Link] ClasaInterioara();
[Link]();
}
}

63
2.14.3 Clasă interioară locală
 Se declară într-un bloc de cod care de obicei este o metodă
 Nu poate avea modificatori de acces şi este vizibilă doar în acel bloc de cod
 Fișierul [Link]
package capitolul2.clase_imbricate2;

class ClasaExterioara {//Outer class


private int a=3;

public void metoda() {


final int b=4;
class ClasaInterioaraLocala{//Local Inner class
public void afisare(){
[Link]("a="+a+", b="+b);
}
}

ClasaInterioaraLocala o=new ClasaInterioaraLocala();


[Link]();
}
64
class MainApp {
public static void main(String[] args) {
ClasaExterioara o1=new ClasaExterioara();
[Link]();
}
}

65
2.14.4 Clasă interioară anonimă
 Clasele anonime permit:
 scrierea comasată a codului
 declararea unei clase și instanțierea ei în același timp

 O clasă anonimă este o expresie. Sintaxa unei clase anonime este asemănătoare cu
apelul unui constructor însoțit de definiția unei clase

 Expresia unei clase anonime conține următoarele:


 Operatorul new
 Numele unei interfețe de implementat sau a unei clase de extins
 Parantezele care contin argumentele unui constructor
 Corpul declarării unei clase
 Fișierul [Link]
package capitolul2.clase_imbricate3;

interface Ordonare {
public void metoda();
}
66
 Fișierul [Link]:

package capitolul2.clase_imbricate3;

public class MainApp {


public static void main(String []args){
Ordonare t=new Ordonare(){
public void metoda(){
[Link]("Ordoneaza prin HeapSort");
}
};
[Link]();
}
}

67
2.14.5 Clase interioare statice
 Clasele interioare pot fi statice. În acest caz instantierea unui obiect al clasei
interioare nu mai necesita un obiect al clasei exterioare (vezi exemplul următor). Din
clasa interioară pot fi accesați membrii statici ai clasei exterioare

package capitolul2.clase_imbricate4;

class ClasaExterioara {//Outer class


private static int a=3;

static class ClasaInterioaraStatica{


private int b=4;
public void afisare(){
[Link]("a="+a+", b="+b);
}
}
}

class MainApp {
public static void main(String[] args) {
[Link] o=new [Link]();
[Link]();
}
}
68
2.14.6 Facilitați pentru operarea cu clase imbricate (Java 11)

 În Java 11 (versiune Java cu suport pe termen lung, lansată în septembrie 2018) a fost introdus
conceptul de cuib (nest) care cuprinde clasele imbricate şi metode care ajută la operarea asupra
unor clase imbricate, precum:
 getNestHost() – returnează clasa exterioară, cea care găzduiește clasele interioare
 getNestMembers() – returnează clasa exterioară şi clasele interioare
 isNestmateOf() – verifică dacă clasa pentru care se apelează se găsește în același cuib ca şi
clasa specificată ca şi parametru de intrare

 Exemplul următor arata cum pot fi utilizate aceste metode (a fost importată ClasaExterioara din
pachetul capitolul2.clase_imbricate1):
package capitolul2.clase_imbricate5;

import [Link];
import capitolul2.clase_imbricate1.ClasaExterioara;

public class MainApp {


public static void main(String[] args) {
Class<[Link]> ci=[Link];
Class<ClasaExterioara> ce=[Link];

[Link]([Link]());
[Link]([Link]());
69
if([Link](ce)) {
[Link](ci +" si "+ce+" se gasesc in acelasi cuib");
}
else {
[Link](ci +" si "+ce+" se gasesc in cuib-uri diferite");
}

[Link]([Link]())
.stream()
.map(Class::getCanonicalName)
.forEach([Link]::println);
}
}

 Ieșirea programului precedent este următoarea:

 Exemplul precedent conține două clase imbricate. Din clasa interioară se pot accesa inclusiv
membrii privați ai clasei exterioare

70
 Metoda getNestHost() returnează numele clasei exterioare, indiferent pentru care clasă se apelează

 Metoda isNestmate() permite verificarea dacă cele două clase se găsesc în același cuib

 În ultimul exemplu s-a creat o listă care conține toți membri din cuib. Toate elementele din stream-ul
asociat listei au fost mapate cu ajutorul metodei getCanonicalName, care returnează numele clasei
împreună cu pachetul în care se găsește. Apoi stream-ul a fost afișat

71
2.15 Clase abstracte
 O clasă abstractă este o supraclasă pentru o ierarhie de clase

 O clasă abstractă conţine câmpuri şi metode normale (pentru care se specifică o


implementare dar şi modele de metode (metode abstracte) care urmează să fie
implementate în mod obligatoriu de clasele normale care extind clasa abstractă)

 O clasa normală poate extinde o clasă abstractă doar dacă implementează metodele
abstracte. Altfel clasa în cauză va conţine metode abstracte care vin pe relaţia de
moștenire şi trebuie făcută abstractă

 O metoda abstractă nu are implementare.

abstract class ClasaAbstracta{


abstract void metoda();
}

 O metodă finală nu mai poate să fie redefinită

 O metodă cu atributul abstract trebuie trebuie să fie redefinită pentru a fi utilizată


printr-o metodă care nu are acelaşi atribut
72
 O clasă abstractă poate să fie extinsă de o altă clasă abstractă sau de o clasă
normală (dacă aceasta implementează metodele abstracte)
class ClasaNormala extends ClasaAbstracta{
@Override
void metoda() {
[Link]("S-a executat metoda");
}
}

 O metodă abstractă se află în mod obligatoriu într-o clasă abstractă sau într-o
interfață

 Clasele normale nu pot contine metode abstracte

 Deoarece metodele abstracte reprezintă numai modele de metode nu se pot face


instanţieri de obiecte pentru clasele abstracte

 Dacă prin extinderea clasei ClasaAbstracta se defineşte o clasă care nu mai este
abstractă se poate face şi o instanţiere
//ClasaAbstracta o=new ClasaAbstracta(); //eroare de compilare
ClasaAbstracta o=new ClasaNormala();

73
2.16 Interfeţe

2.16.1. Noțiuni generale


 Interfeţele sunt un mecanism de abstractizare

 O interfață este un model al unei clase.

 O interfață este un tip abstract utilizat pentru a specifica comportamentul unei clase
(care implementează interfața)

 Dacă o clasă care implementează o interfață nu specifică o implementare tuturor


metodelor abstracte atunci clasa trebuie făcută abstractă

 O clasă care implementează interfața trebuie să specifice codul corespunzător


metodelor din interfață dar poate să declare variabile şi metode care nu apar în
interfaţă

74
 O interfață poate conține:
 Constante (chiar daca se fac declarații de variabile compilatorul le atașează
atributele public static final, transformându-le în acest fel în constante)
 Metode abstracte
 Metode implicite (începând cu Java 8) – metode cu cod care au atributul default
 Metode statice (începand cu Java 8)
 Metode private în interfete (Începând cu Java 9) – apelate din metodele implicite

 Variabilele de tip interfață pot fi utilizate ca argumente ale metodelor şi pot fi obținute
ca rezultat, se pot crea ierarhii de interfețe similare ierarhiilor de clase

 Variabile referinţă la o interfaţă – valoarea unei astfel de variabile poate fi o referință


la un obiect al unei clase care implementează interfața sau a unei clase care extinde
o clasă ce implementează interfața

 Metodele care apar într-o interfaţă sunt în mod implicit publice

 O interfață poate fi extensia unei alte interfeţe, adică poate să adauge noi modele de
metode la o interfaţă care există

 O interfață se declară cu ajutorul cuvântului cheie interface


75
 Implementarea uneia sau mai multor interfețe se face cu ajutorul cuvântului cheie
implements

package capitolul2.interfete1;

interface Figura{
void deseneaza();
}

class Triunghi implements Figura{


@Override
public void deseneaza() {
[Link]("Deseneaza un triunghi!");
}
}

class Cerc implements Figura{


@Override
public void deseneaza() {
[Link]("Deseneaza un cerc!");
}
}

76
class MainApp {
public static void deseneaza(Figura f) {
[Link]();
}
public static void main(String[] args) {
deseneaza(new Triunghi());
deseneaza(new Cerc());
}
}

 În exemplul precedent s-a creat interfața Figura, care conține o metodă abstractă
deseneaza() (atributul abstract poate fi scris explicit sau dedus)

 Pornind de la aceasta interfață se poate realiza o familie de clase care


implementează interfața Figura, o familie de clase care știu sa deseneze diferite figuri
geometrice.

 În programul principal s-a creat metoda statică desenează (era necesar să fie statică
pentru că este apelată din main care este o funcţie statică), metodă care are
parametrul de intrare un obiect de tip Figura. Metoda apelează metoda deseneaza()
a acelui obiect. La apel se pot transmite instanțe ale unei clase care implementează
direct sau indirect interfața. Direct este exemplificat în exemplu, iar indirect ar fi fost
dacă se transmitea o instanță a unei clase care extinde o clasă ce implementează
77
interfața Figura.
 În funcție de instanța transmisă la apel se apelează o anumită metodă de desenare
 Exemplul utilizează polimorfismul, abilitatea unui obiect de a lua mai multe forme
 API-ul Java ofera interfața Comparable, care dispune de metoda abstractă
compareTo() prin care se poate specifica cum să se compare un obiect al unei clase
cu un altul, care a fost dat ca şi parametru. Modul concret în care se face comparația
se specifică la nivelul claselor care implementează această interfață
package capitolul2.interfete2;
class Persoana implements Comparable<Persoana>{
private String nume;
private Integer varsta;

public Persoana(String nume, Integer varsta) {


[Link] = nume;
[Link] = varsta;
}
public String getNume() {
return nume;
}
@Override
public String toString() {
return nume + ", " + varsta;
}
@Override
public int compareTo(Persoana o) {
return [Link]([Link]);
}
78
}
class MainApp {
public static void main(String[] args) {
Persoana p1=new Persoana("Ionel",20);
Persoana p2=new Persoana("Ana",23);

//switch cu expresii caracteristica permanenta din Java 14


String mesaj=switch([Link](p2)) {
case 0->[Link]()+" are aceeasi varsta ca si "+[Link]();
case 1->[Link]()+" este mai in varsta decat "+[Link]();
case -1->[Link]()+" este mai tanar decat "+[Link]();
default->"valoare eroanata";
};
[Link](mesaj);
}
}

 Notația generică <Persoana> determină tipul parametrului din metoda de comparare.


Fără aceasta notație parametrul metodei copareTo ar fi fost de tip Object şi era
necesară operația de cast pentru a accesa caracteristicile din clasa derivată

79
2.16.2 Metode implicite, metode statice (Java 8) şi
metode private în interfețe (Java 9)
 Posibilitatea de a adăuga metode implicite în interfețe (metode cu cod care au
atributul default) a fost introdusă în Java 8. La fel şi posibilitatea de a adăuga metode
statice în interfeţe

 Posibilitatea de a adăuga metode private în interfețe a fost introdusă în Java 9

 Metodele implicite se definesc cu ajutorul cuvântului cheie default. Se utilizează când


se dorește să se adauge o nouă funcționalitate unei interfețe și în același timp să se
păstreze compatibilitatea cu clasele care deja implementează interfața

 Exemplul următor ilustrează cum se pot utiliza aceste metode în interfețe

 Interfața Figura din exemplul precedent, a fost extinsă cu 2 metode implicite,


culoare_contur() şi culare_umplere(). Clasele care implementează această interfață
nu trebuie să işi schimbe structura (să implementeze metodele implicite, dar pot face
aceasta dacă e necesar). Clasa Triunghi redefinește metoda implicită
culoare_contur(), iar clasa Cerc folosește metoda implicită culoare_contur() din
interfață. Ambele clase utilizează metoda culoare_umplere() din interfață

80
package capitolul2.interfete3;

interface Figura{
int grosimea_liniei_de_desenare=10;
abstract void deseneaza();
static void metoda_statica () {//metode statice in interfete incepand cu Java 8
[Link]("Exemplu metoda statica.");
}
private String culoare() {//metode private incepand cu Java 9
return "rosie";
}
default String culoare_umplere() { //metode implicite incepand cu Java 8
return " Culoarea de umplere "+culoare()+".";
}
default String culoare_contur() {
return " Culoarea conturului "+culoare()+".";
}
}
class Triunghi implements Figura{
@Override
public String culoare_contur() {
return "Culoarea conturului neagra.";
}
@Override
public void deseneaza() {
[Link]("Deseneaza un triunghi! "+culoare_contur()+culoare_umplere());
} 81
}
class Cerc implements Figura{
@Override
public void deseneaza() {
[Link]("Deseneaza un cerc!"+culoare_contur()+culoare_umplere());
}
}

public class MainApp {


public static void deseneaza(Figura f) {
[Link]();
}
public static void main(String[] args) {
deseneaza(new Triunghi());
deseneaza(new Cerc());
Figura.metoda_statica();
[Link]("Grosimea liniei de desenare este:"
+Figura.grosimea_liniei_de_desenare);

//eroare de compilare daca se incearca modificarea unei constante (public static final)
//Figura.grosimea_liniei_de_desenare=4;
}
}

82
 Interfața Figura din pachetul capitolul2.interfete1 a fost extinsă cu diferite elemente
prezentate în continuare, ajungând la forma din pachetul capitolul2.interfete3

 Interfața Figura a fost extinsă cu 2 metode implicite, culoare_contur() şi


culare_umplere(). Clasele care implementează această interfață nu trebuie să işi
schimbe structura (să implementeze metodele implicite, dar pot face aceasta dacă
este necesar). Clasa Triunghi redefinește metoda implicită culoare_contur(), iar clasa
Cerc folosește metoda implicită culoare_contur() din interfață. Ambele clase utilizează
metoda implicită culoare_umplere() din interfață.

 Cele 2 metode implicite din interfață apelează metoda privată a interfeței numită
culoare(), metodele private pot să fie introduse în interfețe începând cu Java 9

 Interfața Figura a fost extinsă şi cu o metodă statică, care se apelează cu


Nume_interfata.nume_metoda_statica, în programul principal

 De asemenea interfața Figura a fost extinsă cu constanta întreagă numită


grosimea_liniei_de_desenare. Cu toate că declarația acesteia este similară cu a unei
variabile, ea nu este o variabilă pentru că, compilatorul îi adaugă atributele public
static final. Fiind statică, poate fi accesată pentru a fi afișată în programul principal cu
Nume_interfata.nume_constanta, iar încercarea de a-i modifica valoarea eșuează cu
mesajul The final field Figura.grosimea_liniei_de_desenare cannot be assigned,
demonstrând astfel că este vorba despre o constantă 83
2.16.3 Interfețe funcționale (Java 8)
 O interfață este funcțională dacă are o singură metodă abstractă. Metode implicite,
statice si private pot sa fie oricâte

 Java 8 introduce adnotația @FunctionalInterface si pachetul [Link] care


conține un număr important de interfețe funcționale.

 Amplasarea adnotației @FunctionalInterface deasupra unei interfețe asigură că acea


interfață este într-adevăr una funcțională (dacă nu este funcțională se produce eroare
de compilare). Interfețele se află in ierarhii de interfețe, deci pot avea metode abstracte
moștenite de la super interfețe

 Interfețele funcționale de obicei sunt implementate prin expresii Lamda, de asemenea


introduse în Java 8, care vor fi discutate în cadrul unui subcapitolului următor

 În exemplul următor se consideră clasa Persoana cu variabilele membre nume și


varsta. Datele mai multor persoane vor fi adăugate unei colecții de obiecte de tip List și
apoi vor fi afișate filtrat. Se construiește şi se utilizează interfața funcțională Filtru cu o
metodă abstractă, numită test() şi se utilizează interfața [Link],
din API-ul Java 8, care de asemenea are o metodă pentru testarea unei condiţii
84
package capitolul2.interfete_functionale;

import [Link];
import [Link];
import [Link];

@FunctionalInterface
interface Filtru<T>{
public boolean test(T p);
}

class Persoana{
private String nume;
private int varsta;
public Persoana(String nume, int varsta) {
[Link] = nume;
[Link] = varsta;
}
public String getNume() { return nume; }
public int getVarsta() { return varsta; }
@Override
public String toString() {
return "Persoana [nume=" + nume + ", varsta=" + varsta + "]";
}
}

85
class MainApp {
static void afisare_filtrata1(List<Persoana> pers, Filtru<Persoana> f) {
for(Persoana p:pers)
if([Link](p))
[Link](p);
}
static void afisare_filtrata2(List<Persoana> pers, Predicate<Persoana> f) {
for(Persoana p:pers)
if([Link](p))
[Link](p);
}

public static void main(String[] args) {


List<Persoana> lista=new ArrayList<Persoana>();

[Link](new Persoana("Vladut",23));
[Link](new Persoana("Oana",19));
[Link](new Persoana("Iulia",22));
afisare_filtrata1(lista, new Filtru<Persoana>() {
@Override
public boolean test(Persoana p) {
return [Link]()<20;
}
});

86
afisare_filtrata2(lista, new Predicate<Persoana>() {
@Override
public boolean test(Persoana p) {
return [Link]()<20;
}
});

afisare_filtrata2(lista, p->[Link]()<20); //cu expresie Lambda


}
}

 Filtru este o interfață funcțională şi generică, tipul parametrului de intrare al metodei


abstracte test() va fi stabilit de fiecare clasă care implementează interfața

 Implementarea interfeței Filtru se face prin expresia unei clase anonime, împreună cu
instanţierea unui obiect şi transmiterea acestuia ca şi parametru către metoda
afisare_filtrata1()

 Metoda afisare_filtrata2(), realizează același lucru ca şi metoda afisare_filtrata1() (şi la


apel primește un obiect similar), doar că utilizează interfața funcțională Predicate, din
API-ul Java. Interfața functională Filtru a fost creată pentru a exemplifica cum se poate
crea o interfață funcțională, dar de foarte multe ori se găsesc interfețele funcționale
potrivite în pachetul [Link] şi se recomandă utilizarea acestora în loculul
scrierii unora noi şi similare, pentru a avea un cod mai concis
87
 Ultima afișare filtrată folosește o expresie Lamda, este cea mai concisă, expresiile
Lamda vor fi detaliate în subcapitolul următor

 O mică parte din interfețele funcționale ale pachetului [Link] sunt


următoarele:
 Predicate<T> reprezintă o operație care accepta un singur parametru de intrare și
returnează un boolean

 Consumer<T> reprezintă o operație care accepta un singur parametru de intrare si nu


produce nici un rezultat
 BiConsumer<T,U> are o funcție care acceptă două argumente şi nu returnează nimic

 Function<T,R> reprezintă o funcție care are un parametru de intrare si produce un


rezultat
 BiFunction<T,U,R> are o funcție care are doi parametri de intrare și produce un
rezultat

 UnaryOperator<T> reprezintă un o operație cu un singur operand care produce un


rezultat de același tip cu operandul
 BinaryOperator<T> are o funcție care are doi parametri de intrare de același tip si
produce un rezultat de același tip ca și parametrii de intrare
 etc 88
2.17 Expresii Lambda
 Expresiile Lambda au fost introduse în Java 8

 O expresie Lambda este caracterizată prin următoarea sintaxă:

parametri -> corpul expresiei

 Expresiile Lambda sunt utilizate în principal pentru a defini implementarea inline (printr-o linie de
cod) a unei interfețe cu o singură metodă abstractă (interfaţă funcţională)

 Expresia Lambda elimină necesitatea unei clase anonime și oferă o capacitate de programare
funcțională foarte simplă, dar puternică pentru Java.

 Caracteristicile expresiilor Lambda sunt următoarele:.

 Nu este necesar să se declare tipul unui parametru. Compilatorul poate sa deducă tipul lui

 Parantezele rotunde sunt necesare doar pentru cel puțin doi parametri. Pentru un parametru
sunt opționale

 În corpul expresiilor sunt necesare paranteze acolade doar dacă acestea conțin două sau mai
multe instrucțiuni

 Cuvântul return este opțional – compilatorul automat returnează valoarea dacă corpul are o
singură expresie care să returneze valoarea 89
 Fișierul [Link] are conținutul de mai jos:
package [Link];

class Persoana{
private String nume;
private int varsta;
public Persoana(String nume, int varsta) {
[Link] = nume;
[Link] = varsta;
}
public String getNume() {
return nume;
}
public int getVarsta() {
return varsta;
}
@Override
public String toString() {
return nume + " " + varsta;
}
}

90
 Fisierul [Link] are conținutul de mai jos
package [Link];

import [Link];
import [Link];
import [Link];
import [Link];

class MainApp1 {
public static void main(String[]args) {
List<Persoana> pers=new ArrayList<Persoana>();
[Link](new Persoana("Maria",23));
[Link](new Persoana("Ana",24));
[Link](new Persoana("Oana",22));

[Link](pers);
[Link]("Colectia aleatoare: "+pers);

[Link](pers, new Comparator<Persoana>() {


@Override
public int compare(Persoana o1, Persoana o2) {
return [Link]().compareToIgnoreCase([Link]());
}
});
[Link]("Colectia ordonata dupa nume in Java 7 style: "+pers);

91
[Link](pers);
[Link](pers,(Persoana a,Persoana b)->[Link]().compareToIgnoreCase([Link]()));
[Link]("Colectia ordonata dupa nume in Java 8 style 1: "+pers);

[Link](pers);
[Link](pers,(a,b)->{
return [Link]().compareToIgnoreCase([Link]());
});
[Link]("Colectia ordonata dupa nume in Java 8 style 2: "+pers);

[Link](pers);
[Link](pers,(Persoana a,Persoana b)->{
if ([Link]()<[Link]()) return -1;
else
if([Link]()>[Link]()) return 1;
else return 0;
});

[Link]("Colectia ordonata dupa varsta in Java 8 style 3: "+pers);


}
}

92
 În Java 7 ordonarea unei colecții se face cu ajutorul unei clase care implementează interfața de
comparare

 În Java 8 această problemă poate fi rezolvată cu ajutorul notațiilor Lambda

 Parametrii care apar în expresia Lambda sunt parametrii metodei de comparare a interfeței. Tipul
acestor parametrii poate să fie specificat (Persoana a, Persoana b) sau nu (a,b) (vezi slide-ul
precedent).

 Corpul expresiei Lambda conține codul din metoda de comparare, care este automat returnat

 În cazul în care corpul expresiei Lambda conține mai multe linii de cod, este necesar să se utilizeze
paranteze acolade și să se returneze prin utilizarea lui return valorile dorite (vezi exemplul de
ordonare după vârsta din slideul precedent)

package [Link];
import [Link];
import [Link];
import [Link];

class MainApp2 {
public static void main(String[]args) {
JFrame myFrame = new JFrame("JButton");
[Link](JFrame.EXIT_ON_CLOSE);
[Link](300, 300);
[Link]().setLayout(null); 93
JButton myButton=new JButton("Apasa-ma!");
[Link](100, 10, 100, 20);
[Link](e->[Link](null,"Click pe buton"));
[Link](myButton);
[Link](true);
}
}

 Exemplul de mai sus ilustrează cum se poate adăuga un listener pe un buton în stilul Java 8, cu
ajutorul expresiilor Lambda

94
2.18 Referințe de metode
 O referință de metodă este o construcție Java 8 care poate fi folosită pentru a referi o metodă fără a
o apela

 O referință de metodă poate fi identificată cu ajutorul notatiei :: care separă o clasă sau un obiect de
numele unei metode

 Obținerea unei referințe de constructor se poate realiza în felul următor:

String::new

 Obținerea unei referințe către o metodă statică:


String::valueOf

 Obținerea unei referințe către o metodă legată de un obiect:


str::toString

 Obținerea unei referințe către o metodă nelegată de un obiect:


String::toString

95
 Fișierul [Link] are conținutul de mai jos:
package capitolul2.referinte_metode;

import [Link];

class MainApp {
public static void main(String args[]) {
//metoda [Link]() introdusa in Java 9
List<String> orase = [Link]("Timisoara","Arad","Cluj","Targu-Mures");
[Link]([Link]::println);
}
}

 forEach este un operator introdus în Java 8 pentru a traversa colecțiile. Acesta traversează colecția
și realizează pentru fiecare element actiunea transmisă ca și parametru de intrare

 O referință către metoda println a fost transmisă ca și parametru de intrare a metodei forEach care
traversează colecția și realizează acțiunea dată de parametrul de intrare pentru fiecare element al
colecției, lucru care va determina afișarea colecției în consolă

96
2.19 Stream API
 Introdus în Java 8

 Un stream este un iterator al cărui rol este de a accepta un set de acțiuni să fie aplicate fiecăruia din
elementele pe care le conține

 Un stream reprezintă o secvență de obiecte dintr-o sursă cum ar fi o colecție de obiecte, un vector
sau mai multe elemente individuale.

 Stream-urile au fost proiectate cu scopul de a face procesarea colecțiilor mai simplă și concisă.

 Spre deosebire de colecții cu ajutorul stream-urilor se pot face o serie de prelucrări încă din etapa
de declarare

 Operațiile stream-urilor pot să fie ori intermediare ori terminale. Operațiile terminale returnează un
rezultat de un anumit tip, iar cele intermediare returnează însăși stream-ul, în acest fel se pot
înlănțui mai multe operații
 Operațiile stream-urilor se aplică fiecărui element din stream şi pot să fie executate secvențial sau
paralel

 Colecțiile în Java 8 au fost extinse deci se poate crea un Stream foarte ușor apelând una din
metodele [Link]() or [Link]()
 Stream-ul paralel împarte taskul furnizat în mai multe taskuri pe care le rulează în diferite fire de
execuție. 97
 Fisierul [Link]:
package [Link];

import [Link];

class MainApp {
public static void main(String[] args) {
//metoda [Link]() introdusa in Java 9, creeaza o colectie imutabila de obiecte
List<String> orase = [Link]("Timisoara","Arad","Cluj","Targu-Mures");
[Link]([Link]::println);
//[Link]((o)->[Link](o));

[Link]("Orase care incep cu litera 'T':");


orase
.stream()
.filter((s) -> [Link]("T"))
.forEach([Link]::println);

[Link]("\nLista ordonata a oraselor, scrisa cu litere mari: ");


orase
.stream()
.map(String::toUpperCase)
//.map((o)->[Link]())
.sorted((a, b) -> [Link](b))
.forEach([Link]::println);
}
} 98
 În primul exemplu din programul precedent apelând metoda stream() se creează un stream
secvențial pentru colecția de obiecte de tip List ce conține denumirile orașelor. În continuare stream-
ul se filtrează pentru a rămâne în el doar orașele a căror denumire începe cu litera ‘T’, iar apoi
stream-ul filtrat se afişează

 Cel de-al doilea exemplu utilizează stream-uri pentru a afişa elementele din colecţie ordonat şi cu
litere mari

 Metoda forEach aplică metoda transmisă ca şi parametru de intrare fiecărui element din colecţie
sau fiecărui element din stream, în funcţie de modul de apel al acesteia.

 Întrucât în exemplele precedente parametrul de intrare al metodei forEach este o referinţă a


metodei println, pentru stream-ul asociat monitorului, această metodă se va aplica fiecărui element
afişând-ul pe ecran

 Parametrul de intrare al metodei forEach esta un Consumer (interfaţă funcţională cu o metodă care
are un parametru de intrare şi nu returnează nimic), aceasta putând fi implementat printr-o expresie
Lambda. Parametrul de intrare al expresiei Lambda reprezintă elementul asupra căruia se va aplica
acțiunea din corpul expresiei. Acesta se va aplica rând pe rând fiecărui element din stream

 Metoda filter() are un parametru de intrare de tip Predicate (interfaţă funcţională cu o metodă care
are un parametru de intrare asupra căruia se realizează un test, rezultatul testului fiind returnat sub
forma unui boolean). În exemplul precedent implementarea acestui Predicate s-a făcut printr-o
expresie Lambda care are un parametru de intrare (elementul din stream) iar corpul expresiei
Lamba reprezintă testul care se realizează asupra parametrului de intrare si care este automat
returnat. Metoda filter() se va aplica fiecărui element din stream, lăsând în stream-ul de ieşire doar
elementele care trec testul 99
 Metoda map() are un parametru de intrare de tip Function (interfaţă funcţională cu o metodă
abstractă care are un parametru de intrare şi produce un rezultat). În exemplul precedent, metoda
map primeşte ca şi parametru de intrare o referinţă către metoda toUpperCase din clasa String.
Acesta metoda va fi apelata pentru fiecare element din stream-ul de intrare transformand-ul în
majuscule în stream-ul de ieşire.

 Operaţia de mapare pune în corespondenţă fiecare element din stream-ul de intrare către un
element din stream-ul de ieşire cu ajutorul unei funcţii

 Metoda sorted() are un parametru de intrare de tip Comparator (interfaţă funcţională cu o metodă de
comparare). Metoda de comparare a fost implementată printr-o expresie Lambda, prin care se
specifică cum să se compare elementele din stream pentru a-l ordona. Corpul expresiei Lambda
trebuie să returneze o valoare pozitivă când primul parametru al expresiei este mai mare decât al
doilea după criteriul de comparare, valoarea zero când cei doi parametri sunt egali şi valoare
negativă când al doilea parametru este mai mare decât primul

 În cele două exemple cu stream-uri din programul precedent operaţia terminală este forEach care
afişează elementele stream-ului pe ecran.

 Operația terminală poate fi collect([Link]()) care creează o nouă colecție cu elementele


din stream

 Exemplul următor creează o listă în care pune numerele naturale extrase dintr-o listă de întregi

100
List<Integer> intregi=[Link](-4,3,-2,5,-8,9);
List<Integer> nr_naturale=intregi
.stream()
.filter((nr)->nr>=0)
.collect([Link]());

[Link]("Numere naturale extrase din lista de intregi:");


nr_naturale.forEach([Link]::println);

 În exemplul următor operaţia terminală este count() care returneză numărul de elemente din
stream. Exemplul creează un stream paralel pentru o colecție de stringuri, se pune un filtru care
lasă în stream doar elementele necompletate și apoi se determină numărul acestora cu ajutorul
metodei count

List<String> strings = [Link]("aaa", "", "zz", "bbb", "eee"," ", "ddd");

int count = (int) strings


.parallelStream()
.filter(string -> [Link]())
.count();

 Metoda isBlank() a fost introdusă în Java 11 şi returnează true când string-ul conţine şirul vid sau
doar spaţii albe

101
2.20 Clasa Optional
 Clasa Optional oferă suport pentru evitarea excepției NullPointerException care apare frecvent în
dezvoltarea programelor

 Clasa Optional este diponibilă în API-ul Java începând cu Java 8 şi a continuat să fie îmbunătățită
prin adăugarea de noi metode în Java 9 (or, ifPresentOrElse, stream), în Java 10 (prin introducerea
metodei orElseThrow ) în Java 11 (prin introducere metodei isEmpty), etc

 Evitarea excepţiei NullPointerException implică de obicei multe verificări ale null-ului. Clasa
Optional oferă facilitați pentru scrierea unui cod fără multe teste ale null-ului.

 Prin clasa Optional se pot specifica valori alternative care să fie returnate sau cod alternativ care să
fie rulat când se întâlnește null
 Utilizarea clasei Optional creste lizibilitatea codului

 Metoda de mai jos afișează numărul de cuvinte al unui propoziții dată ca şi parametru de intrare al
funcţiei, printr-un String ale cărui cuvinte sunt separate prin spaţii
public static void nr_cuvinte(String s) {
[Link]("Propozitia: " + s + " are " + [Link](" ").length + " cuvinte");
}

 Dacă la afelul funcției se transmite un String care are valoarea null, aceasta determină producerea
exceptiei NullPointerException

 Pentru evitarea excepției se poate testa null-ul precum în exemplul următor: 102
public static void nr_cuvinte(String s) {
if(s!=null)
[Link]("Propozitia: " + s + " are " + [Link](" ").length + " cuvinte");
else
[Link]("Stringul e null");
}

 Lucrând cu clasa Optional, acest test se poate face în felul următor:


public static void nr_cuvinte_cu_optional(String s) {
Optional<String> opt = [Link](s);
if ([Link]())
[Link]("Propozitia: " + [Link]() + " are " + [Link]().split(" ").length +
" cuvinte");
else
[Link]("Stringul e null");
}

 Optional este un container care poate să conțină o valoare sau nu. Dacă valoarea este prezentă
funcția get() o va returna

 Metoda ofNullable() returnează un obiect Optional care va descrie valoarea specificată ca şi


parametru, dacă aceasta nu este null, în caz contra va returna un Optional gol ([Link])

 API-ul oferă metode adiționale care depind de prezența sau absenta valorii cum ar fi orElse()
metodă care returnează o valoare implicită în caz că valoarea inspectată nu e prezentă sau metoda
ifPresent() care execută un bloc de cod dacă valoarea este prezentă 103
 Un exemplu de utilizare a acestora este următorul:
package capitolul2.optional1;

import [Link];

class MainApp {
public static void fara_optional_uppercase(String s) {
if(s!=null) {
[Link]([Link]());
}
else {
[Link]("valoare lipsa".toUpperCase());
}
}
public static void cu_optional_uppercase(String s) {
Optional<String> opt=[Link]();
opt=[Link](s);
[Link]([Link]("valoare lipsa").toUpperCase());
}

public static void main(String[]args) {


String s=null;
//String s="test";
fara_optional_uppercase(s);
cu_optional_uppercase(s);
}
} 104
 În exemplul precedent metoda orElse() va returna valoarea din container dacă aceasta este
prezentă sau parametrul de intrare în caz contrar. Valoarea returnată de funcţie este apoi
transformate în majuscule

 Un exemplu de utilizare a metodei ifPresent() este următorul:

[Link](value ->[Link]([Link]()) );

 Metoda primește ca şi parametru de intrare un obiect care implementează interfața funcțională


Consumer, interfață ce conține o metodă care primește un singur parametru de intrare şi care nu
returnează nici un rezultat

 Parametrul de intrare al funcției ifPresent() a fost transmis printr-o expresie Lambda care afișează
cu majuscule valoarea din containerul Optional

 O situație frecventă de producere a excepției NullPointerException se întâlnește în aplicațiile cu


baze de date în care apar situații de genul:
Persoana p=findPersoanaById(2);
[Link]([Link]());

 În exemplul de mai sus, dacă persoana căutată nu se găseşte, rândul următor produce
NullPointerException

 Situaţia se poate rezolva lucrând cu Optional şi cu metoda ifPresent() în felul următor


105
Optional<Persoana> optional = findPersoanaById(2);

[Link](p -> {
[Link]("Numele persoanei este " + [Link]());
});

 Metoda findPersoanaById() de mai sus returnează un obiect de tip Optional asociat unui obiect
de tip Persoana

 Metoda or() disponibilă începând cu Java 9, returneză obiectul de tip Optional care descrie valoarea
dacă aceasta este prezentă în container, iar în caz contrar returnează un obiect de tip Optional
implicit
String sir = "o valoare nenula";
//String sir=null;

Optional<String> optional = [Link](sir);


Optional<String> optDefault = [Link]("o valoare implicita");
[Link]([Link](() -> optDefault));

 Parametrul de intrare al metodei or() este de tip Supplier (interfață funcțională cu o metodă care nu
are nici un parametru de intrare și care returnează un rezultat) așadar poate fi implementat printr-o
expresie Lambda

 Metoda ifPresentOrElse() disponibilă începând cu Java 9, realizează o anumită acțiune specificată


asupra valorii din container dacă aceasta este prezentă, în caz contrar realizează o acțiune „empty
based” cum ar fi afișarea unui mesaj 106
// String sir="un sir de caractere";
String sir=null;

Optional<String> optional = [Link](sir);


[Link]([Link]::println,()->[Link]("Valoare lipsa"));

 Metoda orElseThrow() este disponibilă începând cu Java 10. Metoda returnează valoarea din
containerul Optional dacă aceasta este prezentă, iar dacă nu este prezentă aruncă excepția
[Link]: No value present. Metoda este similară cu metoda get şi
începând cu Java 10 se recomandă utilizarea acesteia în locul metodei get()
try {
//Integer a = 1;
Integer a=null;
Optional<Integer> optional = [Link](a);
Integer b = [Link]();
[Link]("b="+b);
}
catch(NoSuchElementException ex) {
[Link](ex);
}

 În exemplul de mai sus, metoda orElseThrow() va face ca b să primească valoarea lui a, dacă a nu
este null. Daca a este null metoda va genera excepția [Link]

107
try {
List<Integer> intregi=[Link](1,3,5);
//List<Integer> intregi=[Link](1,3,5,8);
int primulPar = [Link]()
.filter(i -> i % 2 == 0)
.findFirst()
.orElseThrow();
[Link]("Primul intreg par din lista este: "+primulPar);
}
catch(NoSuchElementException ex) {
[Link](ex);
}

 În exemplul precedent, metoda findFirst() va returna un Optional. Acesta poate să conțină sau nu
un element în container în funcție de situație. Metoda orElseThrow() va returna elementul din
containerul Optional, dacă există un astfel de element, în caz contrar va arunca excepție

 Metoda isEmpty este disponibilă în clasa Optional începând cu Java 11. Metoda verifică dacă
containerul Optional este gol sau nu. Metoda a fost introdusă ca o alternativă la utilizarea metodei
isPresent cu negație

108
package capitolul2.optional3;

import [Link];

class MainApp {
public static void main(String[] args) {
String s = null;
[Link](![Link](s).isPresent());
[Link]([Link](s).isEmpty());
s = "test";
[Link](![Link](s).isPresent());
[Link]([Link](s).isEmpty());
}
}

109
2.21 Inferența tipului la varaibilele locale (local variable
type inference)
 Inferența tipului la variabilele locale a fost introdusă în Java 10
 Termenul de inferență desemnează o „operație logică de trecere de la un enunț la altul și în care
ultimul enunț este dedus din primul” (dex). Inferența tipului se referă la deducerea tipului de către
compilator. Tipul poate fi dedus doar pentru variabile locale, iar notația folosită este var
 Exemplul următor arata cum poate fi utilizat var :
package capitolul2.inferenta_tipului;

import [Link];
import [Link];
import [Link];

public class MainApp {


public static void main(String[]args) {
[Link]("Inainte de Java 10:");
int nr1=3;
String s1="abc";

[Link]("nr="+nr1);
[Link]("Sirul introdus:"+s1+" are "+[Link]()+" caractere");

[Link]("\nCu Java 10:");


var nr2=3;
var s2="abc"; 110
[Link]("nr="+nr2);
[Link]("Sirul introdus:"+s2+" are "+[Link]()+" caractere");

[Link]("\nInainte de Java 10:");


Map<String,List<String>> orase_in_judete1=new HashMap<String,List<String>>();
orase_in_judete1.put("Arad", [Link]("Ineu","Sebis","Arad"));
orase_in_judete1.put("Timis", [Link]("Timisoara","Lugoj","Faget"));

for([Link]<String,List<String>> o:orase_in_judete1.entrySet()) {
[Link]([Link]()+": "+[Link]());
}

[Link]("\nCu Java 10:");


var orase_in_judete2=new HashMap<String,List<String>>();
orase_in_judete2.put("Arad", [Link]("Ineu","Sebis","Arad"));
orase_in_judete2.put("Timis", [Link]("Timisoara","Lugoj","Faget"));

for(var o:orase_in_judete2.entrySet()) {
[Link]([Link]()+": "+[Link]());
}
}
}

 După cum se observă în exemplul de mai sus, începând cu Java 10, la declararea unei variabile
locale se poate pune cuvântul var în locul specificării tipului concret, compilatorul fiind cel care
deduce tipul efectiv. Utilizarea lui var este avantajoasă în special in cazul unor tip-uri lungi (vezi
ultimul exemplu) 111
 După cum se observă în exemplul de mai sus, începând cu Java 10, la declararea unei variabile
locale se poate utiliza cuvântul var în locul specificării tipului concret, compilatorul fiind cel care
deduce tipul efectiv. Utilizarea lui var este avantajoasa în special in cazul unor tip-uri lungi (vezi
ultimul exemplu)

 În exemplul de mai sus s-a creat o colecție de tip Map, care pune în corespondență chei unice către
anumite valori. Cheile au fost alese String-uri şi reprezintă denumiri de județe, iar valorile sunt
colecții de tip list imutabile care conțin orașe din județele alese.

 Utilizarea lui var are anumite restricții. var NU poate fi utilizat pentru:
 Variabile locale neinițializate

var a; //error: Cannot use 'var' on variable without initializer

 Variabile locale inițializate cu null


var a=null; //error:Cannot infer type for local variable initialized to 'null'

 Parametri de intrare ai unor metode


void afisare(var a) { //error: 'var' is not allowed here
[Link](x);
}

 Tipul returnat de o metoda


var calculeaza() { //error: 'var' is not allowed here
return 2*3*4;
} 112
 Parametri de intrare ai unui constructor
class Punct{
private int x,y;
public Punct(var x, var y) {//error: 'var' is not allowed here
this.x=x;
this.y=y;
}
//...
}
 Variabile membre în clase
class Punct{
private var x,y; //error: 'var' is not allowed here
public Punct() {}
//...
}

 Vectori inițializați
var v = { "a", "b", "c" }; //error: Array initializer needs an explicit target-type

 Expresii lambda
Predicate<Integer> predicate1 = n -> n%2 == 0; //ok
int x=5;
[Link]([Link](x)?x+" este par":x+" este impar");

var predicate2 = n -> n%2 == 0; //error: Lambda expression needs an explicit target-type

113
2.22 Tipul înregistrare (record)
 A fost introdus în Java 14 ca şi caracteristică cu previzualizare şi a devenit o
caracteristică permanentă începând cu Java 16 (martie, 2021)

 Tipul record este un tip special de clasă care ajută la reducerea codului standard
(boilerplate code).

 Definirea unei înregistrări este o modalitate concisă de definire a unui obiect imutabil
care deține date.

 Crearea unei înregistrări se face cu ajutorul cuvântului cheie record


 În IntelliJ crearea unei înregistrări se realizează cu ajutorul comenzii New Class, apoi
se introduce numele clasei şi se alege tipul Record. În Eclipse comanda pentru
crearea unei înregistrări este New Record. În continuare se creează în IntelliJ
înregistrarea cu numele Persoana

114
 În fişierul [Link] a fost creată înregistrarea Persoana cu următorul conținut
package [Link];

public record Persoana() {}

 Se completează între parantezele rotunde lista de componente ale record-ului

package [Link];

public record Persoana(String nume,int varsta) {}

 record-ul construit mai sus reprezintă o clasă finală care are:


 variabile membre nume şi varsta finale şi private
 constructor cu parametri
 gettere care asigură acces de citire a variabilelor membre private şi care au
aceeași denumire cu variabilele membre
 metoda toString() care returnează un String ce conține valorile variabilelor
membre
 Metoda hashCode() care returneză [Link](lista_componentelor); în
cazul de faţă [Link](nume,varsta);
 Metoda equals care redefinește metoda equals din clasa Object astfel încât cele
două obiecte comparate nu sunt egale doar în situația în care indică spre
aceeași referință ci şi dacă variabilele membre ale celor două obiecte comparate
au aceleași valori
115
 Tipul record nu poate extinde alte clase întrucât implicit extinde clasa
[Link] şi în java nu este permisă moștenirea multiplă

 O clasă normală nu poate extinde o clasă de tip record pentru ca aceasta este finală

 O clasă de tip record poate implementa interfețe

 Orice alt câmp care se declară într-un record în afara listei de componente (în afara
parantezelor rotunde şi în interiorul parantezelor acolade) trebuie să fie declarat static

 O clasă de tip record poate conţine metode normale

 În programul principal din fişierul [Link] se declară şi se instanţiază două


obiecte de tip record

116
package [Link];

class MainApp {
public static void main(String[] args) {
Persoana p1 = new Persoana("Oana", 23);
Persoana p2 = new Persoana("Oana", 23);
[Link](p1);
[Link]("Persoana cu numele " + [Link]() + " are varsta " + [Link]());
[Link]([Link](p2));
}
}

 Se observă existența constructorului cu parametri, a metodei toString(), a getterelor


care dau acces de citire a câmpurilor nume și varsta şi a metodei equals care
returnează true cu toate ca cele două obiecte comparate nu indică spre aceeași
referință dar au același conținut

117
2.23 Design patterns
 Dacă o problema apare de mai multe ori, soluţia ei este descrisă ca un şablon
(pattern).

 Un şablon de proiectare descrie o problemă care se întâlneşte în mod repetat în


proiectarea programelor şi soluţia generală pentru problema respectivă

 Un şablon este o soluţie a unei probleme, într-un anumit context

 Soluţia este exprimată folosind clase şi obiecte. Atât descrierea problemei cât şi a
soluţiei sunt abstracte astfel încât să poată fi folosite în multe situaţii diferite.

 Folosind şabloanele de proiectare putem face codul mai flexibil, reutilizabil şi uşor de
întreţinut.

 Cartea de referinţă pentru şabloane de proiectare este “Design Patterns: Elements


of Reusable Object-Oriented Software”, având ca autori pe Erich Gamma, Richard
Helm, Ralph Johnson şi John Vlissides. Ea este adesea numită “Gang of Four Book”
(GoF). Apărută în 1994, ea s-a bucurat de un mare succes şi a activat subiectul
şabloanelor în dezvoltarea software, în ultimii ani acesta fiind tratat în numeroase
conferinţe, articole şi cărţi. 118
 Erich Gamma: “Proiectarea unui software orientat pe obiecte este grea, iar
proiectarea unui software orientat pe obiecte reutilizabil este şi mai grea”.

 Designerii experimentaţi reutilizează soluţiile cu care au lucrat în trecut.

 Cunoştinţa şabloanelor cu care a lucrat în trecut, îi permite unui proiectant sa fie mai
productiv, iar designurile rezultate să fie mai flexibile şi reutilizabile

 Sabloanele de proiectare sunt soluţii demonstrate şi testate de către programatori cu


exeperienţă. Ele nu sunt o soluţie absolută la o problemă, dar ele furnizează claritate
în arhitectura sistemului şi posibilitatea de a construi un sistem mai bun.

 Şabloanele de proiectare se împart în următoarele categorii:


 Şabloane creaţionale (Creational Patterns) sunt pattern-uri ce implementează
mecanisme de creare a obiectelor. În această categorie se încadrează pattern-
urile Singleton, Factory şi AbstractFactory
 Şabloane structurale (Structural Patterns) sunt pattern-uri ce simplifică
design-ul aplicaţiei prin găsirea unei metode de a defini relaţiile dintre entităţi. În
această categorie se încadrează pattern-ul Facade.
 Şabloane comportamentale (Behavioral Patterns) sunt pattern-uri ce
definesc modul în care obiectele comunică între ele. În această categorie se
încadrează pattern-ul Command
119
Șablonul de proiectare Singleton
 Şablonul Singleton este utilizat pentru a restricţiona numărul de instanţieri ale unei
clase la un singur obiect. Pentru a asigura o singură instanţiere a clasei, constructorul
trebuie făcut private. A fost discutat în cadrul subcapitolului 2.4 Constructori - 2.4.2
Șablonul de proiectare Singleton

Șablonul de proiectare Factory


 Patternul Factory face parte din categoria Şabloanelor Creaţionale şi ca atare rezolvă
problema creării unui obiect fără a specifica exact clasa obiectului ce urmează a fi
creat. Acest lucru este implementat prin definirea unei metode al cărei scop este
crearea obiectelor.

 Metoda va avea specificat ca parametru de returnat în antet un obiect de tip părinte,


urmând ca, în funcţie de alegerea programatorului, aceasta să creeze şi să întoarcă
obiecte noi de tip copil.

 Situaţia cea mai întâlnită în care se potriveşte acest pattern este aceea când trebuie
instanţiate multe clase care implementează o anumită interfaţă sau extind o altă
clasă (eventual abstractă), ca în exemplul de mai jos. Clasa care foloseşte aceste
subclase nu trebuie să “ştie" tipul lor concret ci doar pe al părintelui. 120
package capitolul2.design_patterns.factory;

interface Forma {
public void deseneaza();
}

class Dreptunghi implements Forma{


@Override
public void deseneaza() {
[Link]("Deseneaza dreptunghi");
}
}
class Triunghi implements Forma{
@Override
public void deseneaza() {
[Link]("Deseneaza triunghi");
}
}
class Cerc implements Forma{
@Override
public void deseneaza() {
[Link]("Deseneaza cerc");
}
}

class FormaFactory {
public Forma getForma(String tipulFormei){
if(tipulFormei == null){
return null;
}
if([Link]("cerc")){
return new Cerc();
} 121
else
if([Link]("dreptunghi")){
return new Dreptunghi();
}
else
if([Link]("triunghi")){
return new Triunghi();
}
return null;
}
}

class MainApp {
public static void main(String[] args) {
FormaFactory factory = new FormaFactory();

Forma forma1 = [Link]("cerc");


[Link]();

Forma forma2 = [Link]("triunghi");


[Link]();

Forma forma3 = [Link]("dreptunghi");


[Link]();
}
}

122
Șablonul de proiectare Abstract Factory
 Abstract Factory face parte din rândul şabloanelor creaţionale

 Acest şablon funcţionează ca o fabrică de fabrici.

 Sistemul este configurat să lucreze cu mai multe familii de produse

 Oferă o interfaţă pentru crearea unei familii de obiecte corelate, fără a specifica
explicit clasele acestora

 În şablonul Abstract Factory o interfaţă este responsabilă pentru crearea unei fabrici
de obiecte relaţionate fără a le specifica clasele – Fiecare fabrică generată poate să
creeze obiecte precum factory pattern

123
package capitolul2.design_patterns.abstract_factory;

interface Forma {
public void deseneaza();
}

class Dreptunghi implements Forma{


@Override
public void deseneaza() {
[Link]("Deseneaza dreptunghi");
}
}

class Triunghi implements Forma{


@Override
public void deseneaza() {
[Link]("Deseneaza triunghi");
}
}

class Cerc implements Forma{


@Override
public void deseneaza() {
[Link]("Deseneaza cerc");
}
}

interface Culoare {
void umple();
}

124
class Rosu implements Culoare {
@Override
public void umple() {
[Link]("-Rosu");
}
}

class Verde implements Culoare {


@Override
public void umple() {
[Link]("-Verde");
}
}

class Albastru implements Culoare {


@Override
public void umple() {
[Link]("-Albastru");
}
}

abstract class AbstractFactory {


abstract Culoare getCuloare(String culoare);
abstract Forma getForma(String forma) ;
}

class FormaFactory extends AbstractFactory{


public Forma getForma(String tipulFormei){
if(tipulFormei == null){
return null;
}
if([Link]("cerc")){
return new Cerc();
} 125
else
if([Link]("dreptunghi")){
return new Dreptunghi();
}
else
if([Link]("triunghi")){
return new Triunghi();
}
return null;
}
Culoare getCuloare(String culoare) {
return null;
}
}
class CuloareFactory extends AbstractFactory {
@Override
public Forma getForma(String tipulFormei){
return null;
}
@Override
Culoare getCuloare(String culoare) {
if(culoare == null){
return null;
}
if([Link]("rosu")){
return new Rosu();
}
else if([Link]("verde")){
return new Verde();
} else if([Link]("albastru")){
return new Albastru();
}
return null;
} 126
}
class FactoryProducer {
public static AbstractFactory getFactory(String choice){
if([Link]("forma")){
return new FormaFactory();
} else if([Link]("culoare")){
return new CuloareFactory();
}
return null;
}
}

class MainApp {
public static void main(String[] args) {
AbstractFactory shapeFactory = [Link]("forma");
Forma f1 = [Link]("cerc");
[Link]();

Forma f2 = [Link]("dreptunghi");
[Link]();

Forma f3 = [Link]("triunghi");
[Link]();

AbstractFactory colorFactory = [Link]("culoare");


Culoare c1 = [Link]("rosu");
[Link]();

Culoare c2 = [Link]("verde");
[Link]();

Culoare c3 = [Link]("albastru");
[Link]();
}
} 127
Şablonul de proiectare Facade
 Facade ascunde complexitatea sistemului şi furnizează o interfaţă către client prin
care clientul poate accesa sistemul. Face parte din rândul patternurilor structurale şi
aduce o interfaţă simplificată sistemelor existente pentru a le ascunde complexitatea
 Acest pattern implică o singură clasă care oferă metode simplificate care sunt cerute
de client şi deleagă apelurile către metodele existente
 Se recomandă utilizarea acestui design pattern atunci când se doreşte atribuirea unei
interfeţe simple unui subsistem complex.
package capitolul2.design_patterns.facade;
interface Forma {
public void deseneaza();
}
class Dreptunghi implements Forma{
@Override
public void deseneaza() {
[Link]("Deseneaza dreptunghi");
}
}
class Triunghi implements Forma{
@Override
public void deseneaza() {
[Link]("Deseneaza triunghi");
}
}
class Cerc implements Forma{
public void deseneaza() {
[Link]("Deseneaza cerc");
}
} 128
class CreatorForme {
private Forma dreptunghi;
private Forma triunghi;
private Forma cerc;

public CreatorForme() {
dreptunghi = new Dreptunghi();
triunghi = new Triunghi();
cerc = new Cerc();
}

public void deseneazaCerc(){


[Link]();
}
public void deseneazaDreptunghi(){
[Link]();
}
public void deseneazaTriunghi(){
[Link]();
}
}

class MainApp {
public static void main(String[] args) {
CreatorForme shapeMaker = new CreatorForme();

[Link]();
[Link]();
[Link]();
}
}

129
Şablonul de proiectare Command
 Command pattern este un şablon comportamental. O cerere este încapsulată într-un obiect sub
forma de comandă şi este pasată unui obiect invocator. Obiectul invocator caută un obiect potrivit
să execute comanda şi pasează cererea acelui obiect

 Un avantaj a acestui design pattern este că separă obiectul care invocă o operaţie de obiectul
care execută operaţia.
package capitolul2.design_patterns.command;

class Comutator {
private EchipamentElectric echipament;

public EchipamentElectric getEquipment() {


return echipament;
}
public void setEquipment(EchipamentElectric echipament) {
[Link] = echipament;
}
public void on() {
[Link]("Comutatorul a fost pornit!");
[Link]();
}
public void off() {
[Link]("Comutatorul a fost oprit!");
[Link]();
}
130
}
class ComutatorCuFir extends Comutator {}

class ComutatorFaraFir extends Comutator {}

interface EchipamentElectric {
void porneste();
void opreste();
}

class Ventilator implements EchipamentElectric {


public void porneste() {
[Link]("Ventilatorul este pornit!");
}
public void opreste() {
[Link]("Ventilatorul este oprit");
}
}

class Candelabru implements EchipamentElectric {


public void porneste() {
[Link]("Lumina este pornita!");
}
public void opreste() {
[Link]("Lumina este oprita!");
}
}
131
class MainApp {
public static void main(String[] args) {
EchipamentElectric ventilator = new Ventilator();
EchipamentElectric candelabru = new Candelabru();

Comutator comutator_cu_fir = new ComutatorCuFir();


Comutator comutator_fara_fir = new ComutatorFaraFir();

comutator_cu_fir.setEquipment(ventilator);
comutator_cu_fir.on();
comutator_cu_fir.off();

comutator_fara_fir.setEquipment(candelabru);
comutator_fara_fir.on();
comutator_fara_fir.off();
}
}

132
Şabonul de proiectare Model-View-Controller (MVC)
 MVC este un şablon de proiectare care are rolul de a separa componentele aplicaţiei

 Utilizarea acestui şablon de proiectare presupune divizarea aplicaţiei în trei


componente (Modelul, View-ul si Controller-ul)

 Modelul reprezintă datele aplicaţiei şi este un obiect

 View-ul reprezintă vizualizarea datelor conţinute în model (interfaţa cu utilizatorul)

 Controllerul acţionează şi asupra view-ului şi asupra modelului. El urmăreşte


modelul şi actualizează datele din view atunci când modelul se schimbă. Controllerul
păstrează view-ul si modelul separate. Controller-ul se ocupa cu comunicarea dintre
utilizatori şi model

 Separarea conceptelor face ca testarea şi mentenanţa aplicaţiei să fie mai uşoare

133
 Fisierul [Link]

package capitolul2.design_patterns.mvc;

class Persoana {
private int id;
private String nume;
private int varsta;

public Persoana(){}
public Persoana(int id, String nume, int varsta) {
[Link] = id; [Link] = nume; [Link] = varsta;
}
public int getId() {
return id;
}
public void setId(int id) {
[Link] = id;
}
public String getNume() {
return nume;
}
public void setNume(String nume) {
[Link] = nume;
}
public int getVarsta() {
return varsta;
}
public void setVarsta(int varsta) {
[Link] = varsta;
}
} 134
 Fisierul [Link]
package capitolul2.design_patterns.mvc;

class PersoanaView {
public void printPersoana(int id, String nume, int varsta){
[Link](id+", "+nume+", "+varsta);
}
}

 Fisierul [Link]
package capitolul2.design_patterns.mvc;

class PersoanaController {
private Persoana model;
private PersoanaView view;

public PersoanaController(Persoana model, PersoanaView view){


[Link] = model;
[Link] = view;
}
public void setPersoanaId(int id){
[Link](id);
}
public int getPersoanaId(){
return [Link]();
}
public void setPersoanaNume (String nume){
[Link](nume);
}
135
public String getPersoanaName(){
return [Link]();
}
public void setPersoanaVarsta(int varsta){
[Link](varsta);
}
public int getPersoanaVarsta(){
return [Link]();
}
public void updateView(){
[Link]([Link](), [Link](),[Link]() );
}
}
 Fisierul [Link]
package capitolul2.design_patterns.mvc;
class MainApp {
public static void main(String[] args) {
Persoana model = getPersoanaFromDB();
PersoanaView view = new PersoanaView();
PersoanaController controller = new PersoanaController(model, view);
[Link]();

[Link](21);
[Link]();
}
private static Persoana getPersoanaFromDB(){
Persoana p = new Persoana();
[Link](1);
[Link]("Maria");
[Link](20);
return p;
} 136
}

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