Sunteți pe pagina 1din 45

1.

COMPENDIU JAVA

Prezenta lucrare presupune că cititorul deţine un nivel minimal de cunoş-


tinţe despre programarea în limbajul Java. Există deja suficientă literatură
legată de programarea în Java [1, 5, 67, 4, 34].
În acest capitol venim în sprijinul "aducerii aminte" prin mai multe
exemple de programe Java. Vom prezenta mai întâi câteva exemple simple.
Apoi facem un sumar al limbajului Java prin comparaţii cu C++. Prezentăm
apoi câteva aspecte Java legate de tratarea excepţiilor şi de interfeţe. Capitolul
se va încheia cu alte câteva exemple simple de programe Java.

1.1. Primele exemple


Specificul Java oferă două tipuri de programe:
• aplicaţii de sine stătătoare, numite în terminologia Java standalone;
• programe activabile prin intermediul navigatoarelor Web, numite
applet-uri.

1.1.1. Programul standalone Salut

Fişierul sursă poartă numele Salut.java şi este prezentat în progra-


mul 1.1.

public class Salut{

public static void main(String a[]) {


System.out.println("Salut");
} // Salut.main

} // Salut

Programul 1.1. Salut.java

Mai întâi câteva precizări, care reprezintă de fapt şi primele reguli Java.
Un program Java constă din definirea şi instanţierea unei clase. Numele textului
sursă este format din numele clasei urmat de sufixul .java. Unele programe
pot conţine în acelaşi text sursă mai multe clase, dar atunci numai una dintre ele
va fi vizibilă în exterior, celelalte fiind numai de uz intern.
17
Un program standalone trebuie să conţină metoda statică main. Ea are
ca parametru un tablou având ca şi elemente şiruri de caractere. Fiecare element
al tabloului conţine un argument transmis în linia de comandă ce lansează
programul în execuţie.
Tipărirea este executată de către metoda println a obiectului out din
clasa System.
Compilatorul Java poartă numele de javac.
Lansarea compilării programului se face cu comanda:
javac Salut.java

Rezultatul compilării este depus în fişierul Salut.class, care


conţine codul JVM echivalent.
Interpretorul standard Java poartă numele java. El preia codul JVM din
Salut.class şi-l execută (interpretativ).
Execuţia interpretativă se lansează prin comanda:
java Salut

Ca efect, pe ecran se va afişa, pe linie nouă:


Salut

1.1.2. Applet-ul SalutAp


Exemplul din secţiunea precedentă, scris ca un applet, este prezentat în
programul 1.2. Textul sursă conţine şi patru linii de comentarii, scrise ca în
C++.

import java.applet.*;
import java.awt.*;

public class SalutAp extends Applet {


public void paint(Graphics g) {
g.drawString("Salut",10,50);
} // SalutAp.paint
} // SalutAp

Programul 1.2. SalutAp.java

Mai întâi se declară folosirea de metode ale pachetelor applet şi awt.


Apoi se indică faptul că clasa SalutAp extinde (este o subclasă pentru)
clasa Applet.

18
Scrierea textului se face prin intermediul metodei paint. Aceasta
realizează rescrierea metodei paint din java.awt.Component, având
rolul de a "picta" un obiect grafic g. Concret, este vorba de scrierea unui string
începând de la pozitia (x= 10 pixeli spre dreapta, y = 50 pixeli în jos).
Compilarea sursei se face folosind aceeaşi comandă de compilare:
javac SalutAp.java

care generează codul de octeţi echivalent sursei în fişierul SalutAp.class.


Pentru lansarea în execuţie a acestui applet este nevoie de un navigator
Web: Netscape, Mozilla, Konqueror, InternetExplorer etc.
Aceste navigatoare operează peste o platformă grafică oferită de către sistemul
de operare: Windows [100] (platformele Microsoft), respectiv X-window [58]
(platformele Unix). În funcţie de platformă, Pachetul Java oferă pentru testarea
applet-urilor, interpretorul appletviewer care este, de fapt, un navigator
Web specializat.
În textul sursă HTML [59, 85] transmis navigatorului Web trebuie
inserată sursa din programul 1.3.

<APPLET code="SalutAp.class" width="500" height="100"> </APPLET>

Programul 1.3. SalutAp.html

Numele fişierului .java trebuie să coincidă cu numele clasei, însă


numele fişierului HTML poate fi diferit (noi l-am luat acelaşi din comoditate).
Prin acest text, care este un tag HTML, se cere navigatorului să-şi
încarce codul JVM din fişierul SalutAp.class şi să-l execute (interpre-
tativ).

Figura 1.1. Imaginea SalutAp prin Mozilla

19
În fig. 1.1 este prezentat rezultatul execuţiei folosind navigatorul
Mozilla.
Dacă se doreşte utilizarea interpretorului standard Java appletviewer, se
va lansa comanda:
appletviewer SalutAp.html

Imaginea ferestrei este prezentată în fig. 1.2

Figura 1.2. Imaginea SalutAp prin appletviewer

1.1.3. Un program ce poate rula


alternativ standalone sau applet
Pare cel puţin interesant să vedem un program care să poată rula, după
preferinţă, ori ca aplicaţie standalone ori ca applet. Unul dintre cele mai simple
programe de acest tip este programul 1.4.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class PrimCompus extends Applet {


public static void main(String a[]) {
PrimCompus oPrimCompus = new PrimCompus();
Frame oFrame = new Frame("Fereastra standalone");
WindowListener l=new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
};
oFrame.addWindowListener(l);
oFrame.setSize(250,350);
oFrame.add("Center", oPrimCompus);
oFrame.show();
oPrimCompus.init();
} // PrimCompus.main

20
public void init() {
} // PrimCompus.init

public void paint(Graphics g) {


g.drawString("Un sir in fereastra",50,60);
} // PrimCompus.paint

} // PrimCompus

Programul 1.4. PrimCompus.java

Clasa PrimCompus extinde clasa Applet spre a se putea comporta ca


şi un applet. De asemenea, în cadrul ei se defineşte metoda main, care este
invocată la lansarea ca şi aplicaţie standalone.
Să urmărim mai întâi comportarea ca şi applet. În momentul lansării
dintr-un navigator, acesta (navigatorul) caută metoda init pe care o lansează
în execuţie. În exemplul nostru metoda init este vidă. În continuare (după
cum am arătat şi la exemplul SalutAp), dacă în cadrul clasei este definită
metoda paint, atunci se lansează în execuţie aceasta. În cazul nostru va afişa
un string în fereastră.
Pentru lansarea din navigator, este suficient ca documentul html să aibă
conţinutul din programul 1.5.

<html>
<title>Fereastra applet </title>
<Applet code="PrimCompus.class" width=250 height=350> </Applet>
</html>

Programul 1.5. PrimCompus.html

Spre a se deosebi de aplicaţia standalone, am precizat şi titlul ferestrei.


Execuţia programului din Mozilla este ilustrată în fig. 1.3

Figura 1.3. Imaginea PrimCompus prin Mozilla

21
Pentru ca programul să poată fi rulat, cu (aproape) acelaşi efect, în cadrul
metodei main trebuie executate câteva acţiuni, de altfel specifice interfeţelor
grafice de tip standalone.
Mai întâi am definit şi construit obiectul oPrimCompus de tipul clasei.
Apoi am definit un obiect oFrame de tip Frame (tipul clasic de
fereastră din pachetul awt). Am fixat titlul acestei ferestre şi dimensiunile ei (în
pixeli).
Am construit obiectul l, de tip WindowListener, definit printr-o clasă
internă, anonimă, de tip WindowsAdapter. Rolul acestui obiect este de a
intercepta evenimentul de apăsare a butonului de distrugere a ferestrei şi de a
termina apoi programul. (Ca rezultat al compilării, din această clasă anonimă se
va crea fişierul PrimCompus$1.class)
Am adăugat, în poziţia centrală a obiectului oFrame obiectul
oPrimCompus şi am cerut ferestrei să devină vizibilă.
În sfârşit, am lansat metoda init a obiectului oPrimCompus, după
care comportarea va fi ca şi la applet: va căuta metoda paint şi va afişa string-
ul în fereastră. Rezultatul execuţiei va fi cel din fig. 1.4.

Figura 1.4. Imaginea PrimCompus ca aplicaţie standalone

1.1.4. Acces la argumentele liniei de comandă


Un program standalone constă din una sau mai multe definiţii de clase,
fiecare dintre acestea aflându-se în propriul fişier *.class. Una dintre aceste
clase trebuie să conţină metoda main().
Pentru a lansa programul, trebuie lansat interpretorul java cu numele
clasei care conţine metoda main. Aceasta are prototipul:
public static void main ( String argv[] ) .

Interpretorul Java execută codul până când metoda main execută un


apel System.exit sau până la sfârşitul codului metodei (atingerea acoladei
de închidere a metodei). Din acel moment, se aşteaptă terminarea tuturor

22
thread-urilor (firelor de execuţie) care încă nu s-au terminat şi apoi execuţia se
termină.
În momentul lansării în execuţie, parametrii transmişi prin linia de
comandă sunt depuşi în vectorul de string-uri notat mai sus prin argv. De aici
utilizatorul le poate accesa. De exemplu, programul 1.6 afişează toate aceste
argumente (tabloul de string-uri din linia de comandă l-am notat cu a).

public class Echo {


public static void main(String a[]){
System.out.println("Urmeaza cele "+a.length+" argumente:");
for(int i=0;i<a.length;i++)
System.out.println(a[i]);
} // Echo.main
} // Echo

Programul 1.6. Echo.java

Trebuie observate diferenţele faţă de C. În C, funcţia main are două


argumente, un întreg şi un tablou de pointeri, notate de obicei de obicei argc
argv, indicând numărul de argumente şi pointerii la string-urile transmise ca
argumente. În Java apare doar tabloul de string-uri, notat de obicei argv,
pentru că numărul de elemente este dat de argv.length, câmpul ce indică
pentru un obiect tablou numărul de elemente. Tot spre deosebire de C, după
cum se poate vedea dacă se rulează programul, numele comenzii nu este
considerat argument al liniei de comandă.

Accesul la variabilele de mediu


Java nu permite accesul la variabilele de mediu ale sistemului de operare
sub care se rulează. Această interdicţie este impusă deoarece aceste
variabile depind de sistemul concret, deci nu asigură independenţa de
platformă. Mediul Java oferă o modalitate proprie, independentă de
platformă, de a defini o listă de proprietăţi sistem. Cititorul interesat
poate să consulte [44, 10] în acest scop.

