Download as pdf or txt
Download as pdf or txt
You are on page 1of 102

PROGRAMARE OBIECT

ORIENTATĂ 2

Iolu Mihai - Ştefan

19 noiembrie 2008
ii
Cuprins

1 Introducere 1
1.1 Mediul de programare Java . . . . . . . . . . . . . . . . . . . 3

2 Structuri fundamentale de programare ı̂n Java 5


2.1 Primul program Java . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Transmiterea parametrilor din linie de comandă . . . . . . . . 8
2.3 Comentarii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.4 Tipuri de date . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.1 Tipuri de date primitive . . . . . . . . . . . . . . . . . 12
2.4.2 Tipuri de date referinţă . . . . . . . . . . . . . . . . . 13
2.5 Operatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Instrucţiuni decizionale . . . . . . . . . . . . . . . . . . . . . . 14
2.7 Instrucţiuni repetitive . . . . . . . . . . . . . . . . . . . . . . 15
2.8 Vectori şi matrici . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.8.1 Vectori . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.8.2 Matrici . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.9 Şiruri de caractere . . . . . . . . . . . . . . . . . . . . . . . . 23
2.9.1 Clasa String . . . . . . . . . . . . . . . . . . . . . . . . 23
2.9.2 Clasa StringBuffer . . . . . . . . . . . . . . . . . . . . 27
2.9.3 Clasa StringTokenizer . . . . . . . . . . . . . . . . . . 28
2.9.4 Expresii regulate ı̂n Java . . . . . . . . . . . . . . . . . 29

3 Concepte şi principii ı̂n POO 31


3.1 Concepte ı̂n programarea obiect orientată . . . . . . . . . . . . 31
3.2 Principii ale programării obiect orientate . . . . . . . . . . . . 32

iii
iv CUPRINS

3.3 Soluţii pentru utilizarea eficienta a principiilor POO . . . . . . 34

4 Obiecte şi clase 37


4.1 Definirea unei clase . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2 Declararea atributelor unei clase . . . . . . . . . . . . . . . . . 38
4.2.1 Modificatorii de vizibilitate . . . . . . . . . . . . . . . . 39
4.2.2 Modificatorul final . . . . . . . . . . . . . . . . . . . . 40
4.2.3 Modificatorul static . . . . . . . . . . . . . . . . . . . . 41
4.2.4 Modificatorii transient şi volatile . . . . . . . . . . . . 42
4.3 Declararea metodelor unei clase . . . . . . . . . . . . . . . . . 42
4.3.1 Modificatorul static . . . . . . . . . . . . . . . . . . . . 43
4.3.2 Transmiterea parametrilor la metode . . . . . . . . . . 43
4.4 Declararea constructorilor unei clase . . . . . . . . . . . . . . 44
4.5 Iniţializatorul static al unei clase . . . . . . . . . . . . . . . . 46
4.6 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . 47
4.7 Probleme propuse . . . . . . . . . . . . . . . . . . . . . . . . . 65

5 Moştenirea ı̂n Java 67


5.1 Noţiuni generale . . . . . . . . . . . . . . . . . . . . . . . . . . 67
5.2 Exemplu de aplicaţie folosind moştenirea . . . . . . . . . . . . 68
5.3 Polimorfismul . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.4 Metode şi clase finale . . . . . . . . . . . . . . . . . . . . . . . 75
5.5 Operaţia de casting . . . . . . . . . . . . . . . . . . . . . . . . 75
5.6 Clase abstracte . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.7 Clasa Object . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.7.1 Metoda toString() . . . . . . . . . . . . . . . . . . . . 83
5.7.2 Metoda equals() . . . . . . . . . . . . . . . . . . . . . . 83
5.7.3 Metoda hashCode() . . . . . . . . . . . . . . . . . . . . 84
5.7.4 Metoda clone() . . . . . . . . . . . . . . . . . . . . . . 84
5.8 Implementarea colecţiilor generice folosind Object . . . . . . . 85
5.9 Utilizarea colecţiilor generice cu template-uri . . . . . . . . . . 89
5.9.1 Boxing şi unboxing ı̂n Java . . . . . . . . . . . . . . . . 90

6 Tratarea excepţiilor ı̂n Java 93


6.1 Clasificarea excepţiilor . . . . . . . . . . . . . . . . . . . . . . 94
6.2 Crearea claselor excepţie . . . . . . . . . . . . . . . . . . . . . 95
6.3 Aruncarea excepţiilor . . . . . . . . . . . . . . . . . . . . . . . 96
6.4 Prinderea excepţiilor . . . . . . . . . . . . . . . . . . . . . . . 96
6.5 Indicaţii referitoare la utilizarea excepţiilor . . . . . . . . . . . 98
6.6 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
CAPITOLUL 1

Introducere

Acest curs se referă la programarea obiect orientată şi principalele car-


acteristici ale acesteia. Acestea vor fi prezentate ı̂n contextul unui limbaj
care oferă un foarte bun suport din acest punct de vedere, respectiv limbajul
Java.
Java este, ı̂n mod indiscutabil, unul din cele mai populare limbaje de
programare obiect orientate (alături de limbaje precum C++, C# sau Visual
Basic). Acest lucru a făcut ca el să fie folosit de milioane de dezvoltatori din
lumea ı̂ntreagă mai ales ı̂n contextul aplicaţiilor web.
El a apărut la sfârşitul anului 1995 şi a avut parte de o mare publicitate
ı̂ncă de la ı̂nceput. Este un limbaj care oferă suport pentru toate tehnicile
moderne de programare precum: programarea ı̂n reţea, lucrul cu fire de
execuţie, internaţionalizare, lucrul cu baze de date şi multe altele.
De-a lungul timpului cei de la Sun Microsystems, care se ocupă de evoluţia
limbajului, au scos 7 revizii majore ale acestuia. Cea mai importantă a fost
cea care a dus la apariţia versiunii JDK 5.0. Momentan s-a ajuns la versiunea
JDK 6.0.
Evoluţia este impresionantă dacă ţinem cont că s-a pornit de la un număr
de aproximativ 200 de clase şi s-a ajuns acum la peste 3000 de clase.
Java este mult mai mult decât un simplu limbaj. Java este o platformă
care are o librărie imensă ce conţine foarte mult cod reutilizabil precum şi
un mediu de execuţie care oferă servicii precum: securitate, portabilitate pe
diverse medii de execuţie precum şi garbage collection. Unele din principalele
avantaje ale acestui mediu Java sunt următoarele:

1
2 CAPITOLUL 1. INTRODUCERE

• Limbajul Java este un limbaj relativ simplu. După cum vom vedea ı̂n
cele ce urmează ı̂n Java nu avem numeroase construcţii care existau ı̂n
C/C++ precum: pointeri, structuri, fişiere header, directive de prepro-
cesor, supraı̂ncărcare de operatori, moştenire virtuală etc.

