Orientarea Pe Obiecte - Part - 1 - 2 - 3 - 4
Orientarea Pe Obiecte - Part - 1 - 2 - 3 - 4
Orientarea pe obiecte
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
3
2.1 Noțiuni generale
Declararea unei clase este similară cu declararea unui nou tip de date
4
Orice construcţie are un tip stabilit la momentul compilării
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 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
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)
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).
Interfaţă unui obiect –modul în care un obiect este cunoscut din exterior
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ă.
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]());
}
}
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:
[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
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);
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];
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
Î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);
}
}
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
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(){}
class MainApp {
public static void main(String[] args) {
//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(){}
class MainApp {
public static void main(String[] args) {
//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;
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";
}
}
26
package capitolul2.modificatori_acces;
class MainApp2 {
public static void main(String []args) {
Testul t=new Testul();
[Link]="Acces permis";
}
}
27
package capitolul2.modificatori_acces.test;
import capitolul2.modificatori_acces.Testul;
class OClasa {
void metoda() {
Testul t=new Testul();
//[Link]="Acces interzis";
}
}
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
Î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
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;
}
@Override
protected void finalize() throws Throwable {
[Link]("Se elibereaza memoria ocupata de obiectul "
+ "care are x cu valoarea "+this.x);
}
}
30
package [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
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 î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
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);
a=b;
[Link]([Link]());
b=(VasDeCroaziera)a;
[Link]([Link]());
}
}
Î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
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).
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).
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
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
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
Enumerările pot conține un constructor privat care se va executa separat pentru fiecare constantă
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");
Anotimp [] anotimpuri=[Link]();
for (Anotimp b:anotimpuri) {
[Link](b);
}
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 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
Câmpurile ale căror valoare nu se dorește să fie serializată trebuie să fie precedate
de cuvântul cheie transient
Fișierul [Link]:
package [Link];
import [Link];
45
Fișierul [Link]:
package [Link];
import [Link];
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;
}
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
49
2.10 Generarea documentației
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]()));
}
}
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)
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
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.
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);
59
2.13 Metode generice
Metodele generice sunt metode care pot fi apelate cu argumente de diferite tipuri
package capitolul2.generice2;
afiseazaVector(v1);
afiseazaVector(c);
}
} 60
2.14 Clase imbricate
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
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;
package clase_imbricate;
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;
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
interface Ordonare {
public void metoda();
}
66
Fișierul [Link]:
package capitolul2.clase_imbricate3;
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 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;
[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);
}
}
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 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 metodă abstractă se află în mod obligatoriu într-o clasă abstractă sau într-o
interfață
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
O interfață este un tip abstract utilizat pentru a specifica comportamentul unei clase
(care implementează interfața)
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
O interfață poate fi extensia unei alte interfeţe, adică poate să adauge noi modele de
metode la o interfaţă care există
package capitolul2.interfete1;
interface Figura{
void deseneaza();
}
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)
Î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;
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
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());
}
}
//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
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
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);
}
[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;
}
});
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()
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.
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);
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;
});
92
În Java 7 ordonarea unei colecții se face cu ajutorul unei clase care implementează interfața de
comparare
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
String::new
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));
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.
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.
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]());
Î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
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");
}
Optional este un container care poate să conțină o valoare sau nu. Dacă valoarea este prezentă
funcția get() o va returna
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());
}
[Link](value ->[Link]([Link]()) );
Parametrul de intrare al funcției ifPresent() a fost transmis printr-o expresie Lambda care afișează
cu majuscule valoarea din containerul Optional
În exemplul de mai sus, dacă persoana căutată nu se găseşte, rândul următor produce
NullPointerException
[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;
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 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](.isPresent());
[Link]([Link](s).isEmpty());
s = "test";
[Link](.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];
[Link]("nr="+nr1);
[Link]("Sirul introdus:"+s1+" are "+[Link]()+" caractere");
for([Link]<String,List<String>> o:orase_in_judete1.entrySet()) {
[Link]([Link]()+": "+[Link]());
}
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
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.
114
În fişierul [Link] a fost creată înregistrarea Persoana cu următorul conținut
package [Link];
package [Link];
O clasă normală nu poate extinde o clasă de tip record pentru ca aceasta este finală
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
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));
}
}
117
2.23 Design patterns
Dacă o problema apare de mai multe ori, soluţia ei este descrisă ca un şablon
(pattern).
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.
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
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 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();
122
Șablonul de proiectare Abstract Factory
Abstract Factory face parte din rândul şabloanelor creaţionale
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();
}
interface Culoare {
void umple();
}
124
class Rosu implements Culoare {
@Override
public void umple() {
[Link]("-Rosu");
}
}
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]();
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();
}
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;
interface EchipamentElectric {
void porneste();
void opreste();
}
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
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;
[Link](21);
[Link]();
}
private static Persoana getPersoanaFromDB(){
Persoana p = new Persoana();
[Link](1);
[Link]("Maria");
[Link](20);
return p;
} 136
}