1.2. Scurtă prezentare a limbajului Java


1.2.1. Java comparat cu C şi C++
După cum se ştie, Java s-a născut din C şi C++. Este deci normal ca
aceste limbaje să aibă trăsături comune, precum şi diferenţe. Deoarece nu avem
în intenţie o prezentare exhaustivă a limbajului Java, preferăm o prezentare a sa
relativă la aceste limbaje. Lista similarităţilor şi a deosebirilor este actualizată

23
cu fiecare nouă distribuţie Java. De exemplu, lucrările [44, 4] prezintă aceste
elemente. Noi vom prezenta deosebirile şi asemănările în ipoteza că cititorul
cunoaşte, la un nivel minimal, limbajele C++ şi (eventual) Java.
Java nu acceptă mecanismul typedef, nici noţiunea de preprocesor. În
consecinţă, construcţiile #define, #if #include ş.a. nu sunt admise.
Dispare deci noţiunea de fişier header. De asemenea, Java nu suportă tipul de
date enum. În locul lui lui define, în Java se pot declara constante cu ajutorul
cuvântului cheie final. Aceste constante pot fi folosite ca înlocuitori pentru
tipul enum.
Ca şi C++, Java operează cu tipul class, dar nu suportă nici tipul
struct, nici union.
Programele standalone C++ cer o funcţie numită main. Pe lângă
aceasta, în programele C++ pot să apară numeroase alte funcţii, atât
independente, cât şi funcţii membre ale claselor (metode). În Java, funcţiile sunt
numai membre ale claselor (metode), deci nu există funcţii independente,
neaparţinând claselor. De asemenea, Java nu admite funcţii globale şi date
globale.
În Java, toate clasele sunt, în ultimă instanţă, descendente din clasa
Object. În schimb, în C++ este posibilă crearea unor arbori de clase fără nici
o legătură între ei.
Toate definiţiile de funcţii (metode) în Java sunt conţinute în interiorul
claselor cărora le aparţin. În C++ este permisă definirea lor în afara claselor. De
asemenea, în C++ este permisă definirea explicită de funcţii inline, în timp
ce în Java această opţiune este lăsată pe seama interpretorului JVM.
Atât C++ cât şi Java permit definirea de variabile şi metode clasă
(static), invocabile independent de existenţa sau nu a unor instanţieri ale
clasei respective.
Java permite utilizarea conceptului interface. Prin intermediul ei se
pot defini prototipuri de metode şi eventual declararea de constante. Într-o
interfaţă nu sunt permise definirea de metode, nici declararea de variabile
membru. C++ nu suportă conceptul de interfaţă. Atât Java, cât şi C++ admit
clase abstracte. Vom reveni asupra acestui concept.
Java nu permite moştenirea multiplă. Într-o oarecare măsură, utilizarea
interfeţelor suplineşte în Java această absenţă. Moştenirea simplă este,
conceptual la fel în Java ca şi în C++, însă în partea de implementare diferenţele
sunt mari.
Java nu acceptă instrucţiunea goto, deşi goto este cuvânt rezervat (dar
nefolosit)! În schimb, Java permite break şi continue, atât în forma
existentă în C++, cât şi extinderile break şi continue etichetate. Acestea
din urmă nu sunt suportate de C++. În forma simplă ele comandă părăsirea,
respectiv reiterarea celui mai interior corp al unui ciclu. Cele etichetate
acţionează în acelaşi mod, dar asupra ciclurilor (mai exterioare) marcate printr-o
24
etichetă. Aceste extinderi pentru break şi continue permit înlocuirea, în
toate cazurile de structuri de control fireşti, a instrucţiunii goto [16].
Java nu permite suprapunerea operatorilor. De asemenea, nu permite
conversia implicită (automată) de tip.
Spre deosebire de C++, Java are obiecte de tip String, obiecte
nemodificabile. Obiectele Java de tip StringBuffer permit şi modificări,
precum şi conversii facile între cele două tipuri de string-uri.
Tablourile în Java sunt obiecte. Într-un tablou Java, elementele lui sunt
indexate de la 0 la length-1. length este o variabilă a unui obiect tablou,
indicând lungimea maximă a domeniului de indexare. Elementele unui tablou
sunt (evident) toate de acelaşi tip, care poate fi orice, inclusiv un alt tablou.
Astfel se implementează tablourile multidimensionale. Java permite operaţia de
atribuire la nivel de tablou. Dimensiunea unui tablou se fixează dinamic, nu
static ca în C++. Spre deosebire de C++, în Java se controlează, la orice
invocare, limitele indicilor, evitându-se astfel indexarea în afara domeniului.
Java nu permite operarea cu pointeri, implicit nu există nici aritmetica
de pointeri. Automat, a dispărut şi echivalenţa dintre pointeri, nume de tablouri
etc. Natural, absenţa pointerilor implică absenţa operatorului de selecţie din
pointer "->".
Operatorul rezoluţie scope "::" din C++ nu este folosit în Java. Toate
selecţiile se fac cu operatorul ".".
Pentru expresiile condiţionale Java s-a introdus tipul boolean, inexistent
în C++. A dispărut astfel statutul de TRUE pentru o valoare nenulă şi FALSE
pentru o valoare nulă, aşa cum stau lucrurile în C.
În ceea ce priveşte tipurile de date primitive, ele sunt aproape identice
cu cele din C. Vom reveni cu detalii într-o secţiune ulterioară.
Operatorii sunt, practic cei din C. Operatorii pe biţi acţionează numai
asupra întregilor. Operatorul >> face deplasarea la dreapta cu propagarea bitului
de semn, iar un nou operator, >>>, face aceeaşi deplasare cu completarea de
zerouri pentru biţii eliberaţi. Singura suprapunere, implicită, este += ce poate
indica şi concatenarea de string-uri.
Cât despre spaţiile de nume: în Java orice variabilă (câmp) şi orice
metodă este declarată numai în interiorul unei clase şi face parte din structura
acesteia. De asemenea, fiecare clasă face parte dintr-un pachet. Pachetele Java
sunt astăzi cele care permit o mare dezvoltare şi extindere a platformelor Java.
O colecţie de clase înrudite, cu ţinta spre un anumit tip de prelucrări, constituie
în fapt o bibliotecă ce poate fi integrată în orice aplicaţie Java. Este suficient să
amintim aici că, dacă la Java 1.0 ([10]) erau opt pachete standard, la Java 1.2
erau 58 [44] şi numărul pachetelor, standard sau de aplicaţie este în continuă
creştere.
Accesul la nume se poate face:

25
• implicit - între clasele aceluiaşi pachet;
• declarat public - accesibil de oriunde;
• declarat protected - accesibil din clasă sau clasele subordonate;
• declarate private - accesibil numai în interiorul clasei.
În C++ orice nume (variabilă, constantă, nume de funcţie etc.) trebuie
mai întâi declarat sau definit şi abia apoi utilizat. În Java această restricţie nu
mai există.
Constructorii, atât în Java cât şi în C++ pot fi suprapuşi. În Java, toate
obiectele sunt transmise prin referinţă, eliminându-se astfel constructorii de
copiere din C++. În Java nu există destructori! Interpretorul JVM are un thread
specializat, care rulează în continuu (cu prioritatea cea mai mică), numit
garbage collector. Acesta face oficiile de destructor atunci când un obiect nu
mai este referit de nicăieri.

Garbage collection
Obiectele Java îşi ocupă memoria necesară în mod dinamic, în faza de
execuţie. Alocarea de memorie este făcută automat, deci lipsesc apelurile
similare lui malloc. De asemenea, nu se face eliberarea explicită a
spaţiului ocupat, deci nu există apeluri similare lui free din C şi C++.
Distrugerea obiectelor se face automat, printr-un mecanism clasic de
garbage collection [76, 17]. Eliberarea spaţiului ocupat de un obiect se
face, fără ştirea utilizatorului, după ce obiectul nu mai este vizibil şi nici
în viaţă.

Funcţiile pot fi suprapuse, atât în C++ cât şi în Java. Spre deosebire de


C++, în Java nu sunt admişi parametri cu valori implicite, nici funcţii cu un
număr variabil de parametri. La fel ca şi în C++, Java permite metode native.
Spre deosebire de C++, Java nu suportă template, nici funcţii generice.
Cuvântul virtual nu apare în Java. Toate metodele nestatice din Java sunt
legate dinamic, aşa că virtual din C++ îşi pierde semnificaţia.
Asupra tratării excepţiilor vom reveni.
Spre deosebire de C++, Java oferă o serie de structuri specializate,
numite containere de obiecte. Relevante în acest sens sunt containerele Vector
şi Hashtable.
În fine, comentariile speciale /** … */ sunt procesate prin javadoc
şi permit elaborarea automată de documentaţii.

26
1.2.2. Modificatorii static, final, syncronized şi native
Modificatorul static
Poate preceda un câmp (variabilă) sau o metodă a unei clase, pentru a
specifica faptul că elementul respectiv este propriu clasei respective şi că
el nu depinde de una sau alta dintre obiectele rezultate din instanţierile
clasei respective. Deci câmpul (variabila) are aceeaşi valoare indiferent
de instanţiere, iar metoda funcţionează independent de instanţiere. Un
element static poate fi accesat indiferent de faptul că există sau nu o
instanţiere a clasei respective.
De exemplu, variabila out din clasa System poate fi accesat chiar dacă
nu există o instanţiere a acestei clase, motiv pentru care putem invoca
metoda println a lui: System.out.println("…");

Modificatorul final
Ataşat unei variabile sau unei metode indică faptul că elementul
respectiv este în ultima lui formă. Din această cauză, nici variabila, nici
metoda finală nu mai pot fi suprascrise în cadrul subclaselor derivate. O
variabilă finală se comportă ca şi o constantă.

Modificatorul syncronized
Poate să caracterizeze o metodă (există şi instrucţiunea syncronized
asupra căreia vom reveni). Dacă două sau mai multe thread-uri invocă o
aceeaşi metodă sincronizată pentru un acelaşi obiect, atunci doar unul
dintre ele o execută, celelalte aşteaptă ca acesta să îşi termine invocarea
ei, după care un altul îşi porneşte exclusiv execuţia ş.a.m.d. Deci, dacă
două thread-uri solicită pentru acelaşi obiect a o metodă sincronizată
met, adică ambele invocă a.met, atunci ele vor fi executate unul
după celălalt. Dacă însă există două obiecte diferite a şi b, atunci două
thread-uri pot să execute simultan apelurile a.met() b.met().