• Limbajul Java este obiect orientat. În ziua de azi, majoritatea limba-
jelor care există pe piaţă şi au o foarte largă răspândire au la bază
principiile programării obiect orientate (vezi C++, C#, Visual Basic).

• Java are un bogat suport pentru aplicaţiile distribuite. Există numeroase


clase care permit crearea unor aplicaţii care să ruleze ı̂n reţea, folosind
diverse modalităţi de lucru: sockets, RMI, CORBA etc.

• Java este un limbaj robust. Cei care au dezvoltat acest limbaj au avut
ca scop foarte important ı̂ncercarea de a evita pe cât posibil apariţia
erorilor, prin depistarea acestora cât mai rapidă sau prin uşurarea mod-
ului de a programa. Un astfel de exemplu este lipsa pointerilor care face
ca programarea să fie mai uşoară şi practic face imposibilă coruperea
memoriei care se putea produce destul de uşor ı̂n C/C++.

• Java este portabil. Aceasta ı̂nseamnă că dacă scriem un cod pe plat-
formă Windows, exact acelaşi cod fără modificări va putea rula şi pe o
platformă Linux de exemplu. Nu acelaşi lucru se ı̂ntâmpla, de exemplu,
ı̂n cazul C++. Tipul int putea avea dimensiuni diferite ı̂n funcţie de
platforma ţintă.

• Java este neutru din punct de vedere arhitectural. În momentul ı̂n care
se compilează codul Java, rezultatul produs este un bytecode. Acesta
este rulat cu ajutorul unei maşini virtuale Java (JVM - Java Virtual
Machine). Din cauza acestui mod de gândire este posibil ca acelaşi
cod să ruleze pe orice sistem de operare, doar implementarea maşinii
virtuale diferind. Un mecanism asemănător a fost dezvoltat şi de către
cei de la Microsoft pentru platforma .NET, care are la bază tot o maşină
virtuală.
Voi ı̂ncerca ı̂n cele ce urmează să prezint un curs ı̂n care accentul se va
pune mai ales pe partea practică a limbajului şi pe facilităţile oferite de
acesta pentru programarea obiect orientată. Din cauza timpului limitat, nu
vom prezenta facilităţile pentru lucrul cu interfeţe grafice, pentru programare
web sau pentru realizarea aplicaţiilor pe mobil. Acestea sunt subiecte de mare
interes care pot fi studiate ı̂ntr-un curs viitor.
Totuşi trebuie precizat ı̂ncă de la ı̂nceput că se foloseşte mediul Java
pentru a realiza mai multe tipuri de aplicaţii:
1.1. MEDIUL DE PROGRAMARE JAVA 3

• Aplicaţii consolă (ı̂n linie de comandă): se pot rula din linie de co-
mandă, neavând o interfaţă grafică
• Aplicaţii grafice (folosesc librarii AWT sau Swing)
• Applet-uri - sunt un anumit tip de aplicaţii grafice care pot fi introduse
ı̂n pagini web, facând ca o pagină web să aibă un aspect mult mai
dinamic asemănător aplicaţiilor desktop
• Pagini web dinamice - sunt create cu ajutorul unor tehnologii care s-au
dezvoltat peste Java şi care adaugă funcţionalităţi noi limbajului (vezi
JSP, Servlets, Spring etc.)
• Aplicaţii pe mobil (se realizează cu ajutorul unei distribuţii Java numită
J2ME - Java 2 Microedition).
Accentul ı̂n continuare se va pune mai ales pe primul tip de aplicaţie,
doar tangenţial se vor atinge celelalte tipuri.
Trebuie spus că majoritatea ideilor prezentate vis-a-vis de programarea
obiect orientată sunt aplicabile şi ı̂n cazul altor limbaje obiect orientate pre-
cum C++ (deja studiat) şi C# (care va fi studiat ı̂n semestrul următor).
Voi ı̂ncerca să prezint ı̂n continuare următoarele lucruri:
• Structurile de programare fundamentale ı̂n Java
• Lucrul cu obiecte şi clase, moştenirea şi lucrul cu interfeţe
• Tratarea excepţiilor
• Stream-uri Java
• Programarea generică şi colecţiile
• Lucrul cu baze de date
• Annotations
• Java Reflection

1.1 Mediul de programare Java


În această secţiune vom prezenta acele softuri necesare pentru a rula
aplicaţiile Java pe care le vom prezenta ı̂n continuare precum şi diversele
moduri ı̂n care se poate lucra folosind mai mult sau mai puţin editoare ded-
icate pentru lucrul cu Java.
4 CAPITOLUL 1. INTRODUCERE

Pentru a folosi Java, ı̂n funcţie de tipul aplicaţiilor care se doreşte a fi


create, se poate descărca una din următoarele variante:

• J2ME - Java 2 Micro Edition (se foloseşte pentru crearea aplicaţiilor


pentru mobile)

• J2SE - Java 2 Standard Edition (se foloseşte pentru crearea aplicaţiilor


consolă sau grafice obişnuite)

• J2EE - Java 2 Enterprise Edition (se foloseşte pentru crearea aplicaţiilor


web).

Pentru a putea compila şi rula aplicaţii Java este bine să ı̂ncepem prin a
instala JDK -ul (Java Development Kit).
Acesta poate fi descărcat gratuit la următoarea adresă web:

http://java.sun.com/javase/downloads/index.jsp.

În acest moment se poate alege ı̂ntre o versiune beta care este mai nouă
sau o versiune stabilă.
Este recomandabil, de asemenea, să se downloadeze şi documentaţia pen-
tru JDK care se găseşte ı̂n secţiunea Java SE 6 Documentation.
După ce se instalează JDK (procesul este foarte uşor, motiv pentru care
nici nu va fi descris) se poate dezarhiva directorul docs şi, eventual, se poate
copia ı̂n directorul ı̂n care a fost salvat JDK.
CAPITOLUL 2

Structuri fundamentale de programare ı̂n Java

2.1 Primul program Java


În această secţiune vom crea primul nostru program Java, pe care-l vom
rula din linie de comandă. Acest program arată astfel:

public class Welcome


{
public static void main(String[] args)
{
System.out.println("Hello world!");
}
}

Pentru a rula acest program din linie de comandă se parcurg mai mulţi
paşi:

1. Se introduce programul ı̂ntr-un editor şi apoi se salvează cu extensia


.java. Uneori numele trebuie să respecte anumite condiţii dar asupra
acestui aspect vom reveni. În cazul nostru vom salva programul cu
numele Welcome.java.

2. Intrăm ı̂n linie de comandă prin Start+Run...+cmd (sau command, ı̂n


cazul calculatoarelor care rulează Win 98 sau Win ME ), după care ne
poziţionăm ı̂n directorul ı̂n care a fost salvat fişierul anterior.

5
6CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

3. Tastăm SET PATH=%PATH%;c:\program files\Java\jdk1.6.0 07\bin


pentru a adăuga directorul bin din distribuţia Java ı̂n cale. Această
cale poate fi diferită ı̂n funcţie de locul ı̂n care a fost instalat Java sau
de versiunea de JDK instalată.

4. Se compilează aplicaţia cu javac Welcome.java. Dacă apar erori, aces-


tea trebuie corectate, după care fişierul trebuie salvat şi se recompilează
aplicaţia până când nu avem erori.

5. În final se rulează aplicaţia cu java Welcome.

Observaţii:

• Limbajul Java este case-sensitive, ceea ce ı̂nseamnă că se face diferenţa


ı̂ntre litere mici şi mari. Astfel, dacă cuvintele class sau System nu
sunt scrise exact ca ı̂n exemplu s-ar genera o eroare de compilare.

• Numele fişierului trebuie să fie chiar Welcome.java deoarece ı̂n program
avem o clasă publică numită Welcome.

• În directorul ı̂n care am instalat Java există mai multe directoare pre-
cum: bin, include, lib etc. O semnificaţie deosebit o are directorul bin
ı̂n care sunt poziţionate mai multe fişiere executabile precum java.exe
şi javac.exe. Este necesar pasul 3 deoarece aceste fişiere executabile nu
sunt implicit ı̂n calea sistemului de operare. De altfel, la paşii 4 şi 5,
compilarea respectiv rularea programelor se face folosind aceste fişiere
executabile.

• În faza de compilare utilitarul javac primeşte ca intrare fişierul Wel-


come.java şi, ı̂n cazul ı̂n care compilarea reuşeşte, generează fişierul
Welcome.class. În faza de rulare, utilitarul java.exe foloseşte acest
fişier generat ı̂n faza de compilare.

În Java tot codul care va fi scris trebuie să fie introdus ı̂n cadrul unor
clase, motiv pentru care şi aici am scris codul ı̂n cadrul unei clase. Această
clasă are plasată ı̂naintea definiţiei sale cuvntul cheie public ceea ce ı̂nseamnă
că ea poate fi accesată de oriunde. Vom vorbi mai multe despre indicatorii
de acces atunci când vom prezenta modul ı̂n care se lucrează cu clase.
În interiorul acestei clase avem definită o metodă main() care este publică
şi statică. Această metodă este mai specială, ea fiind ı̂ntotdeauna publică şi
statică. Ce o face mai specială este faptul că ea constituie punctul de intrare
ı̂n program. Aceasta ı̂nseamnă că atunci când rulăm o aplicaţie, execuţia
acesteia ı̂ncepe cu această metodă.
2.1. PRIMUL PROGRAM JAVA 7

Metoda main() primeşte un şir de şiruri de caractere, care reprezintă


parametrii din linie de comandă. Această construcţie este asemănătoare cu
ceea ce ştiam din limbajul C++.
După cum se poate uşor observa ı̂n Java, instrucţiunile sunt separate prin
”;”, la fel ca şi ı̂n limbajul C/C++.
În acest exemplu avem un obiect, System.out, asupra căruia se apelează o
metodă println(), care primeşte ca parametru un şir de caractere şi realizează
afişarea acestuia pe ecran precum şi saltul la linie nouă. Dacă nu doream
salt la linie nouă foloseam metoda print().
În Java delimitarea blocurilor de cod se face folosind caracterele ”{” şi
”}”.
Cuvântul cheie void are aceeaşi semnificaţie ca şi ı̂n C/C++ şi anume
faptul că metoda main nu returnează nimic.
Foarte important atunci când lucrăm cu limbajul Java este să respectăm
regulile de denumire a claselor, obiectelor, metodelor şi constantelor.
Astfel, numele unei clase ı̂ncepe ı̂ntotdeauna cu literă mare iar ı̂n cazul
ı̂n care cuvântul care se referă la numele clasei este alcătuit din mai multe
cuvinte, fiecare cuvânt este scris cu literă mare. Numele unei clase trebuie
să fie un substantiv la singular. Ex: Stack, StackOverflowException etc.
Numele metodelor ı̂ncep cu literă mică iar ı̂n cazul ı̂n care sunt alcătuite
din mai multe cuvinte, numele primului cuvânt se scrie cu literă mică iar
numele celorlalte cuvinte cu literă mare. Ex: println(), printWords(), as-
sertEquals() etc.
Numele obiectelor respectă exact aceleaşi reguli ca numele metodelor.
Numele constantelor se scriu ı̂ntotdeauna numai cu litere mari. Ex:
MAX VALUE, MIN etc.
Observaţii:

• Compilatorul nu generează eroare dacă aceste reguli nu sunt respectate


ı̂ntocmai, dar este recomandabilă respectarea lor.

• Numele de clase, metode, variabile nu pot conţine caracterul spaţiu.

• În momentul ı̂n care se scrie cod Java, acesta trebuie aliniat core-
spunzător. Compilatorul nu va ”plânge” dacă nu se respectă această
regulă, dar aceasta indică un programator care de multe ori nu ı̂nţelege
ceea ce programează.
8CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

2.2 Transmiterea parametrilor din linie de co-


mandă
În această secţiune vom prezenta un program foarte asemănător cu cel
prezentat ı̂nainte, program care va folosi parametri transmişi din linie de
comandă.

public class Welcome2


{
public static void main(String[] args)
{
for(int i=0;i<args.length;i++)
System.out.println("Hello "+args[i]+"!");
}
}

Acesta este un program foarte simplu care are ca efect afişarea unui mesaj
de ı̂ntâmpinare pentru fiecare nume transmis din linie de comandă.
Astfel, vom rula programul cu urmatoarea comandă:

java Welcome2 radu aurel "ana maria"

efectul lui fiind afişarea următoarelor mesaje:

Hello radu!
Hello aurel!
Hello ana maria!

În acest fel am exemplificat utilitatea parametrului args al metodei main.


Acesta este un şir de şiruri de caractere. Asemenea oricărui vector ı̂n Java
el are un atribut length care reprezintă lungimea vectorului.
Anticipăm un pic prezentarea vectorilor prin a spune că ı̂n Java vectorii
sunt indexaţi ı̂ncepând de la poziţia 0.
Mai prezentăm ı̂n continuare un alt exemplu de aplicaţie care rezolva
ecuaţia de gradul al doilea care are coeficienţii transmişi din linie de comandă:

public class EcGr2


{
public static void main(String[] args) {
if(args.length<3)
{
System.out.println("Nu sunt suficienti parametri");
2.3. COMENTARII 9

System.exit(0);
}
double a=Double.parseDouble(args[0]);
double b=Double.parseDouble(args[1]);
double c=Double.parseDouble(args[2]);

double delta=b*b-4*a*c;

if(delta<0)
System.out.println("Ecuatia nu are solutii reale");
else
if(delta==0)
System.out.println("Solutie unica: "+ (-b)/(2*a));
else
{
double x1=(-b-Math.sqrt(delta))/(2*a);
double x2=(-b+Math.sqrt(delta))/(2*a);
System.out.println("x1="+x1);
System.out.println("x2="+x2);
}
}
}
Observaţii:
• Metoda statică parseDouble() a clasei Double are rolul de a transforma
un şir de caractere ı̂ntr-un număr de tip double. În mod asemănător
există Integer.parseInt() şi Float.parseFloat().

• Funcţiile matematice gata definite ı̂n limbajul Java sunt implemen-


tate ı̂n clasa Math, ele fiind funcţii statice. Un exemplu este funcţia
Math.sqrt() folosită pentru a calcula radicalul unui număr.

2.3 Comentarii
În Java se pot realiza trei tipuri de comentarii care sunt toate prezentate
ı̂n exemplul următor. Această aplicaţie calculează suma cifrelor unui număr
ı̂ntreg:
/**
*
* @author mihai
10CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

* @version 1.0
*/
public class ExempluComentariu
{
/**
* @param n
* numarul pentru care se calculeaza suma cifrelor.
* Trebuie sa fie un numar mai mare decat 0.
* @return suma cifrelor numarului
*/
public static int sumaCif(int n)
{
/*
* Metoda sumaCif este o metoda recursiva
* care calculeaza suma cifrelor unui numar
*/
if(n<9)
return n;
return n%10+sumaCif(n/10); //aici are loc apelul recursiv
}

/**Calculeaza suma cifrelor unui numar folosind o metoda recursiva


* @param args reprezinta argumentele din linie de comanda pentru
* programul meu
*/
public static void main(String[] args)
{
int n=234;
System.out.println("Suma cifrelor este: "+sumaCif(n));
}
}

Cele trei tipuri de comentarii care se pot realiza sunt următoarele:

• Comentarii pe o singura linie, care se realizează la fel ca ı̂n C/C++


folosind caracterele ”//”

• Comentarii pe mai multe linii, care sunt cuprinse ı̂ntre ”/*”’ şi ”*/”’

• Comentarii care ajuta la generarea documentatiei, care sunt cuprinse


ı̂ntre ”/**” şi ”*/”
2.3. COMENTARII 11

Primele două moduri de lucru sunt identice cu ceea ce ı̂ntâlnim ı̂n C/C++,
motiv pentru care ne vom axa ı̂n principal pe a treia modalitate de generare
a documentaţiei.
Comentariile care ajută la generarea documentaţiei sunt folosite pentru
a genera nişte fişiere HTML care descriu clasa pe care am scris-o. Aceste
fişiere vor arăta exact la fel ca documentaţia furnizată ı̂mpreună cu mediul
JDK.
Generarea acestor fişiere se poate face utilizând utilitarul javadoc astfel:

javadoc ExempluComentariu.java

Vom remarca apariţia ı̂n directorul ı̂n care avem fişierul a mai multor fişiere
HTML. Dacă deschidem fişierul ExempluComentariu.html vom putea vedea
documentaţia.
Un rol foarte important ı̂n cadrul documentaţiei ı̂l au tag-urile predefinite,
cu ajutorul cărora se pot specifica anumite informaţii mai speciale:

• @name - reprezintă numele celui care a scris codul pentru această clasă

• @version - reprezintă versiunea clasei

• @return - specifică ce returnează metoda respectivă

• @param - descrie un anumit parametru

• Există şi alte tag-uri care sunt foarte utile. Cel mai bine se poate studia
modul ı̂n care sunt ele folosite dacă examinăm codul sursă Java prezent
ı̂n fişierul src.zip aflat ı̂n directorul ı̂n care este instalat Java.

Aceste comentarii vor conduce la obţinerea unor fişiere HTML, motiv


pentru care este posibil ca ı̂n interiorul lor să folosim cod HTML.
Scrierea documentaţiei pentru clasele pe care le creem este necesară deoarece,
de multe ori, se ı̂ntâmplă ca cel care foloseşte clasa să nu fie aceeaşi persoană
cu cea care a scris codul pentru clasă. Nu este normal ca pentru a folosi o
clasă să trebuiască neapărat să umblăm ı̂n codul ei.
Acestă idee poartă numele de ı̂ncapsulare. Practic, putem să ne gândim
la o clasă ca la un fel de cutie neagră (black-box) care are o interfaţă pe care
o putem folosi fără a cunoaşte conţinutul ei.
Generarea documentaţiei ı̂n Eclipse se poate face prin alegerea opţiunii
Generate JavaDoc din meniul Project. La un moment dat trebuie introdusă
calea către utilitarul javadoc, după care se urmează paşii sugeraţi de wizard.
12CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

2.4 Tipuri de date


Un tip de date reprezintă o plajă de valori pe care o variabilă le poate
lua precum şi operaţiile care se pot efectua asupra acesteia.
Java este un limbaj strong typed ceea ce ı̂nseamnă că orice variabilă are
un tip foarte bine definit. Acest lucru face posibilă determinarea mai uşoară
a erorilor din program la momentul compilării.
În Java avem trei tipuri de date: tipuri de date primitive (byte, short, int,
long, char, float, double, boolean), tipuri de date referinţă (class, interface,
array) şi tipul de date null (corespunzător literalului null).

2.4.1 Tipuri de date primitive


Tipurile de date primitive sunt de mai multe feluri:

• tipuri numerice: byte, short, int, long, float, double

• tipul caracter

• tipul boolean

Tipurile de date numerice sunt prezentate ı̂n următorul tabel:

Tabela 2.1: Tipuri de date numerice

Tip Dimensiune Valori admise


byte 1 byte -128...127
short 2 bytes -32768 ... 32767
int 4 bytes -2,147,483,648 ... 2,147,483, 647
long 8 bytes -9,223,372,036,854,775,808 ...
9,223,372,036,854,775,807
float 4 bytes aproximativ 3.40282347E+38F (6-7 cifre zeci-
male semnificative)
double 8 bytes aproximativ 1.79769313486231570E+308 (15
cifre zecimale semnificative)

Numerele scrise n baza 16 au prefixul 0x iar numerele scrise ı̂n baza 8 au


prefixul 0.
Observaţii:
• Ceea ce este foarte important ı̂n Java şi un mare avantaj faţă de C/C++
este faptul că indiferent de platformă, tipurile de date vor avea exact
aceeaşi dimensiune de reprezentare.
2.5. OPERATORI 13

• În Java se pot reprezenta valori speciale gen Double.NaN, Double.


POSITIVE INFINITY sau Double.NEGATIVE INFINITY (precum
şi corespondentul lor relativ la tipul float). Ele au următoarele semnificaţii:

– NaN se referă la faptul că putem avea o operaţie care să nu


genereze un număr. Ex: radical dintr-un număr negativ sau
ı̂mpărţirea 0/0.
– POSITIVE INFINITY se poate obţine dacă ı̂mpărţim un număr
pozitiv la 0. Aceste constante se folosesc foarte rar ı̂n practică.
Pentru a vedea dacă un număr este de tipul NaN se procedează
astfel:
if (Double.isNaN(x))
....

Un tip de date mai deosebit este tipul char. În Java variabilele de tip
char se reprezintă ı̂ntre caractere ”. La fel ca şi ı̂n C/C++ există caractere
mai speciale care sunt tratate folosind secvenţe escape: \b, \t, \n, \r, \’, \”,
\\.
În Java tipul boolean are două valori true şi false. Nu se mai respectă
convenţia din C/C++ care spunea că valoarea 0 ı̂nseamnă false şi valoare
diferită de 0 reprezintă true.

2.4.2 Tipuri de date referinţă


În plus faţă de tipurile de date primitive, ı̂n Java avem şi tipurile de
date referinţă care se referă la obiecte (instanţe ale unor clase) precum şi
la vectori. Nu vom prezenta acest subiect aici, urmând a reveni ı̂ntr-un
subcapitol ulterior.
Vom preciza doar un singur lucru, şi anume faptul că diferenţa ı̂ntre cele
2 tipuri de date constă ı̂n faptul că pentru tipurile de date primitive se reţine
exact valoarea lor ı̂n timp ce pentru tipurile de date referinţă se reţine ı̂n
variabila corespunzătoare lor adresa unei zone de memorie unde se reţine de
fapt valoarea lor.
Acest lucru va avea un impact foarte important la transmiterea parametrilor.

2.5 Operatori
Un operator utilizează unul sau mai mulţi operanzi pentru a produce o
nouă valoare. Un operator poate, de asemenea, să schimbe valoarea unui
operand.
14CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

Operatorii din Java se pot clasifica astfel:

• Operatorul de atribuire =

• Operatorii aritmetici:

– Operatorii binari: adunare(+), scădere(-), ı̂nmulţire(*), ı̂mpărţire(/),


restul ı̂mpărţirii(%)
– Operatorii unari: semnul minus(-), semnul plus(+), incrementare(++),
decrementare(–)

• Operatorii relaţionali: egal(==), diferit(!=), mai mare(>), mai mic(<),


mai mare sau egal(>=), mai mic sau egal(<=)

• Operatori logici:

– Condiţionali: şi(&&), sau(||), negaţie(!), xor(ˆ)


– Pe biţi: şi(&), sau(|), xor(ˆ), complementare(∼), deplasare la
stânga(<<), deplasare la dreapta (>>)

• Operatorii de atribuire combinaţi (atât aritmetici cât şi pe biţi: +=,


-=, /=, %=, &=, |=, ˆ==, <<==, >>==

• Operatorul condiţional: expr logică ? val1 : val2

• Operatorul adunare de şiruri de caractere

• Operatorul de cast

• Operatorul instanceof

2.6 Instrucţiuni decizionale


În Java avem 2 instrucţiuni condiţionale: if-else şi switch.
Instrucţiunea if-else are următoarea structură:

if (conditie)
secventa1
[else
secventa2]

Instrucţiunea switch permite executarea uneia din mai multe secvenţe de


instrucţiuni ı̂n funcţie de valoarea unei expresii. Forma generală a acesteia
este:
2.7. INSTRUCŢIUNI REPETITIVE 15

switch(expresie)
{
case val1: secventa1
[break;]
case val2: secventa2
[break;]
....
case valN: secventaN
[break;]
[default:
secventa]
}

Instrucţiunea se evaluează astfel: se calculează valoarea expresiei după


care se găseşte case-ul corespunzător expresiei. Începând de la acesta se
evaluează toate secvenele până la ı̂ntâlnirea lui break. În cazul ı̂n care nici e
expresie nu se potriveşte se execută instrucţiunea din clauza default.
Vom prezenta exemple atunci când vom prezenta lucrul cu şiruri de car-
actere.

2.7 Instrucţiuni repetitive


În Java avem trei tipuri de structuri repetitive: while, do-while şi for.
Instrucţiunea while are următoarea formă:

while (conditie)
instructiune

Ideea acestei instrucţiuni este că, att̂a timp cât este adevaărată condiţia,
se execută instrucţiunea cuprinsă ı̂n ciclul while.
Instrucţiunea do-while are următoarea formă:

do
instructiune
while (conditie);

Ideea acestei instrucţiuni este că se tot execută instrucţiunea (care poate
fi simplă sau compusă) atâta timp cât este adevărată condiţia.
Cea de-a treia instrucţiune repetitivă, care de asemenea exista şi ı̂n lim-
bajul C/C++ este instrucţiunea for :
16CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

for (expresie initializare; conditie continuare; secventa incrementare)


instructiune
Ideea acestei instrucţiuni este că ı̂ntâi se executa iniţializările prevăzute
ı̂n expresie iniţializare, după care se evaluează condiţia de continuare. Atâta
timp cât condiţia este adevărată se execută instrucţiunea simpă sau com-
pusă, după care se execută secvenţa de incrementare şi se revine la evaluarea
condiţiei de continuare.
Singura diferenţă faţă de limbajul C/C++ este că, dacă declarăm şi
iniţializăm o variabilă ı̂n expresia de iniţializare, atunci această variabilă
nu este vizibilă decât ı̂n corpul instrucţiunii for.
Un rol important ı̂n cadrul instrucţiunilor repetitive ı̂l are instrucţiunea
break care odată folosită are ca efect ieşirea din ciclul repetitiv cel mai interior
şi trecerea la următoarea instrucţiune de executat după acesta.

2.8 Vectori şi matrici


2.8.1 Vectori
Un tablou (vector) este o listă de elemente de acelaşi tip plasate ı̂ntr-o
zonă continuă de memorie, care pot fi accesate printr-un nume comun. Toate
elementele dintr-un tablou trebuie să aibă acelaşi tip. Tipurile admise sunt
toate tipurile primitive sau orice tip referinţă.
În Java tablourile sunt obiecte. Un tablou are toate metodele pe care
le are clasa Object şi ı̂n plus are un membru length care ne spune care este
dimensiunea tabloului.

Declararea şi alocarea de memorie pentru un tablou


Un tablou se poate declara ı̂ntr-unul din următoarele două moduri echiva-
lente:
tip[] nume;
tip nume[];
Doar declarând un tablou, nu putem face nimic cu el. Pentru a-l putea
folosi trebuie să şi alocăm memorie pentru el. Aceasta se poate face ı̂n două
moduri:
• Prin folosirea operatorului new :

nume=new tip[dimensiune];
2.8. VECTORI ŞI MATRICI 17

• Prin iniţializarea directă a elementelor tabloului:

tip[] nume={element1, element2, element3};

După cum se poate observa, ı̂n acest al doilea caz declararea şi alocarea
de memorie se fac ı̂n aceeaşi instrucţiune.

Observaţii:

• Se poate realiza atât declararea tabloului cât şi alocarea de memorie


ı̂ntr-o singură instrucţiune:

tip[] nume=new tip[dimensiune];

• Atunci când se alocă memorie pentru un tablou, elementele acestuia vor


fi iniţializate cu valoarea implicită corespunzătoare tipului elementelor
tabloului (0 - valori numerice intregi, 0.0 pentru valori reale, false -
pentru tipul boolean, null - pentru obiecte).

• Tablourile ı̂n limbajul Java sunt 0 indexate ceea ce ı̂nseamnă că se


ı̂ncepe de la elementul cu indicele 0 până la elementul cu indicele n − 1
dacă tabloul are n elemente. În cazul ı̂n care vom accesa un element al
tabloului care nu există vom observa faptul că se va produce o eroare
şi execuţia programului se va opri.

• Orice tablou are o dată membră length care returnează lungimea tabloului
respectiv, ca ı̂n exemplul următor:

int[] a=new int[10];


System.out.println(a.length); //afiseaza valoarea 10

Pentru a accesa elementele unui tablou vom folosi, la fel ca şi ı̂n C/C++
numele tabloului şi numărul elementului ı̂n cadrul tabloului:

nume[indice];

Tipul tablou este un tip referinţă ceea ce ı̂nseamnă că variabila cu ajutorul
căreia am declarat tabloul, de fapt reţine adresa zonei efective de memorie
unde se reţin elementele tabloului.
Acesta este motivul pentru care dacă vom transmite ca parametru un
tablou la o metodă modificările efectuate asupra elementelor acestuia se vor
păstra.
18CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

Probleme rezolvate
Problema 1: Se citeşte de la tastatură un şir de numere ı̂ntregi. Să se
sorteze crescător elementele şirului şi să se afişeze şirul astfel obţinut.

Rezolvarea acestei probleme ar putea arăta astfel:


import java.util.*;

public class SortareSir


{
public static void main(String args[])
{
Scanner s=new Scanner(System.in);
System.out.print("n=");
int n=s.nextInt();
int[] a=new int[n];
for(int i=0;i<a.length;i++)
{
System.out.print("a["+i+"]=");
a[i]=s.nextInt();
}
sortare(a);
afisare(a);
}

static void sortare(int[] a)


{
for(int i=0;i<a.length-2;i++)
for(int j=i+1;j<a.length;j++)
if(a[i]>a[j])
{
int aux=a[i];
a[i]=a[j];
a[j]=aux;
}
}

static void afisare(int[] a)


{
for(int i=0;i<a.length;i++)
System.out.print(a[i]+" ");
2.8. VECTORI ŞI MATRICI 19

}
}
Observaţii:
• Se putea realiza afişarea şirului şi ı̂n alt mod, folosind capabilităţile
oferite de JDK 1.6:

for(int x:a)
System.out.println(x+" ");

• Pentru lucrul cu şiruri avem clasa Arrays care are definite câteva
metode foarte utile. Astfel, puteam sorta elementele şirului folosind
următorul cod:

Arrays.sort(a);

Se recomandă consultarea API-ului Java pentru studierea celorlalte


metode utile pentru lucrul cu şiruri.
Problema 2: Să se scrie o metodă care primeşte ca parametru un şir de
numere ı̂ntregi şi returnează şirul obţinut prin inserarea ı̂ntre fiecare 2 ele-
mente a sumei lor.

Metoda ar putea arăta astfel:

static int[] generareSir(int[] a)


{
int[] b=new int[2*a.length-1];
for(int i=0;i<a.length;i++)
b[2*i]=a[i];
for(int i=1;i<b.length;i+=2)
b[i]=b[i-1]+b[i+1];
return b;
}

Problema 3: Să se scrie o metodă care primeşte ca parametru un şir a,


o poziţie poz şi o valoare val şi returnează ı̂ntr-un şir, vectorul obţinut prin
inserarea elementului val pe poziţia poz ı̂n şirul iniţial.

În Java există o metodă prin care putem copia elemente dintr-un şir in
alt şir elegant şi eficient, folosind metoda arraycopy a clasei System. Antetul
acestei metode este următorul:
20CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

public static native void arraycopy(Object src, int srcPosition,


Object dst, int dstPosition, int length)
Efectul apelului acestei metode constă ı̂n faptul că se copiază din şirul
src, ı̂ncepând de la poziţia srcPosition, length elemente, care se vor plasa ı̂n
şirul dst ı̂ncepând de la poziţia dstPosition.
Această metodă va fi folosită la rezolvarea problemei:
static int[] inserare(int[] a, int poz, int val)
{
int[] b=new int[a.length+1];
System.arraycopy(a, 0, b, 0, poz);
b[poz]=val;
System.arraycopy(a, poz, b, poz+1, a.length-poz);
return b;
}
Metoda poate fi apelată scriind cod precum următorul:
a=inserare(a, 2, 3);

2.8.2 Matrici
În Java, o matrice este reţinută sub forma unui şir de şiruri. O matrice
se poate declara şi iniţializa ı̂n trei feluri.
Cea mai simplă variantă este să declarăm matricea şi să alocăm memorie
pentru ea ca ı̂n exemplul următor:
int[][] a=new int[nrLinii][nrColoane];
Matricile se pot şi iniţializa direct. Prezentăm modul ı̂n care se poate
iniţializa o matrice cu 2 linii şi 3 coloane:
int[][] a= {{2, 3, 4},
{1, 4, 2}};
Totuşi, ı̂n Java o matrice poate avea un număr variabil de elemente pe
fiecare linie:
int[][] a= {{2, 3},
{1, 4, 2, 5}};
În acest caz, numărul de linii ale matricii este a.length iar numărul de
coloane ale liniei i este a[i].length.
Dacă dorim să citim o astfel de matrice de la tastatură putem proceda
astfel:
2.8. VECTORI ŞI MATRICI 21

int[][] a=new int[nrLinii][];


for(int i=0;i<a.length;i++)
/
// citire dimensiune m
a[i]=new int[m];
for(int j=0;j<a[i].length;j++)
//citire element a[i][j]
/

Cel mai bine se va vedea cum se lucrează cu astfel de matrici ı̂n exemplele
următoare.
Problema 1 Se citeşte de la tastatură o matrice a, pătratică de ordin n.
Să se verifice dacă toate elementele de pe diagonala principală sunt mai mari
decât suma elementelor de pe liniile corespunzătoare lor.

Rezolvarea problemei ar putea arăta astfel:

import java.util.*;
public class VerificareMatrice
{
public static void main(String args[])
{
Scanner s=new Scanner(System.in);
System.out.print("n=");
int n=s.nextInt();
int[][] a=new int[n][n];
for(int i=0;i<a.length;i++)
for(int j=0;j<a.length;j++)
{
System.out.print("a["+i+"]["+j+"]=");
a[i][j]=s.nextInt();
}
if(verificare(a))
System.out.println("Se respecta conditia");
else
System.out.println("Nu se respecta conditia");
}

static boolean verificare(int[][] a)


{
for(int i=0;i<a.length;i++)
22CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

{
int s=0;
for(int j=0;j<a[i].length;j++)
s+=a[i][j];
if(2*a[i][i]<=s)
return false;
}
return true;
}
}
Problema 2 Se citeşte o matrice a cu n linii şi număr variabil de elemente
pe fiecare linie. Să se calculeze şi să se afişeze minimul dintre maximele de
pe fiecare linie.

Rezolvarea ar putea arăta ı̂n felul următor:


import java.util.*;
public class MatriceNeregulata
{
public static void main(String args[])
{
Scanner s=new Scanner(System.in);
System.out.print("Numarul de linii: ");
int n=s.nextInt();
int[][] a=new int[n][];
for(int i=0;i<a.length;i++)
{
System.out.print("Numarul de coloane ale liniei "+i+":");
int m=s.nextInt();
a[i]=new int[m];
for(int j=0;j<a[i].length;j++)
{
System.out.print("a["+i+"]["+j+"]=");
a[i][j]=s.nextInt();
}
}
System.out.println(minMax(a));
}

static int maximSir(int[] a)


{
2.9. ŞIRURI DE CARACTERE 23

int max=a[0];
for(int i=1;i<a.length;i++)
if(a[i]>max)
max=a[i];
return max;
}

static int minMax(int[][] a)


{
int min=maximSir(a[0]);
for(int i=1;i<a.length;i++)
if(maximSir(a[i])<min)
min=maximSir(a[i]);
return min;
}
}

2.9 Şiruri de caractere


Avem mai multe clase care ne ajută să lucrăm cu şiruri de caractere.
Cele mai cunoscute şi care vor fi prezentate ı̂n continuare sunt: String,
StringBuf f er şi StringT okenizer.

2.9.1 Clasa String


În principiu, pentru a reţine un şir de caractere se foloseşte clasa String.
Aceasta reţine un şir de caractere de tip char.
Un şir de caractere se poate iniţializa astfel:

String nume="Adi";

Pentru a vedea lungimea unui şir de caractere folosim metoda length() a


clasei String astfel:

System.out.println(nume.length());

Efectul acestei instrucţiuni va fi afişarea valorii 3.


Se poate obţine caracterul de pe poziţia i al unui şir de caractere folosind
metoda charAt() a clasei String care are următorul antet:

char charAt(int index)


24CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

Pentru a compara dacă două şiruri de caractere s1 şi s2 sunt egale, nu


folosim operatorul == ci mai degrabă metoda equals ca ı̂n exemplul următor:

if (s1.equals(s2))
...

Principalele metode pentru lucrul cu şiruri de caractere sunt:

• char charAt(int index)


returnează caracterul de pe poziţia index

• int compareTo(String other)


returnează o valoare mai mica decât 0 dacă şirul de caractere asupra
căruia se face operaţia este mai mic decat cel transmis ca parametru,
0 dacă sunt egale şi o valoare mai mare decât 0 ı̂n celelalte cazuri.

• boolean endsWith(String suffix)


returnează true dacă şirul de caractere se termină cu caracterele şirului
suf f ix.

• boolean equals(Object other)

• boolean equalsIgnoreCase(String other)

• int indexOf(String str)

• int indexOf(String str, int fromIndex)

• int lastIndexOf(String str)

• int length()

• boolean startsWith(String prefix)

• String substring(int beginIndex, int endIndex)

• String toLowerCase()

• String toUpperCase()

• String trim()
2.9. ŞIRURI DE CARACTERE 25

Trebuie spus că pentru lucrul cu şiruri de caractere există numeroase


alte metode ale clasei String. Pentru a le putea cunoaşte pe toate este
recomandată studierea API-ului Java.
O operaţie foarte importantă ı̂n lucrul cu şiruri de caractere este con-
catenarea care am văzut ı̂n mai multe exemple că se face cu operatorul +.
Vom prezenta ı̂n continuare câteva exemple de lucru cu şiruri de caractere:
Problema 1 Se citeşte de la tastatură un şir de caractere. Să se verifice
dacă acest şir are aspect de palindrom.

import java.util.*;
public class StringPalindrom
{
static boolean palindrom(String s)
{
for(int i=0;i<=(s.length()-2)/2;i++)
if(s.charAt(i)!=s.charAt(s.length()-i-1))
return false;
return true;
}

public static void main(String args[])


{
Scanner s=new Scanner(System.in);
System.out.print("Sirul de caractere: ");
String str=s.nextLine();
if(palindrom(str))
System.out.println("Are aspect de palindrom");
else
System.out.println("Nu are aspect de palindrom");
}
}

Problema 2 Se citeşte un şir de şiruri de caractere. Să se sorteze crescător


şirul şi să se afişeze şirul astfel obţinut.

import java.util.*;
public class SortareStringuri
{
public static void main(String args[])
26CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

{
Scanner s=new Scanner(System.in);
System.out.print("n=");
int n=s.nextInt();
s.nextLine();
String[] str=new String[n];
for(int i=0;i<str.length;i++)
{
System.out.print("str["+i+"]=");
str[i]=s.nextLine();
}
sortare(str);
afisare(str);
}

static void sortare(String str[])


{
for(int i=0;i<str.length-1;i++)
for(int j=i+1;j<str.length;j++)
if(str[i].compareTo(str[j])>0)
{
String aux=str[i];
str[i]=str[j];
str[j]=aux;
}
}

static void afisare(String[] str)


{
for(String s:str)
System.out.print(s+" ");
}
}

Observaţie importantă:
Clasa String este immutable ceea ce ı̂nseamnă că niciodată o variabilă
de tip String nu se poate modifica. Se poate doar ca ea să reţină o altă
referinţă. Dacă vrem să putem modifica un şir de caractere folosim clasa
StringBuf f er.
2.9. ŞIRURI DE CARACTERE 27

2.9.2 Clasa StringBuffer


Problema 1 Se citeşte un şir de caractere. Să se ı̂nlocuiască fiecare vocală
cu următorul caracter din alfabet şi să se afişeze şirul obţinut.
import java.util.Scanner;
public class InlocuireVocale
{
static boolean vocala(char c)
{
/*switch(Character.toLowerCase(c))
{
case ’a’:
case ’e’:
case ’i’:
case ’o’:
case ’u’: return true;
}
return false;*/
c=Character.toLowerCase(c);
return ("aeiou".indexOf(c)>=0);
}

static String inlocuireVocaleBun(String s)


{
StringBuffer sb=new StringBuffer(s);
for(int i=0;i<sb.length();i++)
if(vocala(sb.charAt(i)))
sb.setCharAt(i,(char)(s.charAt(i)+1));
return sb.toString();
}

static String inlocuireVocaleNuAsaBun(String s)


{
String s2="";
for(int i=0;i<s.length();i++)
if(vocala(s.charAt(i)))
s2+=(char)(s.charAt(i)+1);
else
s2+=s.charAt(i);

return s2;
28CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

public static void main(String args[])


{
Scanner s=new Scanner(System.in);
System.out.print("Sirul de caractere: ");
String str=s.nextLine();
System.out.println("Sirul transformat: "+inlocuireVocaleBun(str));
}
}

Problema s-ar mai fi putut rezolva şi ı̂n alt mod, şi anume prin folosirea
metodei append() a clasei StringBuffer:

static String inlocuireVocaleNuAsaBun2(String s)


{
StringBuffer s2=new StringBuffer("");
for(int i=0;i<s.length();i++)
if(vocala(s.charAt(i)))
s2.append(s.charAt(i)+1);
else
s2.append(s.charAt(i));
return s2.toString();
}

2.9.3 Clasa StringTokenizer


Clasa StringT okenizer se foloseşte pentru a sparge un şir de caractere
ı̂n mai mulţi tokeni. Exemplificăm folosirea ei printr-o problemă:
Problema 1 Se citeşte o expresie matematică. Să se afişeze toţi operanzii
din această expresie.

import java.util.*;
public class AfisareOperanzi
{
public static void main(String args[])
{
Scanner s=new Scanner(System.in);
System.out.print("Expresia matematica: ");
String expr=s.nextLine();
2.9. ŞIRURI DE CARACTERE 29

StringTokenizer st=new StringTokenizer(expr,"+-*/% ");


while(st.hasMoreTokens())
System.out.println(st.nextToken());
}
}

2.9.4 Expresii regulate ı̂n Java


Una din cele mai importante facilităţi legate de lucrul cu şiruri de carac-
tere ı̂n Java o constituie lucrul cu expresii regulate.
Acesta ne permite să ne definim un anumit şablon şi să vedem dacă şirul
de caractere respectă acel şablon sau să găsim toate apariţiile ı̂ntr-un text a
unor subşiruri de caractere care verifică acel şablon.
Problema 1 De exemplu, pentru a verifica dacă un şir de caractere este
alcătuit din exact 3 cifre, prima fiind diferită de zero putem proceda astfel:

import java.util.regex.Pattern;
import java.util.*;

public class ExpresiiRegulate {


public static void main(String args[]) {
Scanner s = new Scanner(System.in);
System.out.print("Introduceti sirul");
String str=s.nextLine();
if(Pattern.matches("[\\d&&[^0]]\\d\\d", str))
System.out.println("Respecta");
else
System.out.println("Nu respecta");
}
}

Tabela 2.2: Clase caracter şi clase predefinite

Reprezentare Semnificaţie
[abc] a, b sau c
[ˆabc] orice caracter cu excepţia lui a, b sau c
[a-zA-Z] caractere ı̂ntre a-z şi A-Z
[a-d[m-p]] caractere ı̂ntre a-d şi m-p
[a-z&&[def]] d, e sau f (intersecţie)
[a-z&&[ˆbc]] de la a la z, cu excepţia lui b şi c
[a-z&&[ˆm-p]] de la a la z, cu excepţia lui m-p
30CAPITOLUL 2. STRUCTURI FUNDAMENTALE DE PROGRAMARE ÎN JAVA

Tabelul 2.2 (continuare)


Reprezentare Semnificaţie
. Orice caracter
\d O cifra: [0-9]
\D Un caracter care nu e cifra: [ˆ0-9]
\s Un caracter spaţiu: [ \t\n\r]
\S Un caracter non-spaţiu [ˆ\s]
\w Un caracter dintr-un cuvant: [a-zA-Z 0-9]
\W Un caracter care nu face parte dintr-un cuvânt: [ˆ\w]

Am prezentat ı̂n tabelul anterior principalele clase de caractere şi clase


predefinite care sunt definite pentru lucrul cu expresii regulate ı̂n Java.
Se poate ca pentru un astfel de caracter să specificăm numărul exact de
apariţii ı̂n text, folosind cuantificatorii prezentaţi ı̂n tabelul următor.
Problema 2 Să se scrie fragmentul de cod care verifică dacă un şir de
caractere citit de la tastatură este alcătuit din concatenarea a ı̂ntre 2 şi 4
numere de câte două cifre, prima fiind diferită de 0.

if(Pattern.matches("([\\d&&[^0]]\\d)2,4", str))
System.out.println("Respecta");
else
System.out.println("Nu respecta");

Tabela 2.3: Cuantificatori pentru lucrul cu expresii reg-


ulate

Reprezentare Semnificaţie
X? X, odată sau deloc
X* X, de zero sau mai multe ori
X+ X, odată sau de mai multe ori
X{n} X, exact de n ori
X{n,} X, de cel puţin n ori
X{n,m} X, de cel puţin n ori dar nu mai mult de m ori
CAPITOLUL 3

Concepte şi principii ı̂n POO

Programarea obiect orientată (POO) este o paradigmă de programare


care foloseşte obiecte precum şi interacţiunile ı̂ntre ele pentru a construi
aplicaţii şi programe. Tehnicile de programare specifice POO se bazează pe
noţiuni precum: ı̂ncapsulare, modularitate, polimorfism şi moştenire.
Programarea obiect orientată a ajuns ı̂n prim plan la ı̂nceputul anilor ’90
odată cu folosirea pe scară largă a limbajelor C++ şi Java.
Aproape toate limbajele de programare importante din zilele noastre
folosesc programarea obiect orientată. Este cazul limbajelor C++, Java,
C#, Visual Basic, Object Pascal şi lista ar putea continua.
POO poate fi vazută ca o colecţie de obiecte care cooperează, spre deose-
bire de programarea procedurală care era vazută ca o grupare de task-uri de
efectuat (subrutine). În POO, fiecare obiect este capabil de a primi mesaje,
de a procesa date şi de a trimite mesaje la alte obiecte.

3.1 Concepte ı̂n programarea obiect orientată


Clasa defineşte caracteristicile comune mai multor lucruri (obiecte), in-
cluzând partea informaţională a acestora (atribute, câmpuri sau proprietăţi)
şi comportamentul lor (lucrurile pe care le pot face - reprezentate prin metode).
Clasa este un tip de date deoarece, la fel ca şi tipul float are o parte
de informaţie precum şi o parte de comportament. Practic, lucrul cu clase
permite definirea de noi tipuri de date ı̂n loc de a folosi doar tipurile care
sunt deja existente ı̂n limbaj.

31
32 CAPITOLUL 3. CONCEPTE ŞI PRINCIPII ÎN POO

Astfel se ı̂mbogăţeşte limbajul prin adăugarea de tipuri de date nece-


sare programatorului. Limbajul nu numai că permite acest lucru dar ı̂l şi
ı̂ncurajează puternic.
Odată creată o clasă se pot crea oricâte obiecte de tipul acelei clase,
obiecte care vor corespunde obiectelor din domeniul problemei.
Clasele oferă de asemenea modularitate şi structură ı̂ntr-un program bazat
pe programarea obiect orientată.
Obiectul este un exemplar al unei clase (o instanţă a unei clase).
Instanţă este un obiect creat la momentul rulării pe baza unei clase.
Setul de valori ale atributelor unui obiect reprezintă starea acelui obiect.
Metodele se referă la abilităţile unui obiect. În cadrul unui program
prin folosirea unei metode este afectat un singur obiect, cel asupra căruia
este apelată metoda respectivă (excepţie fac aici metodele statice).
Transmiterea de mesaje este procesul prin care un obiect trimite date
altui obiect sau ı̂i cere altui obiect să apeleze o metodă.
În urmă cu ceva timp un programator descria limbajul Smalltalk astfel:

• Totul este un obiect (Un obiect este o variabilă mai deosebită care are
atât stare cât şi comportament)

• Un program este alcătuit dintr-un set de obiecte care-şi spun unul altuia
ce să facă.

• Fiecare obiect are propria sa memorie alcătuită eventual din alte obiecte.

• Fiecare obiect are un tip (este o instanţă a unei clase).

• Toate obiectele de acelaşi tip pot primi aceleaşi mesaje (chiar şi ı̂n
contextul moştenirii).

După cum putem uşor observa toate aceste lucruri sunt valabile şi acum
ı̂n contextul limbajului Java.

3.2 Principii ale programării obiect orientate


Abstractizarea reprezintă simplificarea realităţii complexe prin mode-
larea de clase potrivite problemei de rezolvat precum şi lucrul la cel mai
potrivit nivel de moştenire pentru un anumit aspect al unei probleme.
Toate limbajele de programare oferă un anumit grad de abstractizare.
Complexitatea problemei de abstractizat depinde foarte mult de tipul ab-
stractizării şi de calitatea acesteia.
3.2. PRINCIPII ALE PROGRAMĂRII OBIECT ORIENTATE 33

Limbajul de asamblare este o mică abstractizare faţă de limbajul cod


maşină.
Următoarele limbaje care au apărut sunt abstractizări ale limbajului de
asamblare. Ne referim aici la limbaje gen Basic şi C.
Aceste limbaje reprezintă o ı̂mbunătăţire majoră faţă de limbajul de
asamblare dar tot ne fac să gândim ı̂n termen de structură a calculatoru-
lui, nu numai ı̂n termeni referitori la domeniul problemei de rezolvat.
Astfel, avem programe destul de greu de scris şi greu de menţinut.
Abordarea obiect-orientată oferă programatorului instrumente pentru a
reprezenta elementele din spaţiul problemei.
POO ne permite să exprimăm problema ı̂n termenii domeniului core-
spunzător ei. Vom folosi obiecte care au fiecare o stare şi un comportament.
Încapsularea se referă la detaliile funcţionale ale unei clase din punct
de vedere al obiectelor care trimit mesaje către acea clasă. Încapsularea se
obţine prin specificarea clară a claselor care pot folosi membrii unui obiect.
Rezultatul constă ı̂n faptul că fiecare obiect expune unei anumite clase o
anumită interfaţă - acei membri accesibili acelei clase.
Motivaţia folosirii ı̂ncapsulării o constituie prevenirea folosirii de către
clienţi a acelor părţi ale unei clase care s-ar putea să se schimbe ı̂n viitor.
Astfel, metodele sunt publice, private sau private, determinând dacă sunt
valabile tuturor claselor, numai claselor derivate sau numai ı̂n clasa ı̂n care
sunt definite.
Din punctul de vedere al modului ı̂n care folosesc clasele programatorii
se ı̂mpart ı̂n două mari categorii:

• cei care creează clasa - aceştia sunt cei care cunosc toate detaliile despre
clasa respectivă, fiind cei care realizează crearea unui nou tip de date

• cei care sunt doar clienţi ai clasei - aceştia sunt acei programatori care
doar folosesc clasa respectivă nefiind interesaţi de modul ı̂n care clasa
este implementată efectiv, ci doar de serviciile pe care aceasta le oferă.
Acestea sunt specificate prin interfaţ a clasei respective.

Putem practic afirma că interfaţa unei clase reprezintă un contract ı̂ntre
prima categorie de programatori şi ceea de-a doua, ı̂n sensul ı̂n care cre-
atorii clasei le oferă o asigurare celor care folosesc acea clasă ca metodele din
interfaţă nu se vor modifica ı̂n viitor chiar dacă implementările vor fi altele.
Moştenirea se referă la anumite clase care partajează anumite informaţii
precum şi comportament. Subclasele sunt versiuni mai specializate ale unor
clase, care moştenesc atribute şi comportament de la clasele lor părinte şi işi
introduc propriile lor atribute şi metode.
Fiecare subclasă ı̂şi poate altera caracteristicile moştenite.
34 CAPITOLUL 3. CONCEPTE ŞI PRINCIPII ÎN POO

În unele limbaje, nu şi ı̂n Java, putem avea moştenire multiplă. Aceasta
ı̂nseamnă să moşteneşti atribute şi comportament din mai multe clase de
bază.
Polimorfismul permite programatorului să trateze metodele claselor
derivate ca şi pe metodele claselor lor părinte. Mai exact, polimorfismul
reprezintă capabilitatea obiectelor care aparţin unor tipuri de date diferite
să răspundă la apeluri de metode cu acelaşi nume, fiecare corespunzătoare
unui comportament specific tipului lor.

3.3 Soluţii pentru utilizarea eficienta a prin-


cipiilor POO
Există un anumit număr de provocări pe care un dezvoltator le ı̂ntâlneşte
ı̂n mod regulat atunci când face designul unui sistem obiect orientat. Există
de asemenea şi soluţii acceptate la scară largă pentru aceste probleme.
Cele mai cunoscute astfel de soluţii sunt cele oferite de şabloanele de
proiectare documentate ı̂n GoF (cartea Design patterns - Elements of Reusable
Object Oriented Software scrisă de Gamma, Helm, Johnsson şi Vlissides).
Acestea pot fi văzute ca nişte soluţii generale, aplicabile ı̂n mod repetat unor
probleme care tot apar ı̂n designul de software.
Toate aceste şabloane de proiectare au la bază câteva idei foarte impor-
tante:

1. Când un anumit lucru variază acel lucru trebuie ı̂ncapsulat. Aceasta


ı̂nseamnă că atunci când ı̂ntâlnim o anumită parte a sistemului soft,
care este foarte probabil să se modifice ı̂n viitor, aceasta ar trebui izo-
lată ı̂n sensul ı̂n care să se specifice o interfaţă cât mai stabilă a ei,
implementarea putând fi modificată ulterior.

2. Folosirea compoziţiei ı̂n dauna moştenirii. Chiar dacă moştenirea este


un instrument foarte puternic, adesea se abuzează de ea, ı̂n situaţii ı̂n
care ar fi mai bine dacă nu ar fi folosită. Decizia de a folosi moştenirea
trebuie să fie o decizie bine argumentată luată numai după ce s-au
studiat şi alternativele.

3. În general este mai bine să lucrăm pe interfeţe nu pe implementări.


Acest lucru este necesar deoarece dacă la un moment dat se alege să se
lucreze cu o altă implementare şi ne-am bazat pe prima implementare,
va trebui să schimbăm codul. De asemenea, lucrul pe interfeţe ı̂n dauna
moştenirii favorizează utilizarea polimorfismului.
3.3. SOLUŢII PENTRU UTILIZAREA EFICIENTA A PRINCIPIILOR POO35

O altă idee extrem de sugestivă este cea enunţată de cunoscutul infor-


matician Bertrand Meyer şi care poartă numele de Principiul Open-Closed :
Toate entităţile software trebuie să fie deschise pentru extensie şi ı̂nchise
pentru modificare.
Atunci când o schimbare ı̂ntr-o aplicaţie conduce la numeroase alte schimbări
ı̂n cascadă ı̂n alte zone ale aplicaţiei, aceste este un semn că avem de-a face
cu un design slab. Programul devine fragil, greu de stăpânit şi imprevizibil.
Acest principiu spune că trebuie să construim module care nu se schimbă,
ı̂n sensul ı̂n care dacă vrem să adăugăm noi facilităţi putem scrie cod nou,
dar fără a modifica codul deja existent.
În mod evident, nu se poate ca un modul să fie complet ı̂nchis. În anu-
mite situaţii este necesar să modificăm cod existent. Totuşi aplicaţia trebuie
gândită astfel ı̂ncât să permită acest lucru ı̂ntr-un mod cât mai flexibil şi
premeditat.
Există ı̂ncă două principii importante referitoare la designul modulelor
ı̂ntr-o aplicaţie (ele fac parte dintr-o suită mai mare de astfel de sfaturi care
poartă numele GRASP - General Responsabilities Assignmnet Software Pat-
terns):

1. Între componentele unui modul (clasă, pachet, etc.) trebuie să existe
o coeziune ı̂naltă. Aceasta ı̂nseamnă că nu este bine, spre exemplu, ca
ı̂ntr-o clasă să avem mai multe informaţii şi metode ı̂ntre care nu există
legături strânse. Există metrici specializate pentru măsurarea acestei
caracteristici.

2. Între diversele părţi componente ale unei aplicaţii trebuie să existe o
cuplare slabă. Aceasta ı̂nseamnă că atunci când modificăm un modul,
modificările asupra lui să nu genereze modificări ı̂n cascadă asupra altor
module.

În final vom spune câteva cuvinte despre un alt principiu foarte important
referitor la programarea obiect orientată şi anume principiul numit Inversion
of Control (IoC). Acesta este un principiu abstract care descrie un anumit
aspect referitor la designul sistemelor soft ı̂n care fluxul evenimentelor este
invers faţă de cursul normal ı̂ntr-o aplicaţie tradiţională.
El a fost asemănat cu principiul numit ”Hollywood Principle”: don’t call
us, we’ll call you.
Se poate spune că IoC este un mod de a scrie software ı̂n care un cod
generic şi reutilizabil poate fi aplicat la diverse probleme.
Există două moduri de lucru care au la bază principiul IoC:

• programarea orientată pe evenimente (event-driven programming)


36 CAPITOLUL 3. CONCEPTE ŞI PRINCIPII ÎN POO

• injectarea de dependenţă (dependency injection) - aceasta din urmă


ı̂nseamnă, spre exemplu, că avem o componentă care deja are definit
un schelet de utilizare, dar care pentru a fi utilă are nevoie ca noi să
”injectăm” un anumit comportament.
CAPITOLUL 4

Obiecte şi clase

În limbajul Java pentru a crea până şi cea mai simplă aplicaţie este nevoie
să creăm măcar o clasă.
După cum vom vedea ı̂n continuare o aplicaţie Java este alcătuită, ı̂n
principiu, din mai multe clase care colaborează ı̂ntre ele. De obicei există o
clasă care conţine şi o metoda main() şi care constituie punctul de intrare ı̂n
aplicaţie, clasă care foloseşte alte clase la rularea aplicaţiei.
Se poate ı̂ntâmpla ca o aplicaţie să aibă mai multe metode main(),
situaţie ı̂n care, la rularea programului putem specifica care este clasa a
cărei metodă se va executa.

4.1 Definirea unei clase


Pentru definirea unei clase se foloseşte următoarea sintaxă:

[modificatori] class NumeClasa [extends ClasaBaza]


[implements Interfata1[, Interfata2...]]
{
//definire atribute clasa

//definire constructori

//definire metode clasa


}

37
38 CAPITOLUL 4. OBIECTE ŞI CLASE

Modificatorii aplicabili unei clase sunt următorii: public, abstract, final


şi static.
Modificatorul public poate fi folosit pentru a specifica că o clasă poate
fi folosită peste tot ı̂n aplicaţie dacă este inclus pachetul ı̂n care ea este
declarată sau clasa care o foloseşte face parte din acelaşi pachet. Dacă acest
modificator lipseşte atunci ı̂nseamnă că avem vizibilitate la nivel de pachet
(numai clasele din acelaşi pachet pot utiliza această clasă).
Modificatorul abstract specifică faptul că avem de-a face cu o clasă ab-
stractă. Acest lucru ı̂nseamnă că nu se poate instanţia această clasă (nu se
pot crea obiecte de acest tip). Vom discuta mai mult despre acest modificator
ı̂n contextul moştenirii.
Modificatorul f inal aplicat unei clase specifică faptul că această clasă nu
mai poate fi derivată.
Modificatorul static nu se poate aplica decât ı̂n contextul claselor imbri-
cate. Vom studia acest lucru ı̂ntr-o secţiune special dedicată acestor clase.
Despre folosirea lui extends şi implements vom discuta atunci când vom
prezenta moştenirea şi modul ı̂n care o clasă poate implementa interfeţe.

4.2 Declararea atributelor unei clase


În primul rând trebuie făcută clar distincţia ı̂ntre atribute şi variabile
locale.
O variabilă locală este definită ı̂n interiorul unei metode sau a unui con-
structor.
Un atribut al clasei este definit ı̂n afara oricărei metode. După cum vom
vedea ı̂n continuare atributele pot fi de două feluri: statice (atribute ale
clasei) şi nestatice (atribute ale instanţei).
Un exemplul din care se poate vedea acest lucru este următorul:

class Stiva
{
public final static int MAX=100; //atribut static
private int nrElemente; //atribut nestatic

public void push(Object o)


{
int a=0; //variabila locala
....
}
}
4.2. DECLARAREA ATRIBUTELOR UNEI CLASE 39

Pentru a declara (şi eventual iniţializa explicit) un atribut ı̂n Java se


foloseşte următoarea sintaxă:

[modificatori] tip numeAtribut[=val implicita][,...]

Dacă nu iniţializăm ı̂n mod explicit un atribut el are nişte valori implicite:
0 pentru tipurile ı̂ntregi, 0.0 pentru tipurile reale, boolean pentru caractere
şi null pentru obiecte.
Modificatorii aplicabili atributelor ı̂n Java sunt: public, private, pro-
tected, final, static, transient şi volatile.

4.2.1 Modificatorii de vizibilitate


Modificatorul public aplicat unui atribut semnifică faptul că acel atribut
este accesibil de oriunde, folosind notaţia cu punct, ca ı̂n exemplul următor:

class Complex
{
public double re;
public double im;

public Complex(double r, double i)


{
re=r;
im=i;
}
//alte atribute si metode
}

class UseComplex
{
public static void main(String args[])
{
Complex c=new Complex(3,4);
c.re=6; //se poate folosi atributul din afara clasei
}
}

Modificatorul private semnifică faptul că un atribut este accesibil numai


din interiorul clasei ı̂n care este definit. Astfel, exemplul anterior de folosire
a atributului nu ar fi funcţionat dacă s-ar fi declarat atributul ca fiind privat.
40 CAPITOLUL 4. OBIECTE ŞI CLASE

Modificatorul protected specifică faptul că un atribut este vizibil atât ı̂n
clasele derivate din clasa ı̂n care este declarat cât şi ı̂n toate clasele care fac
parte din acelaşi pachet cu clasa ı̂n care a fost declarat atributul.
Dacă nu este folosit nici un modificator de vizibilitate se presupune im-
plicit că avem vizibilitate la nivel de pachet (package). Acest lucru ı̂nseamnă
că acel atribut este accesibil doar ı̂n interiorul pachetului ı̂n care este definită
clasa respectivă.

4.2.2 Modificatorul final


Un atribut care este declarat ca final nu mai poate să-şi schimbe valoarea
ı̂ntr-o atribuire ulterioară.
Acest lucru ı̂nseamnă pentru tipurile primitive că valoarea lor este con-
stantă, iar pentru tipurile referinţă că nu se mai poate modifica adresa către
obiectul către care pointează.
Iată şi câteva exemple:

class Test
{
final int a=100;
final String s="test";
final int[] b={2, 4, 5};

void metoda()
{
//genereaza eroare deoarece nu se poate modifica un atribut de
//un tip primitiv declarat constant
a=1000;

//va genera eroare deoarece cand se modifica valoarea unui


//obiect de tip String automat se aloca memorie pt un nou sir
s="alt test";

//functioneaza deoarece nu se modifica adresa retinuta de sir


b[2]=4;

//nu functioneaza deoarece se modifica adresa retinuta de sir


b=new int[5];
}
}
4.2. DECLARAREA ATRIBUTELOR UNEI CLASE 41

4.2.3 Modificatorul static

În mod implicit, dacă modificatorul static nu este folosit asupra unui
atribut, acel atribut poate avea valori diferite pentru obiecte diferite. Astfel,
dacă avem definită o clasă P ersoana care are un atribut nume, acest atribut
va avea valori diferite pentru fiecare obiect de tipul P ersoana.
Modificatorul static aplicat unui atribut ı̂nseamnă că pentru acel atribut
se alocă memorie o singură dată indiferent câte obiecte de tipul clasei avem.
Spunem despre acel atribut că este un atribut al clasei, practic acea valoare
unică nu ţine de obiecte ci de clasă.
Prezentăm şi un exemplu din care se poate vedea modul ı̂n care se pot
folosi atributele statice şi nestatice. Remarcaţi faptul că un atribut static
poate fi accesat şi prin intermediul numelui clasei, mai degrabă decât prin
numele unui obiect.

class Test
{
public static int atributStatic;
public int atributNestatic;
}

public class ExempluStatic


{
public static void main(String args[])
{
Test t1=new Test();
Test t2=new Test();
t1.atributNestatic=3;
t2.atributNestatic=6;
System.out.println(t1.atributNestatic+" "+t2.atributNestatic);
//afiseaza valorile 3 si 6

t1.atributStatic=1;
t2.atributStatic=2;
System.out.println(t1.atributStatic+" "+Test.atributStatic);
//afiseaza valorile 2 si 2
}
}
42 CAPITOLUL 4. OBIECTE ŞI CLASE

4.2.4 Modificatorii transient şi volatile


Modificatorul transient aplicat unui atribut specifică faptul că acel atribut
nu este un atribut care ar trebui păstrat pe suport extern atunci când se va
serializa o instanţă a clasei.
Modificatorul volatile este folosit pentru atribute care pot fi accesate
simultan din mai multe fire de execuţie. Acest modificator avertizează com-
pilatorul că asupra lor nu trebuie făcute anumite optimizări şi că de fiecare
dată trebuie calculată valoarea lor actualizată.

4.3 Declararea metodelor unei clase


Declararea unei metode ı̂n Java are următoarea sintaxă:

[modificatori] tip returnat numeMetoda( parametri)


[throws Exceptie1[, Exceptie2[...]]]
{
//corpul metodei
}

Ca o primă observaţie importantă, trebuie spus că o clasă poate avea mai
multe metode cu acelaşi nume. Totuşi este evident nevoie ca metodele să
difere măcar prin tipul sau numărul parametrilor transmişi la metodă.
Modificatorii aplicabili metodelor ı̂n Java sunt: public, private, protected,
final, static, abstract, native şi synchronized.
Nu vom mai prezenta ı̂n cele ce urmează modificatorii de vizibilitate
public, private şi protected deoarece ei au aceeaşi semnificaţie ca şi ı̂n cazul
declarării atributelor.
Modificatorul f inal aplicat unei metode specifică faptul că acea metodă
nu mai poate fi redefinită ı̂ntr-o clasă derivată. Mai multe despre acest lucru
ı̂n capitolul dedicat moştenirii.
Modificatorul abstract aplicat unei metode specifică faptul că acea metodă
există pentru tipul respectiv, dar nu se cunoaşte ı̂ncă implementarea ei. Ea
va fi implementată ı̂ntr-o clasă derivată.
Modificatorul synchronized aplicat unei metode specifică faptul că apelul
asupra acelei metode trebuie sincronizat. Acest lucru se foloseşte ı̂n cazul
aplicaţiilor care utilizează fire de execuţie.
Vom prezenta ı̂n subsecţiunea următoare rolul modificatorului static asupra
metodelor.
Nu vom prezenta deocamdată facilităţile care ţin de tratarea excepţiilor.
4.3. DECLARAREA METODELOR UNEI CLASE 43

4.3.1 Modificatorul static


Prin specificarea unei metode ca fiind o metodă statică, practic, ı̂i co-
municăm calculatorului că acea metodă nu a fost creată pentru apelarea ei
asupra obiectelor ci mai degrabă avem o metodă a unei clase, care poate
folosi numai atribute şi metode statice ale clasei.
Acesta este motivul pentru care până acum, de fiecare dată când scriam
o metodă trebuia ca acea metodă să fie statică (din metoda main() care este
statică nu se pot apela decât metode statice).
Prezentăm ı̂n continuare un exemplu de clasă pentru care are sens să
definim metode statice:

class Mathematica
{
private Mathematica()
{
}

public static int max(int a, int b)


{
return a>=b?a:b;
}

public static int abs(int a)


{
return a>=0 ? a : -a;
}
}

public class Testare


{
public static void main(String args[])
{
System.out.println(Mathematica.max(3,4));
}
}

4.3.2 Transmiterea parametrilor la metode


În limbajul Java toţi parametri care se transmit unei metode se
transmit prin valoare.
44 CAPITOLUL 4. OBIECTE ŞI CLASE

Acest lucru ı̂nseamnă că modificările asupra lor nu se păstrează. Totuşi


este important de ı̂nţeles ce se transmite de fapt unei metode.
În cazul ı̂n care se transmite o variabilă de un tip primitiv se transmite
de fapt valoarea ei şi modificările asupra acesteia nu se păstrează.
Dacă se transmite un şir, matrice sau un alt obiect, atunci de fapt se
transmite adresa obiectului respectiv. Acest lucru ı̂nseamnă că acea adresă
nu se poate modifica. Totuşi modificările asupra conţinutului aflat la
aceea adresă se păstrează.

4.4 Declararea constructorilor unei clase


Un rol foarte important atunci când lucrăm cu clase şi obiecte ı̂l au
constructorii. Putem să ne gândim la un constructor ca la o metodă mai spe-
cială (nu se poate zice că este chiar metodă deoarece pentru un constructor
nu se specifică tipul returnat).
Aceştia intervin ı̂n momentul ı̂n care dorim să creem noi obiecte, folosind
instrucţiunea new().
Dacă scriem codul:

String s=new String("Rodica");

atunci, pentru variabila s se va apela un constructor al clasei String care


va realiza iniţializarea tuturor atributelor obiectului s.
Constructorii respectă două caracteristici importante:

1. Au ı̂ntotdeauna acelaşi nume cu clasa pentru care se declară.

2. Pentru un constructor nu se specifică nici un tip returnat.

Observatii:

• Se poate ı̂ntâmpla ca un constructor să fie privat, ca ı̂n exemplul prezen-


tat la secţiunea precedentă. În acel exemplu, după cum am putut ob-
serva este imposibilă crearea obiectelor de tipul M athematica.

• Dacă nu avem nici un constructor definit, nu ı̂nseamnă că acel con-


structor lipseşte. Specificaţia Java prevede faptul că dacă nu definim
nici un constructor, automat este creat unul care iniţializează toate
atributele cu valorile lor implicite (0, 0.0, false şi null). Acesta se
numeşte constructorimplicit. Acest constructor implicit, dar şi orice
alt constructor apelează chiar dacă nu este specificat, constructorul fără
parametri din clasa de baza.
4.4. DECLARAREA CONSTRUCTORILOR UNEI CLASE 45

• Dacă pentru o clasă este specificat un constructor, atunci nu va mai fi


generat constructorul implicit. Acest lucru se ı̂ntâmplă indiferent de
tipul parametrilor constructorului creat de programator.

• O clasă poate avea mai mulţi constructori. Acest lucru va fi prezentat


ı̂n exemplul următor.

Prezentăm ı̂n continuare un exemplu de definire parţială a unei clase


Complex care ar permite lucrul cu numere complexe:

public class Complex


{
private double re, im;

/*public Complex(double r, double i)


{
re=r;
im=i;
}*/

public Complex(double re, double im)


{
this.re=re;
this.im=im;
}

public Complex(double re)


{
this(re, 0);
}

public Complex()
{
this(0);
}

//alte metode...

public static void main(String args[])


{
Complex c1=new Complex(3, 4); //retine pe 3+4i
Complex c2=new Complex(6); //retine pe 6+0i
46 CAPITOLUL 4. OBIECTE ŞI CLASE

Complex c3=new Complex(); //retine pe 0+0i


}
}

Observaţii:

• Dacă ı̂ntr-o metodă numele unui parametru al metodei este acelaşi cu


numele atributului clasei, atunci pentru a se putea face distincţia ı̂ntre
cele două se foloseşte cuvântul cheie this pentru a specifica un atribut
al clasei.

• Se poate ca dintr-un constructor să se apeleze un alt constructor al


aceleiaşi clase. Am facut acest lucru cu ajutorul cuvântului cheie this,
care primeşte ı̂n paranteză parametri către acel constructor. Apelul
acesta trebuie să fie prima instrucţiune din cadrul constructorului apelant.

4.5 Iniţializatorul static al unei clase


Iniţializatorul static al unei clase este codul care este executat prima oară
când se foloseşte o clasă. Acesta este marcat prin cuvântul cheie static urmat
de acolade ı̂ntre care se specifică codul de executat.
Iată şi un exemplu:

class Test
{
static int max=10;

static
{
max=100;
System.out.println("In initializatorul static");
}
}

public class FolosireInitializatorStatic


{
public static void main(String args[])
{
Test t=new Test();
}
}
4.6. PROBLEME REZOLVATE 47

4.6 Probleme rezolvate


Problema 1 Să se construiască o clasă pentru lucrul cu numere complexe
(constructori, metode get/set, implementarea modulului unui număr com-
plex, adunarea, ı̂nmulţirea, ı̂mpărţirea şi scăderea). Să se citească de la
tastatură n numere complexe. Să se rezolve următoarele subprobleme:
• Să se sorteze crescător elementele şirului ı̂n funcţie de modulul lor şi să
se afişeze pe ecran.

• Să se calculeze suma elementelor şirului.

public class Complex


{
private double re, im;

public Complex(double re, double im)


{
this.re=re;
this.im=im;
}

public Complex(double re)


{
this(re, 0);
}

public Complex()
{
this(0);
}

public double getRe()


{
return re;
}

public void setRe(double re)


{
this.re=re;
}
48 CAPITOLUL 4. OBIECTE ŞI CLASE

public double getIm()


{
return im;
}

public void setIm(double im)


{
this.im=im;
}

public double getModul()


{
return Math.sqrt(re*re+im*im);
}

public Complex adunare(Complex c)


{
return new Complex(re+c.re, im+c.im);
}

public Complex inmultire(Complex c)


{
return new Complex(re*c.re-im*c.im, re*c.im+im*c.re);
}

public static Complex scadere(Complex c1, Complex c2)


{
return new Complex(c1.re-c2.re, c1.im-c2.im);
}

public static Complex impartire(Complex c1, Complex c2)


{
//de implementat :-) (de catre studenti)
return null;
}

public String toString()


{
if(im<0)
return re+""+im+"*i";
4.6. PROBLEME REZOLVATE 49

return re+"+"+im+"*i";
}
}

import java.util.*;
public class TestComplex
{
public static void main(String args[])
{
Scanner s=new Scanner(System.in);
System.out.print("n=");
int n=s.nextInt();

Complex[] c=new Complex[n];

for(int i=0;i<c.length;i++)
{
System.out.print("partea reala: ");
double re=s.nextDouble();
System.out.print("partea imaginara: ");
double im=s.nextDouble();
c[i]=new Complex(re, im);
}
sortare(c);
afisare(c);

System.out.println("Suma elementelor sirului: "+suma(c));


}

static void sortare(Complex[] c)


{
for(int i=0;i<c.length-1;i++)
for(int j=i+1; j<c.length;j++)
if(c[i].getModul()>c[j].getModul())
{
Complex aux=c[i];
c[i]=c[j];
c[j]=aux;
}
}
50 CAPITOLUL 4. OBIECTE ŞI CLASE

static void afisare(Complex[] c)


{
for(Complex nr: c)
System.out.print(nr+" ");
System.out.println();
}

static Complex suma(Complex[] c)


{
Complex sum=new Complex();
for(int i=0;i<c.length;i++)
sum=sum.adunare(c[i]);
return sum;
}
}

Problema 2 Să se implementeze o clasă pentrul lucrul cu o stivă care


conţine numere ı̂ntregi şi să se folosească această clasă ı̂ntr-un program sim-
plu.
Codul pentru această problemă ar putea arăta astfel:

public class Stack


{
private int[] elemente;
private int top;

public Stack(int dim)


{
top=0;
elemente=new int[dim];
}

public Stack()
{
this(10);
// implicit se creeaza o stiva care are exact
// 10 elemente
}

private void expandare()


4.6. PROBLEME REZOLVATE 51

{
int[] newElemente=new int[2*elemente.length];
System.arraycopy(elemente, 0, newElemente, 0, elemente.length);
elemente=newElemente;
}

private boolean isFull()


{
return top==elemente.length;
}

public boolean isEmpty()


{
return top==0;
}

public void push(int x)


{
if(isFull())
expandare();
elemente[top++]=x;
}

public int top()


{
return elemente[top-1];
}

public int pop()


{
return elemente[--top];
}

public static void main(String args[])


{
Stack s=new Stack();
s.push(3);
s.push(2);
System.out.println(s.top());
}
}
52 CAPITOLUL 4. OBIECTE ŞI CLASE

Problema 3 Să se creeze o clasă care să simuleze jocul Spânzurătoarea.


Această clasă trebuie să fie o clasă cât mai generică pentru a permite reuti-
lizarea ei ı̂n mai multe situaţii, indiferent dacă avem o aplicaţie grafică sau
ı̂n linie de comandă.
Pentru inceput scriem codul care va folosi această clasă:

import java.util.Scanner;

public class TestSpanzuratoare


{
private static void afisare(char[] c)
{
for(int i=0;i<c.length-1;i++)
System.out.print(c[i]+" ");
System.out.println(c[c.length-1]);
}

public static void main(String args[])


{
Scanner s=new Scanner(System.in);
Spanzuratoare joc=new Spanzuratoare();
while(!joc.isOver())
{
System.out.print("Cuvantul de ghicit: ");
afisare(joc.cuvantCurent());

System.out.print("Introduceti o litera: ");


char c=s.nextLine().charAt(0);

if(joc.ghiceste(c))
System.out.println("BRAVO!!! Ai ghicit !");
else
System.out.println("GRESEALA!!! Mai ai "+joc.incercariRamase()+" ince
}
if(joc.isWinner())
System.out.println("AI CASTIGAT!!!");
else
System.out.println("AI PIERDUT!!!");
}
}
4.6. PROBLEME REZOLVATE 53

După cum se poate uşor observa, metodele şi constructorii necesari pentru
această clasă sunt:

Spanzuratoare
---------------------------
public Spanzuratoare()

public char[] cuvantCurent()

public boolean ghiceste(char c)


public int incercariRamase()

public boolean isOver()


public boolean isWinner()

Având interfaţa clasei, adică metodele publice ale clasei, putem scrie co-
dul pentru aceasta:
import java.util.*;
public class Spanzuratoare
{
private String[] cuvinteDeAles={"abecedar", "calculator", "masina"};
private char[] cuvantCurent;
private char[] cuvantAles;
private int nrGreseli;
private String ghicite;

private final static int MAX_NR_GRESELI=5;

public Spanzuratoare()
{
Random r=new Random();
cuvantAles=cuvinteDeAles[r.nextInt(cuvinteDeAles.length)].toCharArray();
cuvantCurent=cuvantAles.clone();
char c1=cuvantAles[0];
char c2=cuvantAles[cuvantCurent.length-1];
cuvantCurent[0]=c1;
cuvantCurent[cuvantCurent.length-1]=c2;
for(int i=0;i<cuvantAles.length;i++)
if(cuvantAles[i]==c1 || cuvantAles[i]==c2)
cuvantCurent[i]=cuvantAles[i];
else
54 CAPITOLUL 4. OBIECTE ŞI CLASE

cuvantCurent[i]=’_’;
ghicite=""+c1+c2;
}

public char[] cuvantCurent()


{
return cuvantCurent.clone();
}

public boolean isOver()


{
if(nrGreseli==MAX_NR_GRESELI)
return true;
for(int i=0;i<cuvantCurent.length;i++)
if(cuvantCurent[i]==’_’)
return false;
return true;
}

public boolean ghiceste(char c)


{
if(ghicite.indexOf(c)>=0)
return false;
ghicite+=c;
boolean gasit=false;
for(int i=0;i<cuvantAles.length;i++)
if(cuvantAles[i]==c)
{
cuvantCurent[i]=c;
gasit=true;
}
if(!gasit)
nrGreseli++;
return gasit;
}

public int incercariRamase()


{
return MAX_NR_GRESELI-nrGreseli;
}
4.6. PROBLEME REZOLVATE 55

public boolean isWinner()


{
return isOver() && nrGreseli<5;
}
}

Problema 4 Să se construiască o clasă MyStringTokenizer care să lucreze


ı̂ntr-un mod asemănător cu clasa StringTokenizer.
Pentru a construi această clasă ne gândim, ı̂n primul rând cum ar trebui
ea să fie folosită ı̂ntr-un program. Codul poate arăta ı̂n felul următor:
public class TestMyStringTokenizer
{
public static void main(String args[])
{
MyStringTokenizer mst=new MyStringTokenizer("Ce mai faci? Eu sunt aici."
," ?!.");
System.out.println("Avem "+mst.countTokens()+" tokeni");
while(mst.hasMoreTokens())
{
String str=mst.nextToken();
System.out.println(str);
}
}
}
Se poate observa destul de uşor că această clasă trebuie să aibă următoarele
metode:

MyStringTokenizer
---------------------------
public MyStringTokenizer(String str, String sep)
public MyStringTokenizer(String str)

public String getSep()


public void setSep(String sep)

public int countTokens()


public boolean hasMoreTokens()
public String nextToken()

Codul pentru această clasă poate arăta astfel:


56 CAPITOLUL 4. OBIECTE ŞI CLASE

public class MyStringTokenizer


{
private String s;
private String sep;

public MyStringTokenizer(String s, String sep)


{
this.s=s;
this.sep=sep;
}

public MyStringTokenizer(String s)
{
this(s," \t\n\r\f");
}

public String getSep()


{
return sep;
}

public void setSep(String sep)


{
this.sep=sep;
}

private boolean isSeparator(char c)


{
return sep.indexOf(c)>=0;
}

public int countTokens()


{
int i=0, tokens=0;
while(i<s.length() && isSeparator(s.charAt(i)))
i++;
for(int j=i;j<s.length();j++)
if(j==0 || !isSeparator(s.charAt(j)) && isSeparator(s.charAt(j-1)))
tokens++;
return tokens;
}
4.6. PROBLEME REZOLVATE 57

public boolean hasMoreTokens()


{
for(int i=0;i<s.length();i++)
if(sep.indexOf(s.charAt(i))<0)
return true;
return false;
}

public String nextToken()


{
int i=0;
while(i<s.length() && isSeparator(s.charAt(i)))
i++;
if(i==s.length())
return null;
int j=i+1;
while(j<s.length() && !isSeparator(s.charAt(j)))
j++;
String cuv=s.substring(i,j);
s=s.substring(j);
return cuv;
}
}

Problema 5 Să se construiască o clasă Queue cu ajutorul căreia să se poată


lucra cu o coadă de elemente numere ı̂ntregi, alocată folosind liste ı̂nlănţuite.
Codul care ar folosi această clasă ar putea arăta astfel. Menţionăm că
am folosit ı̂n continuare tratarea excepţiilor chiar dacă ea va fi prezentată
ı̂ntr-un capitol următor:

import com.mihai.datastructures.*;

public class TestQueue


{
public static void main(String args[])
{
Queue q=new Queue();

q.push(3);
q.push(5);
58 CAPITOLUL 4. OBIECTE ŞI CLASE

q.push(7);
try
{
System.out.println("In varful cozii: "+q.top());
q.pop();
System.out.println("Continutul cozii: "+q);
}
catch(EmptyQueueException e)
{
System.out.println("Coada goala !!!");
}
}
}

După cum se poate uşor observa vom avea nevoie de două clase: Queue
şi EmptyQueueException.
Clasa Queue are următoarea interfaţă:

Queue
---------------------------
public Queue()

public void push(int x)


public int top()
public int pop()
public boolean isEmpty()

public String toString()

Clasele necesare acestei aplicaţii arată astfel:

package com.mihai.datastructures;

public class EmptyQueueException extends Exception{

----------------------------------------

package com.mihai.datastructures;
class Node {
private int value;
4.6. PROBLEME REZOLVATE 59

private Node next;

Node(int value)
{
this.value=value;
this.next=null; //putea lipsi
}

Node(int value, Node next) {


this.value = value;
this.next = next;
}

int getValue() {
return value;
}

void setValue(int value) {


this.value = value;
}

Node getNext()
{
return next;
}

void setNext(Node next)


{
this.next=next;
}
}

----------------------------------------

package com.mihai.datastructures;
public class Queue {
private Node first, last;

public boolean isEmpty()


{
return first==null;
60 CAPITOLUL 4. OBIECTE ŞI CLASE

public void push(int x)


{
Node node=new Node(x);
if(isEmpty())
first=node;
else
last.setNext(node);
last=node;
}

public int top() throws EmptyQueueException


{
if(isEmpty())
throw new EmptyQueueException();
return first.getValue();
}

public int pop() throws EmptyQueueException


{
if(isEmpty())
throw new EmptyQueueException();
int value=first.getValue();
first=first.getNext();
return value;
}

public String toString()


{
StringBuffer sb=new StringBuffer("[");
Node current=first;
if(!isEmpty())
{
while(current.getNext()!=null)
{
sb.append(current.getValue()+",");
current=current.getNext();
}
sb.append(current.getValue());
}
4.6. PROBLEME REZOLVATE 61

sb.append("]");
return sb.toString();
}
}

Problema 6 Să se construiască o clasă HeapMin care să ne permită să


lucrăm cu heapuri minime.
Ea ar trebui să poată fi folosită ı̂ntr-unul din modurile următoare:
import java.util.Random;

public class TestHeapMin


{
public static void main(String args[])
{
/*Random r=new Random();

int[] a={0, 2, 4, 8, 7};

HeapMin heap=new HeapMin(a);

for(int i=0;i<3;i++)
heap.add(r.nextInt(10));

System.out.println(heap);

while(!heap.isEmpty())
System.out.print(heap.extractMin()+" ");*/

Random r=new Random();

HeapMin heap=new HeapMin();

for(int i=0;i<5;i++)
heap.add(r.nextInt(10));

System.out.println(heap);

System.out.print("Elementul minim: "+heap.getMin());


}
}
62 CAPITOLUL 4. OBIECTE ŞI CLASE

Interfaţa acestei clase arată astfel:

HeapMin
---------------------------
public HeapMin(int[] a)
public HeapMin()

public void add(int x)


public void add(int[] a)

public int extractMin()


public int getMin()

public boolean isEmpty()

public String toString()

Implementarea ei propriu-zisă poate arăta ca ı̂n exemplul următor:

public class HeapMin


{
private int count;
private int[] elements;

public HeapMin()
{
elements=new int[3];
}

public HeapMin(int[] a)
{
elements=new int[a.length+5];
System.arraycopy(a, 0, elements, 1, a.length);
count=a.length;
construiesteHeap();
}

private void expand()


{
int newSize;
if(elements.length<10)
newSize=elements.length*2;
4.6. PROBLEME REZOLVATE 63

else
newSize=elements.length*4/3;
int[] newElements=new int[newSize];
System.arraycopy(elements, 1, newElements, 1, elements.length-1);
elements=newElements;
}

private boolean isFull()


{
return count==elements.length-1;
}

public void add(int x)


{
if(isFull())
expand();
elements[++count]=x;
int j=count;
while(j!=1 && elements[j/2]>elements[j])
{
int aux=elements[j];
elements[j]=elements[j/2];
elements[j/2]=aux;
j=j/2;
}
}

public void add(int[] a)


{
for(int x:a)
add(x);
}

private void construiesteHeap()


{
for(int i=count/2;i>=1;i--)
reconstituieHeap(i);
}

private void reconstituieHeap(int i)


{
64 CAPITOLUL 4. OBIECTE ŞI CLASE

int left=2*i, right=2*i+1, minim;


if(left<=count && elements[left]<elements[i])
minim=left;
else
minim=i;
if(right<=count && elements[right]<elements[minim])
minim=right;
if(minim!=i)
{
int aux=elements[i];
elements[i]=elements[minim];
elements[minim]=aux;
reconstituieHeap(minim);
}
}

public int extractMin()


{
int min=elements[1];
elements[1]=elements[count--];
reconstituieHeap(1);
return min;
}

public int getMin()


{
return elements[1];
}

public boolean isEmpty()


{
return count==0;
}

public String toString()


{
StringBuffer sb=new StringBuffer("");
for(int i=1;i<=count;i++)
sb.append(elements[i]+" ");
return sb.toString();
}
4.7. PROBLEME PROPUSE 65

4.7 Probleme propuse


Propunem spre rezolvare următoarele probleme:

1. Să se construiască o clasă pentru lucrul cu fracţii, ı̂n mod asemănător


cu cea pentru lucrul cu numere complexe şi să se folosească ı̂ntr-un
program.

2. Să se construiască o clasă Polinom şi să se implementeze operaţiile


pentru polinoame.

3. Să se creeze o clasă Punct care conţine coordonatele unui punct ı̂n plan.
Se citesc de la tastatură n puncte ı̂n plan. Să se rezolve următoarele
subprobleme:

• Să se determine 2 puncte ı̂ntre care avem distanţă minimă.


• Să se verifice dacă ı̂n şir avem 3 puncte coliniare.
• Să se afişeze toate punctele situate cel mai la nord.

4. Să se construiască o clasă Matrice pentru lucrul cu matrici.

5. Să se construiască o clasă Queue care să simuleze modul de funcţionare


al unei cozi. Elementele cozii vor fi reţinute cu ajutorul vectorilor.
Putem folosi chiar şi vectori circulari.
66 CAPITOLUL 4. OBIECTE ŞI CLASE
CAPITOLUL 5

Moştenirea ı̂n Java

5.1 Noţiuni generale


Unul din cele mai importante concepte folosite ı̂n programarea obiect
orientată este conceptul de moştenire. Folosirea moştenirii ı̂ncurajează:

• refolosirea claselor deja existente

• tratarea ı̂n mod uniform a unor obiecte care sunt de tipuri diferite
(ı̂ncurajează polimorfismul)

Modul de lucru ı̂n Java seamănă oarecum cu ceea ce cunoaştem deja din
limbajul C++, numai că ı̂n Java sintaxa este mult mai uşoară. În plus,
ı̂n Java nu există conceptul de moştenire multiplă (ı̂n Java o clasă poate fi
derivată dintr-o singură altă clasă).
Pentru a indica faptul că o clasă este derivată din altă clasă folosim
cuvântul cheie extends, ca ı̂n exemplul următor:

class ClasaDerivata extends ClasaBaza


{
...
}

Clasa din care se derivează se numeşte clasă de bază (sau clasă părinte,
superclasă) iar clasa care este derivată din ea se numeşte clasă derivată (sau
clasă copil sau subclasă).

67
68 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

O subclasă descrie obiecte care au aceleaşi proprietăţi ca şi cele ale su-
perclasei şi care, eventual, au şi alte atribute sau metode.
Atributele şi metodele din superclasă sunt accesibile şi ı̂n subclasă, cu
excepţia atributelor şi metodelor care sunt declarate private ı̂n superclasă.
De multe ori, atributele private din superclasă pot fi accesate prin intermediul
unor metode din superclasă.
Reamintim că ı̂n limbajul Java există modificatorul de vizibilitate pro-
tected care are următoarea semnificaţie: acele atribute sau metode cu această
vizibilitate sunt văzute oriunde ı̂n subclase precum şi ı̂n toate clasele din pa-
chetul curent.
După cum ştim şi din C++, atunci când avem moştenire, trebuie să putem
apela din constructorul unei clase constructorul clasei de bază. Acest lucru
se poate face cu ajutorul cuvântului cheie super. Apelul constructorului din
clasa de bază trebuie să fie prima instrucţiune din constructor.
Un astfel de exemplu este prezentat ı̂n continuare:

public class Profesor extends Persoana


{
private String gradDidactic;
public Profesor(String nume, String prenume, String grad)
{
super(nume, prenume);
this.gradDidactic=grad;
}
...
}

Observatie: Dacă nu apelăm dintr-un constructor dintr-o clasă derivată


un constructor din clasa de bază, atunci automat se apelează constructorul
fără parametri din clasa de bază. De aceea, trebuie să fim atenţi, pentru că
ı̂n situaţia ı̂n care avem doar constructori cu parametri ı̂n clasa de bază şi
ı̂n constructorul din clasa derivată nu apelăm pe niciunul din ei, vom obţine
eroare.

5.2 Exemplu de aplicaţie folosind moştenirea


În cele ce urmează vom prezenta un program care ilustrează principiile
prezentate anterior.
Exemplul 1 Să se scrie un program care citeşte de la tastatură informaţii
despre n persoane (numele şi prenumele). Persoanele sunt de 2 tipuri: profe-
sori şi studenţi. Pentru fiecare profesor se cunoaşte ı̂n plus gradul didactic pe
5.2. EXEMPLU DE APLICAŢIE FOLOSIND MOŞTENIREA 69

care-l are, iar pentru fiecare student se cunoaşte dacă este bursier sau nu. Să
se sorteze crescător şirul de persoane după nume şi să se afişeze şirul obţinut.
În primul rând definim o clasă Persoană astfel:
public class Persoana
{
private String nume;
private String prenume;

public Persoana(String nume, String prenume)


{
this.nume = nume;
this.prenume = prenume;
}

public String getNume()


{
return nume;
}

public void setNume(String nume)


{
this.nume = nume;
}

public String getPrenume()


{
return prenume;
}

public void setPrenume(String prenume)


{
this.prenume = prenume;
}

public String toString()


{
return nume + " " + prenume;
}
}
Definim o clasă Profesor derivată din clasa Persoana:
70 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

public class Profesor extends Persoana


{
private String gradDidactic;

public Profesor(String nume, String prenume, String gradDidactic)


{
//daca nu apelez constructorul din clasa de baza
//am eroare
super(nume, prenume); //apel constructor din clasa de baza
this.gradDidactic=gradDidactic;
}

public String getGradDidactic()


{
return gradDidactic;
}

public void setGradDidactic(String gradDidactic)


{
this.gradDidactic = gradDidactic;
}

//suprascriere metoda din clasa de baza


public String toString()
{
//apel metoda din clasa de baza
return super.toString()+" este "+gradDidactic;
}
}
Clasa Student se construieşte asemănător cu clasa Profesor:
public class Student extends Persoana
{
private boolean bursier;

public Student(String nume, String prenume, boolean bursier)


{
super(nume, prenume);
this.bursier = bursier;
}
5.2. EXEMPLU DE APLICAŢIE FOLOSIND MOŞTENIREA 71

public boolean isBursier()


{
return bursier;
}

public void setBursier(boolean bursier)


{
this.bursier = bursier;
}

public String toString()


{
if(bursier)
return super.toString()+" este bursier";
return super.toString()+" nu este bursier";
}
}
În cele din urmă prezentăm şi codul pentru aplicaţia care va folosi clasele
create anterior:
import java.util.Scanner;

public class TestPersoane


{
public static void main(String[] args)
{
Scanner s=new Scanner(System.in);

System.out.print("n=");
int n=Integer.parseInt(s.nextLine());

Persoana[] p=new Persoana[n];

for(int i=0;i<n;i++)
{
System.out.print("Numele: ");
String nume=s.nextLine();
System.out.print("Prenumele: ");
String prenume=s.nextLine();
72 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

System.out.print("Profesor sau student (p/s)");


char tip=s.nextLine().charAt(0);
if(tip==’p’)
{
System.out.print("Gradul didactic: ");
String gradDidactic=s.nextLine();
p[i]=new Profesor(nume, prenume, gradDidactic);
}
else
{
System.out.print("Bursier? (d/n)");
if(s.nextLine().charAt(0)==’d’)
p[i]=new Student(nume, prenume, true);
else
p[i]=new Student(nume, prenume, false);
}
}

sort(p);

afisare(p);
}

private static void sort(Persoana [] p)


{
for(int i=0;i<p.length-1;i++)
for(int j=i+1;j<p.length;j++)
if(p[i].getNume().compareToIgnoreCase(p[j].getNume())>0)
{
Persoana aux=p[i];
p[i]=p[j];
p[j]=aux;
}
}

private static void afisare(Persoana [] p)


{
for(Persoana pers:p)
System.out.println(pers); //POLIMORFISM !!!!
}
}
5.3. POLIMORFISMUL 73

Trebuie remarcat că ı̂n metoda de afişare, atunci când se parcurge şirul
de obiecte de tipul Persoana, ı̂n funcţie de tipul obiectelor, Profesor sau
Student se apelează metoda toString() corectă.
Acest mecanism poartă numele de polimorfism.
Soluţia acestei probleme poate fi prezentată sub forma diagramei UML
din figura 5.1.

Figura 5.1: Diagrama de clase a aplicaţiei cu persoane

5.3 Polimorfismul
Polimorfismul se referă la faptul că atunci când se apelează o metodă
asupra unui obiect care la prima vedere pare de un tip mai generic, de fapt
se apelează metoda corespunzătoare clasei efective a obiectului.
Acest lucru este posibil deoarece ı̂n Java se foloseşte apelul dinamic al
metodelor.
Atunci când se apelează o metodă asupra unui obiect, se parcurg următorii
paşi:

• În lista de metode ale clasei se identifică acele metode care se potrivesc
ca nume cu metoda apelată.
74 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

• Între aceste metode se caută o metodă pentru care parametrii acesteia


să se potrivească cu parametri metodei apelate (overloading resolution).
Dacă nu se gaseşte o astfel de metodă atunci se generează o eroare.

• Dacă avem de-a face cu o metodă privată, statică, finală sau un con-
structor, atunci compilatorul ştie exact care este metoda care trebuie
apelată. În acest caz, avem de-a face cu legarea statică la momentul
rulării.

• Dacă nu suntem ı̂n una din aceste situaţii, atunci metoda apelată se
alege la momentul rulării de către maşina virtuală Java. În primul rând
se caută o metodă ı̂n clasa curentă, după care ı̂n clasa ei de bază, şi
tot aşa până când se găseşte o clasă ı̂n care metoda este implementată.
De fapt, ı̂n realitate lucrurile nu se ı̂ntâmplă chiar aşa, ci mai degrabă,
pentru fiecare clasă se construieşte câte un tabel cu metodele care se
pot apela din clasa respectivă şi doar se face o căutare ı̂n acea tabelă.

De exemplu, pentru clasa Profesor o tabelă de metode ar putea arăta


astfel:

PROFESOR
getNume() => Persoana.getNume()
setNume() => Persoana.setNume()
getPrenume() => Persoana.getPrenume()
setPrenume() => Persoana.setPrenume()
getGradDidactic() => Profesor.getGradDidactic()
setGradDidactic() => Profesor.setGradDidactic()
toString() => Profesor.toString()

Dacă se apelează o metodă polimorfic, atunci se parcurg următorii paşi:

• În primul rând maşina virtuală obţine tabela cu metode corespunzătoare


tipului actual al obiectului.

• Maşina virtuală parcurge lista cu metode şi identifică metoda core-


spunzătoare.

• În final este rulată metoda.

Observaţie: Atunci când suprascriem ı̂ntr-o clasă derivată o metodă care


a fost declarată ı̂ntr-o clasă de bază, această nouă metodă trebuie să aibă un
nivel de vizibilitate cel puţin cât era ı̂n clasa de bază, altfel se obţine eroare.
5.4. METODE ŞI CLASE FINALE 75

5.4 Metode şi clase finale


Atunci când vrem ca o clasă să nu mai poată fi derivată, putem preciza
acest lucru compilatorului prin folosirea modificatorului final, ca ı̂n exemplul
următor:

public final class Utils


{
...
}

Pentru a nu mai permite suprascrierea unei metode ı̂ntr-o clasă derivată,


putem marca acea metodă ca metodă finală cu ajutorul modificatorului final.
Dacă programatorul ı̂ncearcă suprascrierea metodei, atunci compilatorul va
genera o eroare de compilare.

5.5 Operaţia de casting


Se poate să creăm obiecte care să fie reţinute printr-o referinţă la un tip
de bază al lor şi să fie de un tip derivat. Următoarele instrucţiuni sunt valide:

Persoana s=new Student("Ion","Ion",true);


s.setNume("Ionescu");
//urmatoarea instructiune nu functioneaza pentru ca nu putem apela
//asupra obiectului decat metode ale clasei Persoana
//s.setBursier(false);

Şi ı̂n Java, la fel ca ı̂n toate limbajele moderne de programare este supor-
tată operaţia de cast.
Putem avea nevoie de operaţia de ea, de exemplu, atunci când avem un
şir de obiecte de un tip mai generic şi vrem să folosim anumite facilităţi par-
ticulare ale unui obiect care este de un tip mai specializat. Iată un exemplu:

for(int i=0;i<p.length;i++)
if(p[i] instanceof Profesor)
{
Profesor prof=(Profesor)p[i];
prof.setGradDidactic("profesor universitar");
}

Cu ajutorul operatorului instanceof am verificat dacă obiectul p[i] este


de tipul Profesor, şi dacă este am reţinut acel obiect ı̂ntr-un obiect de tip
76 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

profesor. Acest lucru a fost posibil deoarece am făcut o operaţie de cast.


În general nu este bine să scriem programe care să folosească cast-uri, dacă
acest lucru nu este neapărat necesar.
Observaţie: Chiar dacă un obiect p[i] este de tipul efectiv Profesor,
asupra lui nu se pot apela decât metodele corespunzătoare tipului şirului. A
fost nevoie de cast pentru a putea apela o metodă specifică subclasei.

5.6 Clase abstracte


Cu cât aplicaţiile noastre devin mai complexe cu atât vom remarca că şi
numărul de nivele la care avem moştenire se poate mări. La un moment dat
vom ajunge la clase care sunt atât de generice, ı̂ncât e şi greu să implementăm
anumite funcţionalităţi pentru ele.
Cel mai bine vom ı̂nţelege necesitatea utilizării claselor abstracte printr-
un exemplu. Să considerăm următoarea problemă pe care dorim să o re-
zolvăm:
Exemplul 2 Se citesc de la tastatură informaţii despre n figuri ı̂n plan.
Fiecare figură geometrică are o culoare. Figurile geometrice sunt de 2 tipuri:
cercuri şi dreptunghiuri. Pentru un cerc, ı̂n plus faţă de culoare mai cunoaştem
şi raza lui, iar pentru un dreptunghi mai cunoaştem lungimea şi lăţimea. Se
doreşte ordonarea celor n figuri geometrice crescător după arie şi afişarea lor
pe ecran.
În primul rând definim o clasă Figura care va arăta astfel:
public abstract class Figura
{
private String culoare;

public Figura(String culoare)


{
this.culoare=culoare;
}

public String getCuloare()


{
return culoare;
}

public void setCuloare(String culoare)


{
this.culoare = culoare;
5.6. CLASE ABSTRACTE 77

public abstract double aria();

public abstract double perimetru();


}
Observăm că această clasă este abstractă şi că ea are două metode ab-
stracte, cele care calculează aria şi respectiv perimetrul unei figuri geometrice.
O clasă abstractă este o clasă care nu poate fi instanţiată ı̂n mod direct.
O metodă abstractă a unei clase este o metodă care are sens pentru acea
clasă dar a cărei implementare nu poate fi oferită la momentul respectiv.
O clasă care are o metodă abstractă automat trebuie să fie abstractă. Pe
de altă parte, putem avea clase abstracte care nu au nici o metodă abstractă.
Prima afirmaţie este foarte naturală, deoarece ar fi greu de imaginat să
putem crea instanţe ale unei clase care are nişte metode neimplementate. În
situaţia ı̂n care programatorul ar apela acele metode compilatorul nu ar ştii
ce trebuie să facă.
În cele ce urmează ne definim clasele Cerc şi Dreptunghi care sunt
derivate din clasa Figura. Ele arată astfel:
public class Cerc extends Figura
{
private double raza;

public Cerc(String culoare, double raza)


{
//apel constructor din clasa de baza
super(culoare);
this.raza=raza;
}

public double getRaza()


{
return raza;
}

public void setRaza(double raza)


{
this.raza = raza;
}
78 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

public double aria()


{
return Math.PI*Math.pow(raza, 2);
}

public double perimetru()


{
return 2*Math.PI*raza;
}

public String toString()


{
return "Cerc cu raza "+raza+" si de culoarea "+super.getCuloare();
}
}

public class Dreptunghi extends Figura


{
private double lungime, latime;

public Dreptunghi(String culoare, double lungime, double latime)


{
super(culoare);
this.lungime = lungime;
this.latime = latime;
}

public double getLungime()


{
return lungime;
}

public void setLungime(double lungime)


{
this.lungime = lungime;
}

public double getLatime()


{
return latime;
5.6. CLASE ABSTRACTE 79

public void setLatime(double latime)


{
this.latime = latime;
}

public double aria()


{
return lungime*latime;
}

public double perimetru()


{
return 2*(lungime+latime);
}

public String toString()


{
return "Dreptunghi de lungime "+lungime+", latime de "+
latime+"si culoare: "+super.getCuloare();
}
}
Observăm că ı̂n exemplul de mai sus clasele prezentate implementează
metodele aria() şi perimetru() care erau declarate abstracte ı̂n clasa Figura.
Dacă măcar una din cele două metode nu era implementată ar fi trebuit ca
clasa care nu o implementa să fie abstractă (conform regulii care zice că o
clasă care are metode abstracte este obligatoriu abstractă).
Programul care foloseşte cele 3 clase reprezentate anterior este prezentat
ı̂n cele ce urmează:
import java.util.Scanner;

public class TestFiguri


{
public static void main(String[] args)
{
Scanner s=new Scanner(System.in);

System.out.print("Numarul de figuri geometrice: ");


int n=Integer.parseInt(s.nextLine());
80 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

Figura[] f=new Figura[n];

for(int i=0;i<n;i++)
{
System.out.print("Tipul figurii? (c/d)");
if(s.nextLine().charAt(0)==’c’)
{
System.out.print("Culoarea: ");
String culoare=s.nextLine();
System.out.print("Raza: ");
double raza=Integer.parseInt(s.nextLine());
f[i]=new Cerc(culoare, raza);
}
else
{
System.out.print("Culoarea: ");
String culoare=s.nextLine();
System.out.print("Lungime: ");
double lungime=Integer.parseInt(s.nextLine());
System.out.print("Latime: ");
double latime=Integer.parseInt(s.nextLine());
f[i]=new Dreptunghi(culoare, lungime, latime);
}
}

sortare(f);

afisare(f);
}

private static void sortare(Figura[] f)


{
for(int i=0;i<f.length-1;i++)
for(int j=i+1;j<f.length;j++)
if(f[i].aria()>f[j].aria())
{
Figura aux=f[i];
f[i]=f[j];
f[j]=aux;
}
5.6. CLASE ABSTRACTE 81

private static void afisare(Figura[] f)


{
for(Figura fig:f)
System.out.println(fig);
}
}
Putem remarca că ı̂n acest exemplu metoda aria() este tratată polimorfic,
deoarece ı̂n metoda main() ea este utilizată ı̂ntr-un aşa mod ı̂ncât nu se poate
spune la momentul compilării care metodă se va apela.
Soluţia acestei probleme se poate reprezenta cu ajutorul diagramei de
clase reprezentată ı̂n figura 5.2.

Figura 5.2: Soluţia problemei cu figuri geometrice

S-ar putea ca unii cititori să se ı̂ntrebe de ce este necesar ca metodele


aria() şi perimetru() să fie abstracte.
Acest lucru este destul de simplu. Faptul că ele sunt definite ı̂n clasa
Figura ı̂mi permite să le apelez uniform pentru toate obiectele din şirul de
82 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

figuri geometrice. Totuşi, nu puteam să le definesc ı̂n cazul clasei Figura
deoarece nu ştiu cum sunt implementate. Din acest motiv, metodele au fost
declarate ca abstracte.

5.7 Clasa Object


În Java, chiar dacă acest lucru nu este vizibil ı̂n mod direct, toate clasele
sunt derivate din clasa Object. Acest lucru ı̂nseamnă că ele au deja o anumită
funcţionalitate deja definită, şi că au, de asemenea, anumite metode pe care
le pot suprascrie.
Metodele clasei Object sunt sintetizate ı̂n diagrama de clase din figura
5.3.

Figura 5.3: Metodele clasei Object

Faptul că toate obiectele sunt derivate din clasa Object ı̂nseamnă că
următorul cod este perfect valabil:

String s="test";
Object o=s;
s=(String)o;

Se poate observa că pentru a transforma o referinţă la un tip mai general,


către o referinţă la un tip specializat, trebuie să facem o operaţie de cast.
Faptul că toate clasele sunt derivate direct sau indirect din clasa Object
ne permite să scriem clase pentru colecţii care să reţină elemente de tip
referinţă de orice tip. Această modalitate de lucru a fost folosită foarte mult
ı̂nainte de apariţia lucrului cu tipuri generice ı̂n Java.
5.7. CLASA OBJECT 83

5.7.1 Metoda toString()


Metoda toString() poate fi implementată pentru o clasă pentru a putea
obţine cu ajutorul ei o reprezentare folosind un şir de caractere a unui obiect.
Această reprezentare poate fi foarte uşor folosită pentru realizarea operaţiei
de logging.
În general nu este bine să ne bazăm ı̂n interfaţa grafică pe metoda toString()
ci mai degrabă să avem propriile noastre metode care să realizeze afişarea.
Este deja binecunoscut că atunci când pentru un obiect apelăm metoda
println(), ca ı̂n exemplul următor, de fapt pentru afişarea obiectului se foloseşte
metoda toString().

Complex c=new Complex(3,4);


// automat se apeleaza metoda toString()
System.out.println(x);

5.7.2 Metoda equals()


Metoda equals() a clasei Object testează dacă două obiecte au aceeaşi
stare. Câteodată nu are sens să comparăm două obiecte, motiv pentru care
această metodă poate sau nu să fie implementată. Totuşi, dacă avem obiecte
care pot fi comparate pentru egalitate, este important să o scriem.
Este important, de asemenea, să scriem această metodă deoarece, dacă
folosim clase pentru lucrul cu colecţii ı̂n Java, această metodă este folosită
atunci când se caută un anumit element ı̂ntr-o colecţie.
De exemplu, putem scrie metoda equals() pentru clasa Complex ı̂n felul
următor:

class Complex
{
// alte atribute si metode

public boolean equals(Object otherObject)


{
if (this == otherObject)
return true;
if (otherObject == null)
return false;

if (getClass() != otherObject.getClass())
return false;
84 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

Complex other = (Complex) otherObject;


return re==other.re && im==other.im;
}
}

Implementarea metodei equals() este destul de ”tricky” atunci când avem


de-a face cu compararea unor obiecte care fac parte din aceeaşi ierarhie de
obiecte. În anumite situaţii, ı̂n locul apelului metodei getClass() se preferă
folosirea operatorului instanceof.
Pentru ı̂nţelegerea mai bună a modului ı̂n care se lucrează cu metoda
equals() este recomandată studierea ı̂n amănunt a documentaţiei Java.

5.7.3 Metoda hashCode()


Metoda hashCode() returnează un număr ı̂ntreg care este folosit ı̂n situaţia
ı̂n care un obiect este reţinut ı̂ntr-o tabelă de dispersie. Este recomandabil ca,
pe cât posibil, la valori diferite ale conţinutului obiectelor şi valorile returnate
de metodă să fie diferite.
De cele mai multe ori, metoda returnează o valoare bazată pe valorile
câmpurilor care sunt conţinute de clasă.
Iată şi un exemplu:

class Persoana
{
private String nume, prenume;
//alte atribute si metode
public int hashCode()
{
return 3*nume.hashCode()+5*prenume.hashCode();
}
}

Observaţie: Dacă două obiecte prin folosirea metodei equals() sunt egale,
atunci şi metodele lor hashCode() trebuie să returneze aceleaşi valori.

5.7.4 Metoda clone()


Metoda clone() este folosită pentru a crea o copie a unui obiect. Trebuie
ca atunci când realizăm copia unui obiect să creem un nou obiect care nu
partajează absolut nimic cu vechiul obiect.
În primul rând trebuie spus că există situaţii ı̂n care nu vrem ca o instanţă
a unui obiect să poată fi clonată. Dacă suntem ı̂n această situaţie nu trebuie
5.8. IMPLEMENTAREA COLECŢIILOR GENERICE FOLOSIND OBJECT85

să facem nimic, pentru că aceasta este implementarea implicită ı̂n Java. Dacă
totuşı̂ vrem să putem
Dacă obiectul iniţial are numai câmpuri de tipuri primitive atunci este
suficient să copiem câmpurile din vechiul obiect ı̂n cel nou. La fel se ı̂ntâmplă
şi ı̂n cazul câmpurilor de tip String, deoarece această clasă este immutable.
Prezentăm un exemplu ı̂n cele ce urmează:
class Complex implements Cloneable
{
private double re, im;

public Object clone()


{
return new Complex(re, im);
}
}
Totuşi ı̂n unele situaţii nu este bine să facem acest lucru. Acesta ar fi
cazul unei stive, ı̂n care codul ar arătă mai degrabă astfel:
class Stack implements Cloneable
{
private int[] elements;
private int count;

public Object clone()


{
Stack clonedStack=new Stack();
clonedStack.elements=elements.clone();
clonedStack.count=count;
}
}

5.8 Implementarea colecţiilor generice folosind


Object
Datorită faptului că toate clasele ı̂n Java sunt derivate din clasa Object
se poate ca să definim colecţii care să poată fi utilizate pentru a reţine obiecte
de orice tip.
Acest lucru se face prin reţinerea elementelor care alcătuiesc colecţia ı̂n
structuri bazate pe clasa Object.
86 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

În cele ce urmează prezentăm un astfel de exemplu. Clasa Queue este cea
prezentată anterior, ı̂n plus adăugând doar faptul că ea poate reţine obiecte
de orice tip.

package com.mihai.datastructures;

public class EmptyQueueException extends Exception{

package com.mihai.datastructures;
class Node {
private Object value;
private Node next;

Node(Object value)
{
this.value=value;
this.next=null; //putea lipsi
}

Node(Object value, Node next) {


this.value = value;
this.next = next;
}

Object getValue() {
return value;
}

void setValue(Object value) {


this.value = value;
}

Node getNext()
{
return next;
}

void setNext(Node next)


{
5.8. IMPLEMENTAREA COLECŢIILOR GENERICE FOLOSIND OBJECT87

this.next=next;
}
}

package com.mihai.datastructures;
public class Queue {
private Node first, last;

public boolean isEmpty()


{
return first==null;
}

public void push(Object x)


{
Node node=new Node(x);
if(isEmpty())
first=node;
else
last.setNext(node);
last=node;
}

public Object top() throws EmptyQueueException


{
if(isEmpty())
throw new EmptyQueueException();
return first.getValue();
}

public Object pop() throws EmptyQueueException


{
if(isEmpty())
throw new EmptyQueueException();
Object value=first.getValue();
first=first.getNext();
return value;
}

public String toString()


{
88 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

StringBuffer sb=new StringBuffer("[");


Node current=first;
if(!isEmpty())
{
while(current.getNext()!=null)
{
sb.append(current.getValue()+",");
current=current.getNext();
}
sb.append(current.getValue());
}
sb.append("]");
return sb.toString();
}
}

package com.mihai.main;
import java.util.StringTokenizer;

import com.mihai.datastructures.*;

public class TestQueue


{
public static void main(String args[])
{
Queue q=new Queue();

q.push("mihai");
q.push("ioana");
q.push("ana maria");

try
{
System.out.println("In varful cozii: "+q.top());
String str=(String)q.pop();
System.out.println("Continutul cozii: "+q);
}
catch(EmptyQueueException e)
{
System.out.println("Coada goala !!!");
}
5.9. UTILIZAREA COLECŢIILOR GENERICE CU TEMPLATE-URI 89

}
}

În exemplul anterior am văzut cum se poate implementa o colecţie generică


având la bază clasa Object.
Totuşi această abordare are anumite defecte, ceea ce a condus la imple-
mentarea ı̂n versiunile mai noi ale limbajului ale unor astfel de colecţii cu
ajutorul template-urilor. Aceste defecte sunt:

• Se poate ca din greşeală un anumit programator, care nu ştie exact ce


tip au elementele dintr-o astfel de colecţie să introducă elemente de un
alt tip.

• La parcurgerea apelul metodelor care obţin elemente din colecţie, tre-


buie făcute casturi care pot să genereze erori din cauza unor greşeli
prezentate anterior. În plus, acest mecanism de obţinere a elementelor
dintr-o colecţie nu este elegant.

Practic, dacă ne gândim mai bine o să observăm că aceste colecţii nu
oferă siguranţă ı̂n utilizare şi pot produce erori care nu pot fi depistate la
momentul rulării.
Din acest motiv, s-au introdus colecţiile care folosesc template-uri, care
sunt prezentate pe scurt ı̂n secţiunea următoare.

5.9 Utilizarea colecţiilor generice cu template-


uri
Cel mai bine pentru a ı̂nţelege cum se folosesc aceste liste, prezentăm un
exemplu de folosire a lor.

import java.util.*;
public class TestLista {

public static void main(String[] args) {


List<String> lista=new LinkedList<String>();

lista.add("test1");
lista.add("test2");
lista.add("test3");

for(String s:lista)
90 CAPITOLUL 5. MOŞTENIREA ÎN JAVA

System.out.println(s);

System.out.println("-----------------------------");

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

lista.remove("test2");
lista.add("test4");

System.out.println("-----------------------------");

for(int i=0;i<lista.size();i++)
{
String str=lista.get(i);
System.out.println(str);
}

System.out.println("-----------------------------");

System.out.println("Rezultat contains: "+lista.contains("test2"));


}

}
Se poate testa faptul că ı̂n noua listă nu se mai pot introduce elemente
de alte tipuri decât cel declarat. Se poate observa, de asemenea, că nu mai
este nevoie de operaţii de cast pentru a obţine un anumit obiect din colecţie.
Deocamdată nu intrăm ı̂n mai multe amănunte relativ la folosirea colecţiilor
ı̂n Java, urmând a reveni ı̂n capitolul special dedicat colecţiilor Java.

5.9.1 Boxing şi unboxing ı̂n Java


În mod normal ı̂n colecţii nu se pot reţine decât obiecte. Acest lucru era
valabil la versiunile mai vechi de Java. În versiunile mai noi s-a introdus
conceptul de boxing care face posibil următorul cod:
ArrayList<Integer> lista=new ArrayList<Integer>();
5.9. UTILIZAREA COLECŢIILOR GENERICE CU TEMPLATE-URI 91

lista.add(3);
lista.add(4);
for(int x:lista)
System.out.print(x+" ");

Acest cod este posibil deoarece atunci când este apelată metoda add()
parametrul transmis este automat convertit la un obiect de tip Integer (box-
ing).
Exact opusul se ı̂ntâmplă atunci când realizăm afişarea, ı̂n acest caz
putând afirma că avem de-a face cu conceptul de unboxing.
Cel mai bine se poate vedea acest lucru pe următorul cod:

Integer intObject = 100;


//echivalent cu apelul Integer intObject = new Integer(100);

int x = intObject;
//echivalent cu apelul int x = intObject.getValue();

Observaţie: La fel ca şi clasa String, clasa Integer este o clasă immutable
(nu ı̂şi poate schimba niciodată valoarea).
92 CAPITOLUL 5. MOŞTENIREA ÎN JAVA
CAPITOLUL 6

Tratarea excepţiilor ı̂n Java

Unul din cele mai importante lucruri pentru orice programator care foloseşte
programarea obiect orientată este ı̂nţelegerea mecanismului care stă la baza
lucrului cu excepţii.
Evident, ar fi de dorit ca ı̂n practică să nu apară excepţii, dar după cum
fiecare dintre noi a observat, acest lucru este imposibil. Astfel, este important
să le tratăm corespunzător.
Mecanismul de lucru cu excepţii ı̂n Java seamănă foarte mult cu mecan-
ismul pe care deja ı̂l cunoaştem din C/C++.
În momentul ı̂n care apare o excepţie, cel care utilizează programul sau
programatorul trebuie să fie ı̂nştiinţat pentru a o putea trata corespunzător.
Astfel, aplicaţia trebuie să fie capabilă să facă unul din următoarele două
lucruri:

• Să termine aplicaţia ı̂ntr-un mod civilizat (cu salvarea eventual a lu-
crurilor care trebuie salvate, ı̂nchiderea conexiunilor care se folosesc ı̂n
aplicaţie)

• Să permită utilizatorului să se ı̂ntoarcă la o stare din care să poată
continua aplicaţia (de exemplu, dacă o citire nu reuşeşte, trebuie să mi
se permită să reiau acest pas sau să tratez altfel această situaţie).

Teoretic, chiar dacă acest lucru este mult mai complicat şi mai puţin ele-
gant, se pot ı̂ncerca şi alte abordări ale tratării excepţiilor decât cele folosite
ı̂n limbajul Java. Astfel, se poate de exemplu, ca fiecare metodă să returneze
un anumit cod, care să ne spună dacă metoda a reuşit sau nu.

93
94 CAPITOLUL 6. TRATAREA EXCEPŢIILOR ÎN JAVA

Totuşi, pe termen lung, această abordare este greu de utilizat pentru


programator. Ea mai are un mare dezavantaj, şi anume faptul că are ca
efect amestecarea codului care prezintă cazul de dorit ı̂n care totul merge
bine cu codul care se ocupă de tratarea erorii.
Mai mult, unele erori ar fi greu de tratat folosind această metodă.
Din această cauză, vom vedea că limbajul Java acordă o mare importanţă
tratării erorilor. Astfel, avem o ı̂ntreagă ierarhie de clase destinată acestui
scop.

6.1 Clasificarea excepţiilor


Excepţiile care pot fi ı̂ntâlnite ı̂ntr-un program Java sunt de două feluri:

• unchecked exceptions - sunt acele excepţii care apar la momentul rulării


dar pe care programatorul nu este obligat să le ”vâneze” (să le trateze
ı̂n mod explicit). Exemple de astfel de exceptii sunt: ArrayIndexOut-
OfBoundsException, DivisionByZeroException etc.

• checked exceptions - exceptiile pe care programatorul este obligat să le


ia ı̂n considerare (el trebuie să fie conştient de faptul că acestea pot
apărea ı̂ntr-un anumit loc). Astfel de excepţii sunt: FileNotFoundEx-
ception, IOException etc.

Cele mai importante clase care sunt folosite ı̂n Java pentru tratarea
excepţiilor sunt Throwable, Exception, Error şi RuntimeException. Ele
se află ı̂n relaţiile prezentate ı̂n figura 6.1.

Figura 6.1: Diagrama claselor pentru tratarea excepţiilor


6.2. CREAREA CLASELOR EXCEPŢIE 95

Toate excepţiile sunt derivate din clasa Throwable care se comportă pen-
tru excepţii in modul ı̂n care Object se comportă pentru toate clasele. Avem
două tipuri derivate din Throwable şi anume Error (erori care apar din cauza
maşinii virtuale şi care ı̂n principiu sunt foarte greu de tratat; tot ceea ce
se poate face este să ı̂ncheiem programul ı̂ntr-un mod elegant) şi Exception
(erori care apar din cauze care nu ţin de implementarea maşinii virtuale).
Un caz mai deosebit de erori sunt cele de tipul RuntimeException care
sunt erorile unchecked de tipul Exception. Aceste erori sunt acele erori care
nu ţin de maşina virtuală ci mai degrabă de programator şi care dacă ar trebui
tratate de fiecare dată ar ı̂ngreuna foarte mult scrierea codului. Astfel,ar fi
greu dacă de fiecare dată când folosim un şir sau o matrice am verifica dacă
nu cumva se foloseşte un element din afara matricii.
Totuşi, ı̂n aceste situaţii se poate trata o eroare de acest tip.
Erorile de tip Exception care nu sunt de tipul RuntimeException sunt
erori care trebuie neapărat tratate ı̂ntr-un fel de programator (checked er-
rors).

6.2 Crearea claselor excepţie


Pentru a crea o nouă excepţie ı̂n Java, trebuie să scriem o clasă care este
derivată direct sau indirect din clasa Exception.
De exemplu, pentru a crea o excepţie de tipul checked exception putem
scrie următorul cod:

class MatriciIncompatibileException extends Exception


{
public MatriciIncompatibileException()
{
}

public MatriciIncompatibileException(String message)


{
super(message);
}
}

Noua clasă pentru tratarea unei excepţii poate fi derivată şi dintr-o altă
clasă de tipul excepţie.
Dacă dorim crearea unei excepţii unchecked derivăm clasa excepţie din
RuntimeException sau dintr-o clasă derivată din aceasta:
96 CAPITOLUL 6. TRATAREA EXCEPŢIILOR ÎN JAVA

class StackEmptyException extends RuntimeException


{
public StackEmptyException()
{
}
public StackEmptyException(String message)
{
super(message);
}
}

6.3 Aruncarea excepţiilor


Pentru a arunca o excepţie procedăm ı̂n felul următor folosim cuvântul
cheie throw după care trebuie să folosim un obiect de tip excepţie:
public int pop()
{
if(isEmpty())
throw new StackEmptyException();
//codul pentru eliminarea unui element din stiva
}
Observaţie: Dacă avem de-a face cu o excepţie de tipul ”checked excep-
tion” trebuie să specificăm că această metodă aruncă o astfel de excepţie:
public static Matrice adunare(Matrice m1, Matrice m2)
throws MatriciIncompatibileException
{
if ((m1.noLines()!=m2.noLines()) ||
((m1.noCols()!=m2.noCols()))
throw new MatriciIncompatibileException();

//continua codul pentru aceasta metoda


}
Dacă o metodă poate arunca mai multe excepţii de tipul ”checked” atunci
acestea trebuie scrise toate ı̂n secţiunea throws separate prin virgulă.

6.4 Prinderea excepţiilor


Prinderea excepţiilor ı̂n Java respectă următoarea sintaxă:
6.4. PRINDEREA EXCEPŢIILOR 97

try {
instr1;
instr2;
....
}
catch(Exception1 e){
//tratare exceptie 1
}
catch(Exception2 e){
//tratare exceptie 2
}
finally{
//aici am cod care se executa mereu
//indiferent daca a aparut exceptie sau nu
}

Dacă ı̂ntre instrucţiunile din blocul try nu apare nici o eroare atunci se
execută toate aceste instrucţiuni după care se execută toate instrucţiunile
din blocul finally.
Dacă la un moment dat apare eroare când se execută o instrucţiune ı̂n
blocul try atunci execuţia instrucţiunilor din acel bloc se opreşte şi se execută
instrucţiunile doar din primul catch care prinde eroarea potrivită, după care
se execută instrucţiunile din blocul finally.
După cum se poate observa se poate să avem mai multe blocuri catch
dar un singur bloc finally. Trebuie remarcat, de asemenea, că blocul finally
poate lipsi.
În momentul ı̂n care scriem cod ı̂n care la un moment dat poate apărea o
excepţie de tipul ”checked” putem să rezolvăm problema ı̂n unul din următoarele
moduri:

• Dacă nu ştim cum să tratăm eroarea şi vrem ca ea să fie tratată ı̂n
altă parte, putem să aruncăm eroarea mai departe. Pentru acest lucru
trebuie să specificăm că acea metodă poate arunca eroarea respectiva
ı̂n clauza throws.

• Dacă nu ştim cum să tratăm eroarea dar vrem să aruncăm mai departe
o altă eroare, putem folosi un bloc try-catch ı̂n care pe ramura de catch
corespunzătoare excepţiei să aruncăm o altă excepţie.

• Putem trata excepţia direct ı̂n secţiunea catch corespunzătoare.


98 CAPITOLUL 6. TRATAREA EXCEPŢIILOR ÎN JAVA

6.5 Indicaţii referitoare la utilizarea excepţiilor


Atunci când se lucrează cu tratarea excepţiilor este bine să se urmeze
regulile prezentate ı̂n continuare:

• Tratarea excepţiilor nu trebuie să ı̂nlocuiască testele care verifică dacă


se poate efectua o operaţie (motive de eficienţă, ı̂n primul rând)

• Nu trebuie să separăm codul ı̂ntr-o serie de blocuri try-catch atunci


când este mai bine ca ele să fie ı̂n acelaşi bloc try.

• Nu trebuie să se folosească numai excepţii de tipul RuntimeException.


Trebuie de fiecare dată să identificăm excepţia corectă şi să derivăm
din acea excepţie.

• Nu trebuie să ı̂năbuşim tratarea unei excepţii prin prinderea ı̂ntr-un


bloc catch care tratează direct o excepţie de tipul Exception.

• Nu toate excepţiile trebuie tratate ı̂n blocuri catch. În unele situaţii
este de dorit să se arunce mai departe acea excepţie.

6.6 Exemple

You might also like