Modificatorul native
Indică faptul că metoda respectivă este descrisă într-un fişier extern
provenit dintr-o altă compilare. De exemplu dintr-o compilare C. Evi-
dent, acest gen de metode nu mai păstrează independenţa de platformă.

27
1.2.3. Cuvinte cheie
Java utilizează următoarele cuvinte cheie:
abstract boolean break byte case
catch char class const continue
default do double else extends
final finally float for goto
if implements import instanceof int
interface long native new null
package private protected public return
static short super switch synchronized
this throw throws transient try
void volatile while

1.2.4. Tipuri de date primitive


Unicode: codificarea şi evitarea caracterelor
Java este unul dintre puţinele limbajele de programare care “încalcă” un
principiu care părea statuat de facto: caracterele Java şi string-urile sunt
codificate în codul Unicode pe 16 biţi. Asta face ca setul de caractere să fie
potrivit şi pentru alte caractere, neexistente în alfabetul englez. Setul Unicode
este efectiv un supraset al lui ASCII, deci textele ASCII au aceeaşi reprezentare,
dar fiecare octet ASCII este completat cu câte un octet semnificativ cu valoarea
0 (neinterpretat de maşinile ce văd doar ASCII).
Cititorii care doresc să cunoască întregul set de caractere Unicode, pot
consulta http://unicode.org. Specificarea caracterelor ASCII tipăribile
se face ca şi în C. Mecanismul de evitare a caracterelor din C se păstrează şi se
mai introduce un mod suplimentar de specificare, astfel:
\uxxxx unde xxxx sunt patru cifre hexa;
\xxx unde xxx sunt trei cifre octale.
\n \r \t \f \b \” \’ \\ sunt cele cunoscute din C.

Tipul char
Se reprezintă pe doi octeţi în Unicode şi respectă convenţiile de
conversie la întregi din C.

String-urile
Se scriu ca şi în C (între ghilimele). Spre deosebire de C, în Java nu
există continuare (linie terminată cu \ în C sau C++) pentru string-urile
lungi. În schimb, operatorul + Java este extins şi pentru concatenarea de

28
string-uri. Deci şirurile lungi se scriu folosindu-se operatorul + de conca-
tenare. Există tipul de date String.

Tipul Boolean
Este nou introdus în Java (nu este în C). În schimb dispare convenţia C
cu 0 având valoarea false şi diferit de 0 având valoarea true. Un tip
Boolean nu este un întreg şi nici nu poate fi convertit la acesta.

Tipurile întregi
La tipurile întregi, împărţirea cu 0 activează excepţia numită
ArithmeticException. Constantele întregi lungi au după şirul de
cifre litera l sau L. Constantele octale încep cu 0, iar cele hexazecimale
cu 0x sau 0X.

Tipurile flotante
Pentru tipurile flotante există constantele predefinite:
POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN (Not a Number).

La tipurile flotante depăşirile nu se semnalează, ci rezultatul poate fi


unul dintre constantele de mai sus. Există, de asemenea, zero negativ şi
zero pozitiv. Constantele flotante se pot specifica plasând la sfârşitul
scrierii numărului litera f sau F pentru simplă precizie, respectiv cu d
sau D pentru dublă precizie.
Tabelul următor descrie tipurile de date primitive.
Tip Conţinut Valoare implicită Lungime
Boolean true sau false false 1 bit
Char caracter Unicode \u0000 16 biţi
Byte întreg cu semn 0 8 biţi
Short întreg cu semn 0 16 biţi
Int întreg cu semn 0 32 biţi
Long întreg cu semn 0 64 biţi
Float standard IEEE simplă precizie 0.0 32 biţi
Double standard IEEE dublă precizie 0.0 64 biţi

29
1.2.5. Tipul referinţă obiect
Tipurile de date neprimitive sunt obiecte, în care includem şi tablouri.
Ele sunt numite în Java “tipuri referinţă”, deoarece sunt manevrate prin adresă:
compilatorul pune adresa unui obiect sau tablou într-o variabilă şi aşa este
transmis către metode. Prin contrast, tipurile primitive sunt manevrate “prin
valoare”.
Deoarece obiectele sunt transmise prin referinţă, este posibil ca două
variabile diferite să se refere la acelaşi obiect. De exemplu:

Button p, q;
p = new Button();
q = p; int i = 3;
p.setLabel(“Ok”); int j = i;
String s = q.getLabel(); i = 2;

Aici, s va avea valoarea “Ok”. Aici, i este 2 şi j este 3 (deci nu e valabil


acelaşi lucru la datele primitive!).

Copierea obiectelor şi “compararea” lor


Din cauza referinţei, atribuirea între obiecte nu face copiere. De
exemplu,
Button a = new Button(“Ok”);
Button b = new Button(“Cancel”);
a = b;

Aici, a şi b punctează ambele la butonul Cancel, iar butonul Ok este


pierdut.
Pentru a se face o atribuire veritabilă, trebuie executată o operaţie de
copiere a componentelor de la sursă la destinaţie. Majoritatea tipurilor de date
standard au definită o metodă clone(), care execută efectiv copierea, compo-
nentă cu componentă. O astfel de atribuire funcţionează ca în exemplul următor,
în care variabila c se va referi la un duplicat al obiectului b:
Vector b = new Vector;
c = b.clone();

Pentru copierea de tablouri, se foloseşte metoda:


System.arraycopy(sursa,inceput,destinatie,inceput,lungime)

Obiectele nu pot fi comparate nici măcar cu egalitate. Utilizatorul îşi


poate defini proceduri proprii de comparare. Unele clase au, în acest scop, o
metodă numită equals().

30
Din raţiuni de portabilitate, de independenţă de platformă şi de siguranţă,
nu există pointeri! Deci, nu se pune problema referirii, dereferirii, conversiei
referinţelor la tablouri în întregi etc.
Constanta null indică inexistenţa unui obiect sau a unui tablou.

1.3. Tratarea excepţiilor


O excepţie Java este un obiect care descrie o condiţie (situaţie)
excepţională ce are loc într-o secvenţă de cod. Atunci când are loc o condiţie
excepţională Java creează un obiect reprezentând eroarea şi aruncă excepţia
metodei care a cauzat eroarea. Metoda poate să aleagă între a prelua excepţia
(şi a o trata), sau a o ignora. În oricare din cazuri la un anumit moment excepţia
este prinsă şi tratată.
Excepţiile pot fi generate de mediul de execuţie Java, sau de pro-
gramator. Excepţiile generate de Java se referă la erori fundamentale care
violează regulile limbajului sau ale mediului de execuţie. Excepţiile aruncate de
programator sunt folosite de regulă pentru a raporta nesatisfacerea precon-
diţiilor apelului unei metode. Pentru rezolvarea problemelor legate de excepţii
sunt folosite cinci cuvinte rezervate: try, catch, throw, throws
şi finally. Iată cum funcţionează mecanismul:
• secvenţa de instrucţiuni care este susceptibilă de erori este pusă într-
un bloc try; dacă se generează o excepţie, datorită execuţiei
blocului de instrucţiuni, atunci Java creează un obiect excepţie şi îl
aruncă metodei (spre secvenţa respectivă);
• dacă dorim să prindem excepţia, atunci folosim clauza catch,
imediat după blocul try;
• dacă vrem să provocăm (aruncăm) o anumită excepţie atunci folo-
sim throw; excepţiile fundamentale sunt provocate automat de
către Java;
• dacă anumite instrucţiuni trebuie să fie executate înainte de a ieşi
(normal sau anormal) din metodă, atunci punem instrucţiunile într-
un bloc finally;
• în antetul de definire a unei metode, pentru a specifica că metoda
este posibil să arunce o excepţie, trebuie specificată excepţia
(excepţiile) folosind o clauză throws
Forma generală pentru manipularea excepţiilor este:
try {
. . . // Bloc de instructiuni in care pot apare exceptii
}

31
catch (TipExceptie1 objExceptie1) {
. . . // Tratare exceptie TipExceptie1
}
catch (TipExceptie2 objExceptie2) {
. . . // Tratare exceptie TipExceptie2
}
. . . ...
finally {
. . // Bloc de instructiuni ce se executa
// inainte de a se incheia blocul try
}

Pentru comoditate, în multe dintre programele noastre de test evităm


folosirea lui try - catch adăugând la definirea lui main:
public static void main(String a[]) throws Exception {

Aceasta provoacă tratarea automată a excepţiilor de către JVM.


Tot pentru comoditate, spre a nu "ne bate capul" cu prea multe catch şi
a inventaria toate excepţiile posibile de aruncat dintr-un bloc try, plasăm un
singur catch sub forma:
catch(Exception e) {
System.out.println(e.getMessage()); // eventual lipseşte
e.printStackTrace();
System.exit(1); // eventual lipseşte
}

Atragem însă atenţia că în cadrul programelor reale trebuie tratată fiecare


excepţie în parte în conformitate cu cerinţele problemei de rezolvat! În caz
contrar pot apărea conflicte serioase la interfaţa cu alte componente ale
aplicaţiei! Într-unul dintre capitolele următoare vom prezenta o aplicaţie mai
mare în care vom aborda "cu simţ de răspundere" tratarea excepţiilor.
Aruncarea unei excepţii se face prin:
new ExceptieAruncata (... );

Toate tipurile de excepţii sunt subclase ale clasei


java.lang.Throwable cu două subclase: java.lang.Exception şi
java.lang.Error. Pentru fiecare eroare apărută se instanţiază un obiect al
uneia dintre cele două subclase. Printre altele, fiecare obiect excepţie conţine un
string ce are ca valoare mesajul explicativ al excepţiei sau erorii respective.
Excepţiile generate (aruncate) prin program trebuie să fie de tip Exception.
Excepţiile de tipul Error sunt generate de mediu şi nu pot fi prinse de
program (de exemplu blocarea mediului Java datorită memoriei insuficiente).
O parte din ierarhia de excepţii şi erori este:
Throwable
• Exception
• IOException

32
• FileNotFoundException
• ...
• RuntimeError
• ArithmeticException
• IndexOutOfBoundsException
• ArrayIndexOutOfBoundsException
• StringIndexOutOfBoundsException
• NullPointerException
• ...
• ...
• Error
• VirtualMachineError
• ...

1.3.1. Istanţierea şi distrugerea obiectelor. Constructori


Instanţierea
Instanţierea (crearea) unui obiect se face, după cum am mai arătat,
folosind new. Rutina care se execută în momentul instanţierii poartă numele de
constructor. Din punct de vedere formal, un constructor este o metodă care:
poartă acelaşi nume cu cel al clasei, nu se specifică void la tipul întors, şi nu
conţine return în interiorul corpului.
Dacă nu se specifică nici un constructor, atunci se generază automat un
constructor vid, fără parametri.
E posibil să se definească mai mulţi constructori. Mai mult, unii pot să-i
folosească pe alţii. Avem astfel de-a face cu o suprapunere (suprascriere) a
constructorilor.
În continuare exemplificăm prin câteva variante de constructori pentru
clasa Cerc. La unele dintre ele vom folosi variabile locale - parametri şi
câmpuri de date - cu aceleaşi nume, diferenţierea dintre ele făcându-se prin
cuvântul rezervat this.

public class Cerc {


public double x, y, r;
public Cerc (double x, double y, double r) // Centrul (x,y), raza r
{this.x = x; this.y = y; this.r = r;}
public Cerc (double r) // Centrul (0,0), raza r
{x = 0.0; y = 0.0;this.r = r;}
public Cerc (Cerc c) // Copiere a altui cerc
{x = c.x; y = c.y; r = c.r;}
public Cerc () // Centrul (0,0), raza 1
{x = 0.0; y = 0.0; r = 1.0;}
- - -
}

33
Singura restricţie la constructorii multipli este aceea că orice doi cons-
tructori diferiţi trebuie să aibă liste de parametri la care cel puţin o pereche de
parametri corespunzători să difere. De exemplu, primul şi al doilea constructor
diferă prin parametrul al doilea, prezent doar la primul constructor; al doilea şi
al treilea constructor au numai câte un parametru dar aceştia sunt de tipuri
diferite ş.a.m.d.
Pentru a apela un constructor, în cadrul unui new numele clasei va avea
argumentele dorite, din care compilatorul va deduce despre care constructor este
vorba. De exemplu, următoarele linii au acelaşi efect:
Cerc c = new Cerc(); c.x = 1.0; c.r = 0.5;

Cerc c = new Cerc (1.0, 0.0, 0.5);

Cuvântul this poate să apară într-un constructor pentru a desemna un


alt constructor din aceeaşi clasă. De exemplu, cei patru constructori de mai sus
se pot defini şi astfel:
public class Cerc {
public double x, y, r;
public Cerc (double x, double y, double r) // Centrul (x,y), raza r
{this.x = x; this.y = y; this.r = r;}
public Cerc (double r) // Centrul (0,0), raza r
{this(0.0,0.0,r);}
public Cerc (Cerc c) // Copiere a altui cerc
{this(c.x,c.y,c.r);}
public Cerc () // Centrul (0,0), raza 1
{this(0.0,0.0,1.0);}
- - -
}

Singura restricţie de folosire a lui this pe post de constructor într-un alt


constructor este aceea că apelul this trebuie să apară ca primă instrucţiune în
corpul noului constructor. După acest apel pot să urmeze şi alte instrucţiuni.

Distrugerea obiectelor
Distrugerea obiectelor se face automat prin mecanismul de Garbage
Collection care este un thread (fir de execuţie) de prioritate minimă. El culege
zonele de memorie după ce acestea nu mai sunt folosite şi eliberează spaţiul
ocupat de ele. Aceasta este, după părerea noastră, cea mai mare diferenţă de stil
de programare între Java şi C sau C++. Urmează ca cititorii să se convingă
singuri: va părea ciudat, cel puţin la început, să nu gestionezi eliberarea spaţiilor
de memorie!
Este însă posibil ca eliberarea de spaţiu să se petreacă uneori târziu.
Uneori (poate) este utilă forţarea eliberării mai rapide. Iată o posibilă schemă:

34
public static void main() {
int T = new int[100000];
- - -
calcule(T);
T = null; // Din acest moment, tabloul poate fi distrus
- - -
restul calculelor
- - -
}

Finalizator de obiecte
De multe ori, anumite obiecte sau resurse, cum ar fi spre exemplu,
descriptorii de fişiere şi socket-urile, sunt păstrate indefinit. Pentru a forţa
eliberarea acestora, se pot folosi metode finalizatori, după modelul de mai jos.
protected void finalize() throws IOException
{ if (fd != null) close(); // fd este un descriptor }

Există câteva reguli privitoare la finalizatori:


• dacă un obiect are o metodă finalizator, atunci ea este invocată
înainte de Garbage Collection;
• nu se garantează ordinea de intervenţie a lui Garbage Collection, de
aceea nu se garantează momentele în care intră în lucru finaliza-
toarele;
• după finalizator, obiectul nu mai este disponibil!
• de multe ori, finalizatorul se asociază cu excepţiile.

1.3.2. Definiri statice (clasă):


variabile, metode, iniţializatori
Variabile clasă (statice)
Variabilele definite într-o clasă au câte o copie pentru fiecare instanţiere
a unui obiect. Există însă situaţii în care sunt necesare variabile proprii claselor,
ale căror valori nu depind de instanţiere. Acestea vor fi numite variabile clasă
sau variabile statice.

Metode clasă (statice)


Analog cu variabilele clasă, există şi metode clasă, metode independente
de instanţiere. În secvenţa de mai jos vom defini două metode de comparare a
două cercuri. Una va compara cercul curent cu un alt cerc, iar alta va compara

35
două cercuri primite prin parametri. Evident, prima va fi dependentă de
instanţiere, dar a doua va fi independentă de instanţiere.
public class Cerc {

public double x, y, r;
- - -
public Cerc maxim (Cerc c)
{ if (c.r > r) return c; else return this;}

public static Cerc maxim (Cerc a, Cerc b)


{if (a.r > b.r) return a; else return b;}
- - -
}

Apelurile lor pot fi invocate astfel:


Cerc a = new Cerc (2.0);
Cerc b = new Cerc (3.0);
- - -
// Doua utilizari ale primei metode
Cerc c = a.maxim (b);
Cerc d = b.maxim (a);
- - -
// Utilizare a celei de-a doua metode
Cerc c = Cerc.maxim (a,b);
- - -

În funcţie de context, programatorul va prefera una sau alta dintre


metodele maxim.
Precizăm câteva caracteristici ale metodelor statice:
• sunt declarate cu prefixul static;
• sunt invocate din clase în loc de a fi invocate din instanţe;
• în astfel de metode nu se foloseşte this ! (fapt normal, deoarece
this desemnează instanţierea curentă).

Iniţializatori statici
Atât variabilele instanţă, cât şi cele statice, pot fi iniţializate, prin
mecanismele cunoscute din C şi C++. De exemplu,
static int nr_cercuri = 0;

Java oferă posibilitatea de a face iniţializări mai complexe. Acestea se


desemnează prin construcţia static{. . .}, unde între acolade poate să
apară o secvenţă de instrucţiuni. Iată de exemplu cum se poate iniţializa un
tablou cu valori ale funcţiilor trigonometrice, pentru a evita calcularea lor de
fiecare dată:

36
public class Cerc {
- - -
static private double[] sinus = new double[1000];
static private double[] cosinus = new double[1000];
- - -
static {
double x, deltax;
int i;
deltax = (Math.PI / 2) / (1000-1);
for (i=0, x=0.0; i<1000; i++, x+=deltax) {
sinus[i] = Math.sin(x);
cosinus[i] = Math.cos(x);
}
- - -
}

1.4. Interfeţe Java


Interfeţele Java abstractizează complet metodele unei clase (interfaţa
clasei) faţă de orice implementare. Folosind cuvântul rezervat interface (în
loc de class) specificăm ce poate face o clasă şi nu cum realizează clasa acel
lucru.

1.4.1. Definirea unei interfeţe


Forma generală a definirii unei interfeţe Java este
interface nume {
tipRezultat1 numeMetoda1(listaParametri1);
- - -
tipRezultatn numeMetodan(listaParametrin);

tipVariabilaFinala1 numeVariabilaFinala1 = valoare1;


- - -
tipVariabilaFinalak numeVariabilaFinalak = valoarek;
}

1.4.2. Implementarea interfeţelor


Odată ce o interfaţă a fost definită, una sau mai multe clase pot să ofere o
implementare a interfeţei. Pentru a implementa o interfaţă, o clasă trebuie să
includă cuvântul rezervat implements în antetul definiţiei sale:
class NumeClasa
[ extends ClasaSuperioara ]
implements numeInterfata1 [ , numeInterfata2 . . .] {

37
după care să definească toate metodele declarate de interfaţă. De asemenea, se
impune ca toate metodele interfeţelor să fie declarate public. În consecinţă,
clasele care implementează interfaţa trebuie să fie declarate public.

1.4.3. Apelul metodelor prin


variabile referinţe la interfeţe
Deoarece metodele unei interfeţe sunt implementate de o clasă, evident
că le putem apela prin variabile referinţă la clasa care implementează interfaţa.
Java tratează interfeţele ca şi clase pur abstracte. Astfel,
• putem să definim variabile de tip interfaţă (referinţe la interfaţă);
• o variabilă interfaţă poate referi o instanţă a unei clase ce imple-
mentează interfaţa;
• dacă o variabilă interfaţă referă o instanţă care implementează inter-
faţa atunci la apelul unei metode din interfaţă se va apela metoda
instanţei (apel conform obiectului referit şi nu conform referinţei - ca
şi la redefinirea metodelor în cazul moştenirii).

1.5. Exemple de programe Java


În cele ce urmează prezentăm câteva aplicaţii standalone scrise în Java
prin care exemplificăm cele prezentate mai sus.

1.5.1. Calcule aritmetice simple


1.5.1.1. Distanţa între două puncte

Se cere determinarea distanţei euclidiene dintre două puncte ale căror


coordonate se dau în linia de comandă sub forma a patru numere. Sursa este
dată prin programul 1.7.

public class Distanta {

public static void main(String a[]) throws Exception {


// Determina distanta intre punctele de coordonate (a[0],a[1]), (a[2],a[3])
double x1 = Double.parseDouble(a[0]);
double y1 = Double.parseDouble(a[1]);
double x2 = Double.parseDouble(a[2]);
double y2 = Double.parseDouble(a[3]);
System.out.println("Distanta este: "+ // Afiseaza distanta
Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)));

38
} // Distanta.main

} // Distanta

Programul 1.7. Distanta.java

1.5.1.2. Calculul unor medii ponderate

Se cere un program care calculează succesiuni de medii ponderate ale


unor note. Ponderile sunt date ca şi argumente în linia de comandă. Notele sunt
cerute pe rând de la intrarea standard. Sursa este dată în programul 1.8

import java.io.*;

public class MediiPonderate {

public static void main(String a[]) throws Exception {


int i;
String s;
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
double[] p = new double [a.length];
double[] n = new double [a.length];
double medie, total;
for (i=0, total=0; i<a.length; i++) {
p[i] = Double.parseDouble(a[i]);
total += p[i];
} // for
for (;; ) {
for (i=0, medie=0; i<a.length;i++) {
System.out.print("Nota "+(i+1)+": ");
s = in.readLine();
if (s == null)
System.exit(0);
medie += (p[i]*Double.parseDouble(s));
} // for
medie /= total;
System.out.println("Media: "+medie);
} // for
} // MediiPonderate.main

} // MediiPonderate

Programul 1.8. MediiPonderate.java

39
Figura 1.5. Utilizarea MediiPonderate

În fig. 1.5 este prezentat un exemplu de utilizare a programului, pentru


medii ponderate cu ponderile 5, 7.5, 2.5 şi 5. (Altfel spus, prima şi ultima notă
contează la fel şi împreună cât celelalte două; nota a doua mai mult, a treia mai
puţin ...):
Dacă din greşeală la lansarea programului de mai sus nu se dau ponderi
în linia de comandă în momentul lansării, atunci programul va intra în ciclu
infinit!

1.5.2. Generarea de elemente aleatoare

1.5.2.1. Generarea de parole

În administrarea utilizatorilor din reţele este necesară o “rezervă” de


parole cu care să opereze administratorii. Programul care urmează generează
aleator 2000 parole de câte 8 caractere, litere mari, litere mici şi cifre zecimale.
Sursa este dată în programul 1.9.

import java.io.*;
import java.util.*;

public class GenPasswd {

public static void main(String a[]) {


String car = "abcdefghijklmnopqrstuvwxyz"+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
"0123456789"; // Alfabetul
StringBuffer lin = new StringBuffer(" "); // Spatiu pentru
// o parola
Random r = new Random(); // Obiect numar aleator
for (int n=0; n<2000; n++) {
for(int i=0; i<8; i++)
lin.setCharAt(i,
car.charAt(

40
Math.abs(r.nextInt())%car.length())); // Genereaza un caracter
System.out.println(lin); // Tipareste o parola
} // for: cele 2000 parole generate
} // GenPasswd.main

} // GenPasswd

Programul 1.9. GenPasswd.java

1.5.2.2. Generarea de expresii booleene

Pentru o culegere de probleme a fost necesară scrierea unor expresii


booleene. Acestea trebuie simplificate şi să se construiască circuitele combina-
ţionale echivalente. Autorii au decis generarea aleatoare a unui anumit număr de
variabile, a unui număr oarecare de termeni conjunctivi şi o manieră aleatoare
de negare a variabilelor. Programul 1.10 face acest lucru.

import java.io.*;
import java.util.*;

public class GenExpr {


public static void main(String a[]) {
String var="abcde"; // Variabilele
String neg=" -"; // Negare sau nu
String s;
int v, n, t, i, j, k;
Random r = new Random(); // Obiectul numar aleator
for (i=0; i<20; i++) {
v = 3 + Math.abs(r.nextInt())%3; // Numar de variabile
t = 4 + Math.abs(r.nextInt())%8; // Numar de termeni conjunctivi
for (s = "f(",k=0; k<v; k++) {
s += var.substring(k,k+1);
s += (k<v-1)?",":") = ";
} // for: Partea stanga a definirii functiei
for(j=0; j<t; j++) {
for (k=0; k<v; k++) {
n = Math.abs(r.nextInt())%2; // Alege negare sau nu
s +=neg.substring(n,n+1)+var.substring(k,k+1);
// Lipeste variabila negata sau nu
} // for: generat un termen conjunctiv
if (j < t-1)
s += " V "; // Pune operatorul SAU
} // for: Expresia booleeana
System.out.println(s); // Tipareste expresia generata
} // for: cele 20 expresii
} // GenExpr.main
} // GenExpr

Programul 1.10. GenExpr.java

41
Iată, în fig. 1.6, cum arată rezultatul execuţiei acestui program.

Figura 1.6. Generarea unor expresii booleene

1.5.3. Ordonare linii


Programul 1.11 prezintă un exemplu simplu de operare cu string-uri. Se
citeşte de la intrarea standard o succesiune de linii. Programul reţine aceste linii
şi apoi le afişează în ordine alfabetică a conţinutului lor. Terminarea transmiterii
liniilor de la intrarea standard trebuie marcată prin CTRL/Z (cazul Windows),
respectiv CTRL/D (cazul Unix), pentru a marca sfârşitul fişierului de intrare
standard.
import java.io.*;
import java.util.*;

public class Linii {

public static void main(String a[]) {


BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
String s, d; // String-uri de serviciu
Vector ts = new Vector(); // Tabloul de stringuri
int n;
for(n=0; ; ) { // Citeste linie cu linie si adauga la ts
s=null;
try {
s = in.readLine();
} // try
catch (IOException e) {
e.printStackTrace();
} // catch
if (s==null)
break; // S-a tastat sfarsitul intrarii
ts.add(s);
n++;
} // for
for(int i=0; i<n-1; i++){ // Ordoneaza tabloul

42
for(int j=i+1; j<n; j++) {
d=(String)ts.elementAt(j);
s=(String)ts.elementAt(i);
if (s.compareTo(d) > 0) {
ts.set(i, d);
ts.set(j, s);
} // if
} // for j
} // for i
System.out.println("\nLiniile ordonate lexicografic:\n");
for (int i=0; i<n;i++){
s=(String)ts.elementAt(i);
System.out.println(s); // Afiseaza liniile
} // for
} // Linii.main

} // Linii

Programul 1.11. Linii.java

Iată, în fig. 1.7 o mostră de execuţie a acestui program:

Figura 1.7. Ordonarea liniilor

1.6. Thread-uri Java


În prezent, tendinţele în programarea concurentă utilizează în loc de
proces conceptul de thread [3, 6, 14]. Pachetul java.lang oferă clasa
Thread. Aceasta, împreună cu unele metode moştenite de la clasa Object,
permit crearea, execuţia, controlul şi sincronizarea thread-urilor. În particular,

43
sincronizarea se bazează pe conceptul de monitor. Este cunoscut faptul că
operarea multithreading dă multă bătaie de cap programatorilor C şi C++.
Programatorii trebuie să-şi implementeze mecanisme proprii de blocare a unor
secţiuni critice şi de partajare a unor resurse critice. Primitivele furnizate de
Java reduc esenţial acest efort.

1.6.1. Elemente de limbaj Java în contextul thread-urilor


Utilizarea thread-urilor Java este deja bine cunoscută în literatură [14,
32]. Având în vedere faptul că ne vom referi frecvent la o serie de metode
specifice lucrului cu thread-uri, considerăm oportună o trecere în revistă a
acestora. Pentru detalii se poate consulta [14].

1.6.1.1. Obiecte versus thread-uri

Având în vedere importanţa conceptului de thread, prezentăm pe scurt


metodele din clasa Object care se referă la thread-uri. Reţinem din clasă doar
ceea ce, într-un fel sau altul, poate fi util în context multithreading. Prototipurile
metodelor se găsesc în documentaţiile standard [91].

Metodele wait()
Pun obiectul în aşteptare până la apariţia unui eveniment. Ultimele două
forme indică o durată maximă de aşteptare, fie numai în
milisecunde, fie în milisecunde plus nanosecunde.

Metodele notify() şi notifyAll()


Sunt duale metodelor wait(), ele anunţă alte obiecte apariţia unui
eveniment.

Metoda getClass()
Întoarce clasa din care s-a instanţiat obiectul apelant al metodei.

Metoda equals()
Oferă, prin suprascriere în subclase, posibilitatea de a se defini ce
înseamnă egalitatea a două obiecte din clasa respectivă. Implementarea
acestei metode trebuie să respecte, pentru orice referiri nenule x, y,
z, următoarele condiţii:
• reflexivitate, adică x.equals(x) trebuie să întoarcă true;

44
• simetrie, adică x.equals(y) este true dacă şi numai dacă
y.equals(x) este true;
• tranzitivitate, adică x.equals(y) este true şi y.equals(z) este
true implică x.equals(z) este true;
• consistenţă, adică datele utilizate în metoda equals să nu modifice
relaţia între două obiecte; deci, pentru două obiecte x şi y, apelurile
multiple x.equals(y) trebuie să întoarcă mereu fie true, fie
false;
• referinţa null: x.equals(null) să fie mereu false.

Metoda clone()
Creează o copie a obiectului. Înţelesul exact al “copiei” depinde de clasă.
În general următoarele condiţii sunt adevărate:
• x.clone() != x este true;
• x.clone90.getClass() == x.getClass() este true;
• x.clone().equals(x) este true.

Sunt extrem de rare cazurile în care apar mici abateri de la aceste reguli.
Este vorba de situaţiile în care suprascrierea lui clone realizează o
“copiere superficială”.

Metoda finalize()
Poate fi suprascrisă în subclase şi ea conţine acţiunile pe care compo-
nenta “garbage collection” din JVM le va executa imediat înainte de a
distruge un obiect. Trebuie remarcat faptul că programul utilizator nu
poate stabili cu exactitate momentul şi ordinea de execuţie a metodelor
finalize legate de obiecte expirate.

Metoda toString()
Întoarce reprezentarea textuală a obiectului. Este utilă uneori în activi-
tatea de depanare. Se recomandă suprascrierea ei dacă se intenţionează a
fi utilizată.

1.6.1.2. Clasa Thread

În acelaşi stil ca şi în secţiunea precedentă, prezentăm principalele


metode ale clasei Thread.
Dacă se specifică în constructor un obiect tinta , atunci corpul thread-
ului (corpul metodei run) este ataşat acestui obiect. Dacă nu se specifică,

45
atunci corpul este ataşat thread-ului nou creat. Prin constructor, sau mai târziu
folosind metoda setName(), se poate atribui thread-ului un nume. Dacă nu se
specifică altă valoare, atunci numele implicit va fi “Thread-“+n, unde n este
un întreg. Metoda getName() întoarce numele curent al thread-ului.

Metoda start()
Lansează în execuţie noul thread. Din acest moment, execuţia progra-
mului este controlată de (cel puţin) două thread-uri: pe de o parte
funcţionează thread-ul curent - cel care execută start(), iar pe de altă
parte se lansează în execuţie noul thread, ale cărui instrucţiuni sunt
precizate în metoda run().

Metoda run
Conţine corpul efectiv al thread-ului. Întreaga activitate curentă a noului
thread trebuie descrisă prin suprascrierea acestei metode.

Metodele sleep()
Pun thread-ul curent în aşteptare un anumit interval de timp.

Metodele join()
Aşteaptă ca obiectul thread care le apelează să moară. Ultimele două
forme limitează timpul maxim de aşteptare.

Metoda yield()
Cedează controlul de la obiectul thread la planificatorul JVM, pentru a
permite unui alt thread (green) să ruleze.

Metoda interrupt()
Trimite o întrerupere obiectului thread care o invocă.

Metodele interrupted() şi isInterrupted()


Testează dacă thread-ul apelant a fost întrerupt sau nu. Metoda
interrupted() modifică statutul de întrerupt, aşa că la un dublu apel
al ei starea thread-ului revine la vechea ei formă. Metoda
isInterrupted nu modifică această stare.

Metoda isAlive()
Răspunde dacă obiectul thread apelant este în viaţă sau nu.

46
Metoda activeCount()
Întoarce numărul de thread-uri active din grupul curent. Metoda
currentThread() întoarce numărul thread-ului curent. Metoda
enumerate() întoarce în tabloul t thread-urile membre ale grupului
curent şi ale celor din subgrupuri. Metoda getThreadGroup()
întoarce grupul curent de thread-uri. Metoda dumpStack() afişează la
ieşirea standard stiva curentă de thread-uri.

Metodele stop(), suspend() şi resume()


Au fost definite în primele versiuni Java. Ulterior s-a dovedit că folosirea
lor poate conduce, în cazul unei proiectări neatente a programului, la
impas şi din această cauză începând cu versiunea 1.2 au fost declarate
“deprecated”, motiv pentru care nici noi nu le vom acorda prea mare
atenţie. Metoda stop() provoacă oprirea execuţiei unui thread. Metoda
suspend() suspendă temporar execuţia thread-ului, reluarea şi conti-
nuarea execuţiei făcându-se cu metoda resume().

1.6.2. Operaţii asupra thread-urilor Java;


creare, terminare
1.6.2.1. Crearea unui thread

Crearea unui thread folosind Java API se poate face în două moduri:
• implementând o clasă derivată din clasa predefinită Thread, clasă
care face parte din pachetul java.lang şi ca urmare este importată
automat în orice program Java;
• definind o clasă care implementează interfaţa Runnable.
Primul dintre ele constă în a declara o clasă ca subclasă a clasei
Thread. În interiorul acesteia se redefineşte metoda run(), metodă care
conţine “corpul” de instrucţiuni ale thread-ului. După aceea, se poate defini şi
instanţia noua subclasă. Schematic, crearea se face conform modelului de mai
jos.
class ThreadPropriu extend Thread {
// - - - datele subclasei - - -
ThreadPropriu ( /* parametrii constructorului */ ) {
// - - - descrierea constructorului
}
public void run() {
// - - - defineste corpul thread-ului ThreadPropriu - - -
}

47
}

A doua metodă constă în implementarea clasei Runnable. Această


interfaţă conţine o unică metodă, şi anume metoda run:
interface Runnable {
public void run ();
}

Astfel, o clasă care implementează interfaţa Runnable, evită descen-


denţa din clasa standard Thread. Instanţele acestei clase sunt create ca obiecte
active, în thread-uri diferite de thread-ul curent. Schematic, acest mod de creare
a unui thread apare aproape ca mai sus, cu excepţia primei linii:
class ThreadPropriu - - - implements Runnable {
// - - - datele subclasei - - -
ThreadPropriu ( /* parametrii constructorului */ ) {
// - - - descrierea constructorului
}
public void run() {
// - - - defineste corpul thread-ului ThreadPropriu - - -
}
}

După definirea clasei ThreadPropriu, indiferent de modalitate,


urmează crearea thread-ului şi lansarea lui în execuţie:
ThreadPropriu thr = new ThreadPropriu(- - -);
thr.start();

Mai întâi se creează obiectul thread, după care se lansează metoda


start() pentru efectuarea iniţializărilor.
Execuţia curentă a unui thread constă în execuţia metodei run(),
similară din acest punct de vedere cu funcţia main() a unui program
standalone. Spre deosebire de main(), metoda run() nu primeşte argumente
din linia de comandă. Eventualii parametri pot fi transmişi thread-ului prin
constructor, variabile statice sau prin alte alte metode stabilite de programator.

1.6.2.2. Terminarea unui thread

JVM execută un thread, precum şi pe cele pe care acesta le creează, până


când apare una dintre următoarele situaţii:
• Este apelată metoda exit() a clasei Runtime, iar managerul de
securitate a permis îndeplinirea cu succes a acestui apel.
• Toate thread-urile care nu sunt daemon s-au terminat, fie prin
întoarcerea din apelul metodei run(), fie printr-un apel al metodei

48
stop() din partea altui thread, fie prin trimiterea unei întreruperi
(interrupt()) din partea altui thread.
• S-a invocat metoda stop() a obiectului thread (metodă depăşită).
La terminarea unui thread, resursele specifice acestuia sunt eliberate.
Obiectul thread nu va fi şters din memorie de către garbage collector decât la
dereferenţierea manuală a instanţei (atribuirea valorii null pentru referinţă)
sau la terminarea programului.

1.6.2.3. Aşteptarea terminării unui thread

Dacă se doreşte aşteptarea terminării unui thread, se poate folosi metoda


join() a obiectului thread după care se aşteaptă. Thread-ul a cărui terminare
se aşteaptă nu este afectat de acest apel. Funcţia join() are acelaşi efect ca şi
combinaţia de metode sleep() şi isAlive().
Apelul this.join() înseamnă “wait forever” pentru că thread-ul
curent nu se va termina niciodată datorită acestui apel de aşteptare.
Funcţia join() apelată pentru un thread, care nu şi-a început execuţia
sau s-a terminat, returnează fără să aştepte.

1.6.3. Sincronizarea thread-urilor Java


1.6.3.1. Conceptul de monitor Java; synchronized

Mecanismele de sincronizare ale thread-urilor Java au la bază conceptul


de monitor (vezi [14]). Obiectele monitor aplică principiul excluderii mutuale
pentru grupul de proceduri sincronizate. Excluderea mutuală la datele partajate
se obţine prin accesul serializat al thread-urilor: "un thread odată".
Limbajul Java a implementat noţiunea de monitor ca un element asociat
fiecărui obiect Java. Utilizarea directă a monitoarelor este permisă prin
intermediul cuvântului rezervat synchronized aplicat funcţiilor membre sau
unor secţiuni de cod din interiorul metodelor.
Ca urmare, pentru a proteja o secţiune critică, se poate declara sincro-
nizată a) o metodă completă, sau b) un bloc (o secvenţă de instrucţiuni) dintr-o
metodă. În prima situaţie, mecanismul de sincronizare este asociat obiectului
curent (mai corect spus, monitorului acestuia) iar în al doilea caz, se poate alege
obiectul de sincronizare, de exemplu, un obiect declarat static în clasa curentă
sau din nou, obiectul curent. Ca regulă, nu se alege ca obiect de sincronizare un
obiect a cărui valoare se modifică în interiorul blocului. Deci, pentru un obiect,
statutul de monitor se poate obţine fie pe întreaga durată a execuţiei unei meto-
de, fie pe durata execuţiei unei anumite secvenţe de instrucţiuni.

49
Schematic, aplicarea declaraţiilor synchronized se face după una din
următoarele două scheme:
public - - - synchronized - - - numeMetoda (… ){ - - - }
sau
synchronized (obiect) { bloc (secvenţă de instrucţiuni) }

În prima situaţie, un apel obiect.numemetoda(…) face ca obiect


să primească statutul de monitor. Deci orice apel obiect.altaMetoda(…),
unde altaMetoda este şi ea sincronizată, va rămâne în starea de aşteptare a
exclusivităţii până când apelul obiect.numemetoda(…) îşi încheie activi-
tatea.
În a doua situaţie, se conferă obiectului obiect statutul de monitor
până la terminarea secvenţei de instrucţiuni specificată între acoladele instruc-
ţiunii synchronized. Trebuie remarcat faptul că obiectul argument al ins-
trucţiunii synchronized nu este obligatoriu să coincidă cu obiectul curent.
În legătură cu metodele/secţiunile de program sincronizate trebuie făcute
următoarele precizări:
Când este apelată o procedură sincronizată (care conţine atributul
synchronized) pentru un obiect, se verifică monitorul asociat obiectului.
Dacă există alte metode sincronizate care se execută, prima metodă se va bloca,
până la terminarea lor. Deci se pot executa în paralel: o singura metodă sincro-
nizată cu mai multe metode nesincronizate

Metode sincronizate statice


În cazurile precedente de sincronizare, am precizat că acest mecanism
este posibil prin obţinerea monitorului asociat obiectului curent de
sincronizare. Metodele statice nu sunt particulare unei anumite instanţe a
clasei curente, motiv pentru care la sincronizarea acestora se foloseşte un
obiect denumit class lock. Şi în acest caz, sistemul este destul de
inteligent ca să suporte blocări imbricate, deci dacă o metodă statică
sincronizată este apelată din interiorul altei metode statice sincronizate,
nu mai este cerut obiectul class lock asociat.

1.6.3.2. Mecanismul wait/notify

Un thread care are exclusivitatea asupra unui obiect cu statut de monitor


poate ceda temporar exclusivitatea. Această cedare temporară se face apelând
metoda wait(). Ca efect, este cedată exclusivitatea asupra obiectului, iar
thread-ul curent îşi opreşte temporar execuţia instrucţiunilor lui, adică trece în
starea Waiting.

50
Reluarea exclusivităţii cedate de către un thread care a executat un wait
are loc atunci când thread-ul respectiv “se trezeşte” ca urmare a receptării unui
anunţ trimis de către un alt thread activ care execută una dintre metodele
notify() sau notifyAll(). În urma acestui eveniment, thread-ul îşi
recapătă exclusivitatea cedată anterior şi îşi continuă execuţia instrucţiunilor de
unde şi-a oprit-o anterior (după apelul wait()).
Anunţul efectuat prin notify() se adresează tuturor thread-urilor care
sunt în starea Waiting, dar numai unul dintre ele îşi va relua activitatea, celelalte
vor rămâne în continuare în starea Waiting. În schimb, anunţul efectuat prin
notifyAll() este recepţionat de către toate thread-urile aflate în starea
Waiting. În consecinţă toate thread-urile ies din această stare şi îşi continuă
activitatea.
Este posibil ca un thread să solicite intrarea în starea Waiting doar pentru
un interval limitat de timp. În acest caz el va specifica în apelul wait durata
maximă pe care o admite.
De asemenea, un thread poate ieşi din starea Waiting dacă un alt thread
apelează metoda interrupt() a thread-ului aflat în starea Waiting. În acest
caz execuţia lui se reia cu secvenţa catch ce interceptează întreruperea.
În sumar, prezentăm scenariul de comunicare între thread-uri, folosind
funcţiile wait şi notify:
1. Un thread are nevoie în timpul execuţiei de o condiţie despre care
presupune că va fi realizată în alt thread.
2. Primul thread aşteaptă realizarea condiţiei cu ajutorul metodei
wait(), oferind astfel fiecărui thread suport pentru acest meca-
nism de comunicare.
3. Thread-ul care realizează condiţia în timpul execuţiei, anunţă acest
lucru unui thread care aşteaptă, prin apelul funcţiei notify().
Metodele wait() şi notify() trebuie apelate din interiorul unei
metode sau bloc sincronizat, pentru a preveni eventuale rezultate incorecte,
generate de lipsa sincronizării. Înainte de a intra în aşteptare folosind wait(),
thread-ul curent eliberează obiectul de sincronizare blocat şi-l obţine din nou
când thread-ul este anunţat că a fost realizată condiţia aşteptată.
Metodele wait() şi notify() sunt metode native, scrise în C şi nu
este posibilă reimplementarea lor folosind numai limbajul Java.
Mai facem câteva precizări în legătură cu comportarea mecanismului
wait/notify:
• Dacă notify() este apelată dintr-un thread, dar nu este nici un
thread care să aştepte, atunci metoda notify() returnează fără nici
o eroare.

51
• Funcţia wait() eliberează obiectul de sincronizare la intrarea în
aşteptare şi îl reobţine la recepţionarea unei notificări. Sistemul
asigură atomicitatea acestor operaţii critice.
• Dacă există mai multe thread-uri care aşteaptă o anumită condiţie, nu
se poate ştii care thread primeşte notificarea după apelul funcţiei
notify(). Acest lucru depinde de JVM şi de mecansimul de
planificare a thread-urilor.
• Dacă se doreşte apelul funcţiilor wait() şi notify() din metode
statice, atunci se declară în interiorul clasei un obiect de sincronizare
static, iar funcţiile wait() şi notify() se vor apela pentru acest
obiect static.
Diferenţa esenţială dintre funcţiile wait şi sleep constă în faptul că
metoda sleep nu este obligatoriu să fie apelată dintr-un bloc sincronizat şi nu
implică blocarea sau deblocarea unui obiect de sincronizare.

1.6.3.3. O schemă exemplu de sincronizări

În cele ce urmează, pentru a exemplifica utilizarea synchronized,


wait(), notify(), notifyAll(), prezentăm schematic părţile esenţiale
ale unei clase Bd care poate fi accesată în regim de cititor/scriitor [14].
class Bd {
static int cititori = 0;
- - - - - - - - - - - -
public void citeste ( - - - ){
- - -
synchronized (this) {
cititori++;
}
- - - // citirea propriuzisa
synchronized (this) {
cititori--;
notify();
}
}
public synchronized void scrie( - - - ) {
while (cititori > 0) {
- - - - - - - -
try { wait(); }
catch (InterruptedException e) { - - - }
}
- - - // scrierea propriu-zisa
notifyAll();
}

52
Partea esenţială pentru sincronizare constă din definirea variabilei
cititori şi a metodelor citeste şi scrie.
Variabila clasă cititori reţine de fiecare dată câţi cititori sunt activi
la un moment dat. După cum se poate observa, instanţa curentă a clasei Bd este
blocată (pusă în regim de monitor) pe parcursul acţiunilor asupra variabilei
cititori. Aceste acţiuni sunt efectuate numai în interiorul metodelor scrie
şi citeste.
Metoda citeste incrementează (în regim monitor) numărul de cititori.
Apoi, posibil concurent cu alţi cititori, îşi efectuează activitatea, care aici constă
doar în afişarea stării curente. La terminarea acestei activităţi, în regim monitor
decrementează şi anunţă thread-urile aşteptătoare. Acestea din urmă sunt, cu
siguranţă, numai scriitori.
Metoda scrie este declarată synchronized (regim monitor), deoa-
rece întreaga ei activitate se desfăşoară fără ca celelalte procese să acţioneze
asupra Bd.

1.6.4. Un exemplu de multithreading:


alinierea cuvintelor într-un fişier text
Enunţul problemei este simplu:
Se cere rearanjarea paragrafelor dintr-un text aşa încât toate liniile, cu
excepţia ultimei din paragraf, să aibă exact aceeaşi lungime, iar cuvintele
să fie dispuse (cât mai) echidistant în linie.
Abordăm problema multithreading, în sensul următor. Se defineşte un
recipient de cuvinte, care să funcţioneze după disciplina FIFO şi să se
sincronizeze ca şi buffer-ul de la problema producătorului şi a consumatorului
[14]. Programul este descris cu ajutorul a patru clase:
• RecipientCuvinte, care înmagazinează un număr limitat de
cuvinte şi le livrează la cerere;
• ProducătorCuvinte, este un thread care citeşte textul de
reformatat de la intrarea standard şi îl depune în
RecipientCuvinte;
• ConsumatorCuvinte, este un thread care extrage pe rând cuvinte din
RecipientCuvinte, le pune în linii şi distanţează uniform
cuvintele între ele;
• AliniereCuvinte, care este programul principal.
Vom prezenta pe scurt fiecare dintre clase.

53
1.6.4.1. Clasa RecipientCuvinte

Clasa RecipientCuvinte foloseşte un Vector pentru a memora


maximum RECIPIENT cuvinte. Constructorul primeşte şi reţine valoarea
RECIPIENT. (Se ştie că tipul Vector nu impune limitări asupra numărului de
obiecte din el. Noi am introdus această limitare numai pentru a rămâne în
condiţiile problemei clasice a producătorului şi a consumatorului.)
Metoda get, asigură, în regim de monitor, livrarea următorului cuvânt
din recipient. Dacă nu are cuvânt de livrat, atunci thread-ul solicitant este pus în
aşteptare până când producătorul livrează (cel puţin) un cuvânt.
Metoda put, asigură, în regim de monitor, preluarea de la producător a
următorului cuvânt şi depunerea lui în recipient. Dacă nu mai este spaţiu (am
limitat spaţiul doar cu scop didactic), atunci thread-ul producător este pus în
aşteptare până când consumatorul extrage (cel puţin) un cuvânt.
Textul sursă al clasei este prezentat în programul 1.12.

import java.util.*;

class RecipientCuvinte {
private Vector b = new Vector();
private String continut;
private int RECIPIENT;

public RecipientCuvinte(int recipient) {


RECIPIENT = recipient;
} // RecipientCuvinte.RecipientCuvinte

public synchronized String get() { // Extrage urmatorul cuvant din recipient,


// daca are ce extrage
while (b.size()==0) {
try { wait();
} // try
catch (Exception e) {
e.printStackTrace();
} // catch
} // while
continut = (String)b.elementAt(0); // Extrage cuvantul
b.remove(0); // Sterge-l
notify();
return continut;
} // RecipientCuvinte.get

public synchronized void put(String continut) { // Depune inca un


// cuvant in recipient, daca are loc
while (b.size()>=RECIPIENT) {
try { wait();
} // try
catch (Exception e) {

54
e.printStackTrace();
} // catch
} // while
b.add(continut);
notify();
} // RecipientCuvinte.put

} // RecipientCuvinte

Programul 1.12. RecipientCuvinte.java

1.6.4.2. Clasa ProducătorCuvinte

ProducatorCuvinte este un thread care citeşte linii dintr-un fişier,


le desparte în cuvinte şi depune cuvintele în recipient. Constructorul thread-ului
primeşte şi reţine referinţa la recipient şi la fişierul de unde îşi citeşte liniile.
Metoda run, corpul thread-ului, citeşte fişierul linie după linie. La
întâlnirea unei linii goale sau a unei linii care începe cu caracterul '\t' (TAB),
consideră că paragraful precedent s-a terminat şi introduce, numai pentru uzul
lui ConsumatorCuvinte, caracterul '\t' ca şi marcaj de terminare a
paragrafului.
La întâlnirea sfârşitului de fişier, de asemenea numai pentru uzul lui
ConsumatorCuvinte, introduce '\n' ca şi marcaj de sfârşit de fişier.
În mod normal, fiecare linie este împărţită în cuvinte cu ajutorul unui
obiect StringTokenizer, iar apoi fiecare dintre cuvinte este depus în
recipient cu ajutorul metodei put a acestuia. Este posibil (şi probabil) ca din
când în când thread-ul să aştepte extragerea de către ConsumatorCuvinte a
unui cuvânt din recipient înainte de a depune cuvântul curent.
Sursa clasei este prezentată în proogramul 1.13.

import java.util.*;
import java.io.*;

class ProducatorCuvinte extends Thread {


private RecipientCuvinte r;
private BufferedReader in;

ProducatorCuvinte(RecipientCuvinte r, BufferedReader in) {


this.r = r;
this.in = in;
} // ProducatorCuvinte.ProducatorCuvinte

public void run() {


String s = "";
StringTokenizer st;
r.put("\t");
for (;;) {

55
try {
s = in.readLine();
} // try
catch (Exception e) {
e.printStackTrace();
} // catch
if (s == null)
break; // Sfarsit fisier intrare
if (s.equals("")) {
r.put("\t"); // Depune marcaj de paragraf
continue;
} // if
if (s.charAt(0) == '\t')
r.put("\t"); // Depune marcaj de paragraf
st = new StringTokenizer(s.trim());
for ( ; st.hasMoreTokens(); )
r.put(st.nextToken()); // Depune cuvant
} // for
r.put("\n"); // Depune sfarsit de fisier
} // ProducatorCuvinte.run

} // ProducatorCuvinte

Programul 1.13. ProducătorCuvinte.java

1.6.4.3. Clasa ConsumatorCuvinte

ConsumatorCuvinte este un thread care extrage succesiv cuvinte


din recipient, le aranjează (frumos) în linii şi le depune pe fişierul de ieşire.
Constructorul thread-ului primeşte şi reţine referinţa la recipient, referinţa la
fişierul de ieşire şi lungimea curentă a liniei de ieşire.
Metoda run, corpul thread-ului, citeşte din recipient cuvânt după
cuvânt, folosind metoda get şi reţine într-un string cuvintele citite, unul după
altul, separate printr-un spaţiu. La întâlnirea unui caracter '\t' (TAB), care
semnifică sfârşit de paragraf, scrie la ieşire ultima linie a paragrafului.
La întâlnirea caracterului '\n' (marcaj de sfârşit de fişier), scrie ultima
linie (a ultimului paragraf) şi thread-ul se termină.
În celelalte cazuri, verifică dacă următorul cuvânt mai încape în linia
curentă (nu a depăşit încă LUNGIME). În caz afirmativ depune cuvântul în linia
de ieşire. Dacă cuvântul curent nu mai încape, atunci transmite linia curentă
(fără cuvântul curent) metodei distantare care aliniază cuvintele din linie.
Alinierea se face plasând primul cuvânt aliniat la stânga în linie, ultimul cuvânt
aliniat la dreapta, iar celelalte cuvinte sunt deplasate aşa încât spaţiile dintre ele
să fie plasate cât mai uniform.

56
Extragerile din recipient se fac prin invocarea metodei get a recipien-
tului. Este posibil (şi probabil) ca din când în când thread-ul să aştepte extra-
gerea din recipient înainte de a depune.
Sursa clasei este prezentată în programul 1.14.

import java.io.*;
import java.util.*;

class ConsumatorCuvinte extends Thread {


private RecipientCuvinte r;
private PrintStream out;
private int LUNGIME;

ConsumatorCuvinte(RecipientCuvinte r, PrintStream out,


int lungime) {
this.r = r;
this.out = out;
LUNGIME = lungime;
} // ConsumatorCuvinte.ConsumatorCuvinte

private String distantare(String s) {


int i, nrSeparatori, nrSeparatoriMaiLungi=0,
pas=0, start, x;
String separator;
StringTokenizer st;
for (i=0, nrSeparatori=0; i<s.length(); i++)
if (s.charAt(i) == ' ')
nrSeparatori++;
if (nrSeparatori == 0)
return s;
nrSeparatoriMaiLungi =
(LUNGIME - s.length()) % nrSeparatori;
if (nrSeparatoriMaiLungi != 0)
pas = nrSeparatori / nrSeparatoriMaiLungi;
start = pas / 2;
x = (pas+1)*(nrSeparatoriMaiLungi-1)+1;
if (x <= nrSeparatori) {
pas++;
start = (nrSeparatori - x) /2;
} // if
for (i=0, separator=" ";
i<(LUNGIME-s.length())/nrSeparatori;
i++, separator+= " ");
st = new StringTokenizer(s);
for (i=0; st.hasMoreTokens(); i++)
if (i==0)
s = st.nextToken();
else if (nrSeparatoriMaiLungi == 0)
s += separator+st.nextToken();
else if ((((i-1)%pas)!=start))
s += separator+st.nextToken();
else {

57
s += " "+separator+st.nextToken();
nrSeparatoriMaiLungi--;
} // if
return s;
} // ConsumatorCuvinte.distantare

public void run() {


String linie = "", cuvant;
for (; ; ) {
cuvant = r.get();
if (cuvant.equals("\n"))
break; // Sfarsitul intrarii de cuvinte
if (cuvant.equals("\t")) {
if (linie.equals(""))
continue;
out.println(linie);
out.println(); // Încheie vechiul paragraf
linie = "";
continue;
} // if
if (linie.length()+1+cuvant.length() >= LUNGIME) {
out.println(distantare(linie));
// Scrie o linie din paragraf
linie = "";
} // if
if (linie.equals(""))
linie = cuvant;
else
linie += " "+cuvant;
} // for
out.println(linie);
} // ConsumatorCuvinte.run

} // ConsumatorCuvinte

Programul 1.14. ConsumatorCuvinte.java

1.6.4.4. Clasa AliniereCuvinte

Clasa principală a programului este AliniereCuvinte. Mai întâi se


definesc constantele lungime a unei linii de ieşire şi capacitate a recipientului.
Apoi se definesc şi construiesc câte un obiect recipient, un thread producător şi
unul consumator. Intrarea pentru producător este intrarea standard, iar ieşirea
pentru consumator este ieşirea standard. Programul se încheie cu lansarea în
lucru a celor două thread-uri.
Sursa clasei este prezentată în programul 1.15.

58
import java.io.*;

class AliniereCuvinte {
final public int LUNGIME = 60, RECIPIENT = 100;
RecipientCuvinte r;
ProducatorCuvinte p;
ConsumatorCuvinte c;

public static void main(String[] a) {


AliniereCuvinte ac = new AliniereCuvinte();
ac.r = new RecipientCuvinte(ac.RECIPIENT);
ac.p = new ProducatorCuvinte(ac.r,
new BufferedReader(
new InputStreamReader(System.in)));
ac.c = new ConsumatorCuvinte(ac.r, System.out, ac.LUNGIME);
ac.p.start();
ac.c.start();

} // AliniereCuvinte.main

} // AliniereCuvinte

Programul 1.15. AliniereCuvinte.java

1.7. Procese externe lansate din Java


Facilităţile independente de platformă oferite de limbajul Java sunt
completate de posibilitatea utilizării de cod extern, specific platformei. Este
vorba fie de includerea în codul Java a unor funcţii native, fie de existenţa unei
interfeţe simple la aplicaţii şi utilitare specifice platformei gazdă. În acest
paragraf, vom prezenta cea de-a doua facilitate dependentă de platformă.
Astfel, limbajul Java permite lansarea în execuţie din interiorul unui
program, a unei aplicaţii executabile externe JVM. De exemplu, se poate lansa
un fişier de tip .bat sau .exe sub Windows, sau un fişier shell, sub Unix.
Interfaţa de execuţie a unor comenzi externe din programe Java, este realizată
cu ajutorul metodei statice exec, din clasa Runtime [91]. Există patru
variante de apel ale acestei metode, prezentate în continuare:
public Process exec (String command);
public Process exec (String [] cmdArray);
public Process exec (String command, String [] envp);
public Process exec (String [] cmdArray, String [] envp);

Metoda exec primeşte ca parametru un şir (sau mai multe şiruri) de


caractere, reprezentând comanda (comenzile) externă de executat. Dacă este
prezent al doilea parametru, acest şir de caractere conţine setările variabilelor de
mediu, sub forma nume=valoare.

59
Metoda exec lansează un thread separat pentru execuţia comenzii
(comenzilor) respective şi returnează un obiect de tip Process, asociat
procesului creat, astfel, în sistem. Dacă aplicaţia lansată din programul Java are
nevoie de date de intrare, acestea sunt transmise aplicaţiei externe cu ajutorul
obiectului OutputStream, ataşat obiectului Process:
OutputStream getOutputStream();

Similar, eventualele date de ieşire ale aplicaţiei, se pot obţine folosind


obiectul InputStream, al aceluiaşi obiect Process:
InputStream getInputStream();

Se recomandă ca înainte de a obţine rezultatele aplicaţiei, să se aştepte


terminarea procesului creat folosind metoda waitFor(), din clasa Process.
De asemenea, codul de retur al aplicaţiei se poate obţine cu ajutorul metodei
exitValue(). Dacă întregul întors are valoarea 0, atunci aplicaţia s-a
terminat cu succes, altfel a apărut o eroare.
În programul 1.16 prezentăm un exemplu de program Java, care afişează
conţinutul directorului curent folosind o comandă externă, mai precis un fişier
de comenzi shell.

import java.io.*;

public class ExecJava {

static String com="fisier.bat";


static int SMAX=1000;

public static void main(String argv[]) {


try {
Process p;
p=Runtime.getRuntime().exec(com);
p.waitFor();

// obtine codul de retur al aplicatiei externe


int exval=p.exitValue();
System.out.println(exval);

// obtine datele de iesire


InputStream in=p.getInputStream();
byte blin[]=new byte[SMAX];
int noct=in.read(blin,0,SMAX);
String s=new String(blin,0,noct);
System.out.println(s);
}
catch(IOException e1) {
System.out.println(e1);
}
catch(InterruptedException e2){

60
System.out.println(e2);}
}// ExecJava.main

}// ExecJava

Programul 1.16. ExecJava.java

Fişierul fisier.bat, dacă este sub Windows poate conţine numai


comenzi DOS externe! Comenzile interne, cum ar fi dir, copy etc. nu pot fi
rulate, deoarece acestea presupun încărcarea fişierului DOS command.com.
Pentru execuţia unui fişier shell sub Unix dintr-un program Java este
obligatorie includerea, ca prim comentariu în fişier, a specificării interpretorului
de comenzi folosit. De exemplu, pentru un fişier executat de către shell-ul
Bourne (sh) [65, 75, 99], prima linie din fişier va fi:
# !/bin/sh

Folosind facilitatea de apel a unor comenzi externe din programe Java,


se pot compila şi executa noi programe Java, din interiorul altor programe.
Compilarea şi/sau execuţia acestor noi programe se realizează dinamic, în
momentul apelurilor de metode exec.
Iată, spre exemplu, partea esenţială dintr-un program Java care lansează
din interiorul lui o compilare Java, după care lansează crearea unei arhive jar
din fişierul class obţinut:
String[] javac = {"javac", "FisierProgram.java"};
Process p=Runtime.getRuntime().exec(javac);
p.waitFor();
String[] jar = {"jar", "cf", "FisierProgram.jar",
"FisierProgram.class"};
p=Runtime.getRuntime().exec(jar);
p.waitFor();

61

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