Capitolul
V
5.3 Instrucțiuni
5.3.1 Blocuri de instrucțiuni
5.3.1.1 Declarații de variabile locale
5.3.2 Tipuri de instrucțiuni
5.3.2.1 Instrucțiuni de atribuire
5.3.2.1.1 Atribuire cu operație
5.3.2.1.2 Atribuiri implicite
5.3.2.2 Instrucțiuni etichetate
5.3.2.3 Instrucțiuni condiționale
5.3.2.3.1 Instrucțiunea if
5.3.2.3.2 Instrucțiunea switch
5.3.2.4 Instrucțiuni de ciclare
5.3.2.4.1 Instrucțiunea while
5.3.2.4.2 Instrucțiunea do
5.3.2.4.3 Instrucțiunea for
5.3.2.5 Instrucțiuni de salt
5.3.2.5.1 Instrucțiunea break
5.3.2.5.2 Instrucțiunea continue
5.3.2.5.3 Instrucțiunea return
5.3.2.5.4 Instrucțiunea throw
5.3.2.6 Instrucțiuni de protecție
5.3.2.6.1 Instrucțiunea try
5.3.2.6.2 Instrucțiunea synchronized
5.3.2.7 Instrucțiunea vidă
5.3.1 Blocuri de instrucțiuni
Un bloc de instrucțiuni este o secvență, eventual vidă, de instrucțiuni și
declarații de variabile locale. Aceste instrucțiuni se execută în ordinea în care
apar în interiorul blocului. Sintactic, blocurile de instrucțiuni sunt delimitate în
sursă de caracterele { și }.
În limbajul Java, regula generală este aceea că oriunde putem pune o instrucțiune
putem pune și un bloc de instrucțiuni, cu câteva excepții pe
care le vom sesiza la momentul potrivit, specificând în acest fel că instrucțiunile
din interiorul blocului trebuiesc privite în mod unitar și tratate ca o singură
instrucțiune.
5.3.1.1 Declarații de variabile locale
O declarație de variabilă locală introduce o nouă variabilă care poate fi
folosită doar în interiorul blocului în care a fost definită. Declarația trebuie să
conțină un nume și un tip. În plus, într-o declarație putem specifica o valoare
inițială în cazul în care valoarea implicită a tipului variabilei, definită standard
de limbajul Java, nu ne satisface.
Numele variabilei este un identificator Java. Acest nume trebuie să fie diferit de
numele celorlalte variabile locale definite în blocul respectiv și de eventualii
parametri ai metodei în interiorul căreia este declarat blocul.
De exemplu, este o eroare de compilare să declarăm cea de-a doua variabilă x
în blocul:
{
int x = 3;
{
int x = 5;
}
}
Compilatorul va semnala faptul că deja există o variabilă cu acest nume în
interiorul metodei. Eroarea de compilare apare indiferent dacă cele două variabile sunt
de același tip sau nu.
Nu același lucru se întâmplă însă dacă variabilele sunt declarate în două
blocuri de instrucțiuni complet disjuncte, care nu se includ unul pe celălalt. De
exemplu, declarațiile următoare sunt perfect valide:
{
{
int x = 3;
}
{
int x = 5;
}
}
Practic, fiecare bloc de instrucțiuni definește un domeniu de existență a
variabilelor locale declarate în interior. Dacă un bloc are subblocuri declarate în
interiorul lui, variabilele din aceste subblocuri trebuie să fie distincte ca nume față
de variabilele din superbloc. Aceste domenii se pot reprezenta grafic ca în figura
următoare:
Figura 5.4 Reprezentarea grafică a domeniilor de existență a
variabilelor incluse unul într-altul.
Figura reprezintă situația din primul exemplu. Observați că domeniul blocului
interior este complet inclus în domeniul blocului exterior. Din această cauză, x
apare ca fiind definită de două ori. În cel de-al doilea exemplu, reprezentarea
grafică este următoarea:
Figura 5.5 Reprezentarea grafică a domeniilor de existență a
variabilelor disjuncte.
În acest caz, domeniile în care sunt definite cele două variabile sunt complet
disjuncte și compilatorul nu va avea nici o dificultate în a identifica la fiecare
referire a unei variabile cu numele x despre care variabilă este vorba. Să mai
observăm că, în cel de-al doilea exemplu, referirea variabilelor x în blocul
mare, în afara celor două subblocuri este o eroare de compilare.
În cazul în care blocul mare este blocul de implementare al unei metode și metoda
respectivă are parametri, acești parametri sunt luați în considerare la fel ca niște
declarații de variabile care apar chiar la începutul blocului. Acest lucru face ca
numele parametrilor să nu poată fi folosit în nici o declarație de variabilă locală
din blocul de implementare sau subblocuri ale acestuia.
În realitate, o variabilă locală poate fi referită în interiorul unui bloc abia
după declarația ei. Cu alte cuvinte, domeniul de existență al unei variabile locale
începe din punctul de declarație și continuă până la terminarea blocului.
Astfel, următoarea secvență de instrucțiuni:
{
x = 4;
int x = 3;
}
va determina o eroare de compilare, cu specificația că variabila x nu a fost
încă definită în momentul primei atribuiri.
Acest mod de lucru cu variabilele face corectă următoarea secvență de
instrucțiuni:
{
{
int x = 4;
}
int x = 3;
}
pentru că, în momentul declarației din interiorul subblocului, variabila x
din exterior nu este încă definită. La ieșirea din subbloc, prima declarație își
termină domeniul, așa că se poate defini fără probleme o nouă variabilă cu același
nume.
5.3.2 Tipuri de instrucțiuni
5.3.2.1 Instrucțiuni de atribuire
O atribuire este o setare de valoare într-o locație de memorie. Forma generală a
unei astfel de instrucțiuni este:
Locație = valoare ;
Specificarea locației se poate face în mai multe feluri. Cel mai simplu este să
specificăm un nume de variabilă locală. Alte alternative sunt acelea de a specifica un
element dintr-un tablou de elemente sau o variabilă care nu este declarată finală din
interiorul unei clase sau numele unui parametru al unei metode.
Valoarea care trebuie atribuită poate fi un literal sau rezultatul evaluării unei
expresii.
Instrucțiunea de atribuire are ca rezultat chiar valoarea atribuită. Din această
cauză, la partea de valoare a unei operații de atribuire putem avea chiar o altă
operație de atribuire ca în exemplul următor:
int x = 5;
int y = 6;
x = ( y = y / 2 );
Valoarea finală a lui x va fi identică cu valoarea lui y și va fi
egală cu 3. Parantezele de mai sus sunt puse doar pentru claritatea codului,
pentru a înțelege exact care este ordinea în care se execută atribuirile. În
realitate, ele pot să lipsească pentru că, în mod implicit, Java va executa întâi
atribuirea din dreapta. Deci, ultima linie se poate rescrie ca:
x = y = y / 2;
De altfel, gruparea inversă nici nu are sens:
( x = y ) = y / 2;
Această instrucțiune va genera o eroare de compilare pentru că, după executarea
atribuirii din paranteze, rezultatul este valoarea 6 iar unei valori nu i se
poate atribui o altă valoare.
În momentul atribuirii, dacă valoarea din partea dreaptă a operației nu este de
același tip cu locația din partea stângă, compilatorul va încerca conversia valorii
la tipul locației în modul pe care l-am discutat în paragraful referitor la conversii.
Această conversie trebuie să fie posibilă, altfel compilatorul va semnala o eroare.
Pentru anumite conversii, eroarea s-ar putea să nu poată fi depistată decât în
timpul execuției. În aceste cazuri, compilatorul nu va semnala eroare dar va fi
semnalată o eroare în timpul execuției și rularea programului va fi abandonată.
5.3.2.1.1 Atribuire cu operație
Se întâmplă deseori ca valoarea care se atribuie unei locații să depindă de
vechea valoare care era memorată în locația respectivă. De exemplu, în
instrucțiunea:
int x = 3;
x = x * 4;
vechea valoare a lui x este înmulțită cu 4 și rezultatul
înmulțirii este memorat înapoi în locația destinată lui x. Într-o astfel de
instrucțiune, calculul adresei locației lui x este efectuat de două ori, o dată
pentru a lua vechea valoare și încă o dată pentru a memora noua valoare. În
realitate, acest calcul nu ar trebui executat de două ori pentru că locația variabilei x
nu se schimbă în timpul instrucțiunii.
Pentru a ajuta compilatorul la generarea unui cod eficient, care să calculeze locația
lui x o singură dată, în limbajul Java au fost introduse instrucțiuni mixte de
calcul combinat cu atribuire. În cazul nostru, noua formă de scriere, mai eficientă,
este:
int x = 3;
x *= 4;
Eficiența acestei exprimări este cu atât mai mare cu cât calculul locației este
mai complicat. De exemplu, în secvența următoare:
int x = 3, y = 5;
double valori[] = new double[10];
valori[( x + y ) / 2] += 3.5;
calculul locației de unde se va lua o valoare la care se va aduna 3.5 și
unde se va memora înapoi rezultatul acestei operații este efectuat o singură dată. În
acest exemplu, calcului locației presupune execuția expresiei:
( x + y ) / 2
și indexarea tabloului numit valori cu valoarea rezultată. Valoarea din partea
dreaptă poate fi o expresie arbitrar de complicată, ca în:
valori[x] += 7.0 * ( y * 5 );
Iată toți operatorii care pot fi mixați cu o atribuire:
*=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, |=, ^= ^
5.3.2.1.2 Atribuiri implicite
Să mai observăm că, în cazul folosirii operatorilor de incrementare și
decrementare se face și o atribuire implicită pentru că pe lângă valoarea rezultată
în urma operației, se modifică și valoarea memorată la locația pe care se aplică
operatorul. La fel ca și la operațiile mixte de atribuire, calculul locației asupra
căreia se aplică operatorul se efectuează o singură dată.
5.3.2.2 Instrucțiuni etichetate
Unele dintre instrucțiunile din interiorul unui bloc de instrucțiuni trebuie referite
din altă parte a programului pentru a se putea direcționa execuția spre aceste
instrucțiuni. Pentru referirea acestor instrucțiuni, este necesar ca ele să aibă o etichetă
asociată. O etichetă este deci un nume dat unei instrucțiuni din program prin care
instrucțiunea respectivă poate fi referită din alte părți ale programului.
Eticheta cea mai simplă este un simplu identificator urmat de caracterul :
și de instrucțiunea pe care dorim să o etichetăm:
Etichetă: Instrucțiune
ca în exemplul următor:
int a = 5;
Eticheta: a = 3;
Pentru instrucțiunea switch, descrisă mai jos, sunt definite două
moduri diferite de etichetare. Acestea au forma:
case Valoare: Instrucțiune
default: Instrucțiune
Exemple pentru folosirea acestor forme sunt date la definirea instrucțiunii.
5.3.2.3 Instrucțiuni condiționale
Instrucțiunile condiționale sunt instrucțiuni care selectează pentru execuție o
instrucțiune dintr-un set de instrucțiuni în funcție de o anumită condiție.
Instrucțiunea if primește o expresie a cărei valoare este
obligatoriu de tipul boolean. Evaluarea acestei expresii poate duce la doar două valori:
adevărat sau fals. În funcție de valoarea rezultată din această evaluare, se execută
unul din două seturi de instrucțiuni distincte, specificate de instrucțiunea if.
Sintaxa acestei instrucțiuni este următoarea:
if( Expresie ) Instrucțiune1 [else Instrucțiune2]
După evaluarea expresiei booleene, dacă valoarea rezultată este true,
se execută instrucțiunea 1.
Restul instrucțiunii este opțional, cu alte cuvinte partea care începe cu cuvântul
rezervat else poate să lipsească. În cazul în care această parte nu
lipsește, dacă rezultatul evaluării expresiei este false se va executa
instrucțiunea 2.
Indiferent de instrucțiunea care a fost executată în interiorul unui if,
după terminarea acesteia execuția continuă cu instrucțiunea de după instrucțiunea if,
în afară de cazul în care instrucțiunile executate conțin în interior o
instrucțiune de salt.
Să mai observăm că este posibil ca într-o instrucțiune if să nu
se execute nici o instrucțiune în afară de evaluarea expresiei în cazul în care
expresia este falsă iar partea else din instrucțiunea if
lipsește. Expresia booleană va fi întotdeauna evaluată.
Iată un exemplu de instrucțiune if în care partea else
lipsește:
int x = 3;
if( x == 3 )
x *= 7;
și iată un exemplu în care sunt prezente ambele părți:
int x = 5;
if( x % 2 == 0 )
x = 100;
else
x = 1000;
Instrucțiunea 1 nu poate lipsi niciodată. Dacă totuși pe ramura de adevăr a
instrucțiunii condiționale nu dorim să executăm nimic, putem folosi o instrucțiune
vidă, după cum este arătat puțin mai departe.
În cazul în care dorim să executăm mai multe instrucțiuni pe una dintre ramurile
instrucțiunii if, putem să în locuim instrucțiunea 1 sau 2 sau pe
amândouă cu blocuri de instrucțiuni, ca în exemplul următor:
int x = 5;
if( x == 0 ) {
x = 3;
y = x * 5;
} else {
x = 5;
y = x * 7;
}
Expresia booleană poate să fie și o expresie compusă de forma:
int x = 3;
int y = 5;
if( y != 0 && x / y == 2 )
x = 4;
În acest caz, așa cum deja s-a specificat la descrierea operatorului &&,
evaluarea expresiei nu este terminată în toate situațiile. În exemplul nostru, dacă
y este egal cu 0, evaluarea expresiei este oprită și rezultatul este fals.
Această comportare este corectă pentru că, dacă un termen al unei operații logice de
conjuncție este fals, atunci întreaga conjuncție este falsă.
În plus, acest mod de execuție ne permite să evităm unele operații cu rezultat
incert, în cazul nostru o împărțire prin 0. Pentru mai multe informații
relative la operatorii && și || citiți
secțiunea destinată operatorilor.
5.3.2.3.2 Instrucțiunea switch
Instrucțiunea switch ne permite saltul la o anumită instrucțiune
etichetată în funcție de valoarea unei expresii. Putem să specificăm câte o
etichetă pentru fiecare valoare particulară a expresiei pe care dorim să o
diferențiem. În plus, putem specifica o etichetă la care să se facă saltul implicit,
dacă expresia nu ia nici una dintre valorile particulare specificate.
Sintaxa instrucțiunii este:
switch( Expresie ) {
[case ValoareParticulară: Instrucțiuni;]*
[default: InstrucțiuniImplicite;]
}
Execuția unei instrucțiuni switch începe întotdeauna prin evaluarea
expresiei dintre parantezele rotunde. Această expresie trebuie să aibă tipul caracter,
octet, întreg scurt sau întreg. După evaluarea expresiei se trece la compararea valorii
rezultate cu valorile particulare specificate în etichetele case din
interiorul blocului de instrucțiuni. Dacă una dintre valorile particulare este egală cu
valoarea expresiei, se execută instrucțiunile începând de la eticheta case
corespunzătoare acelei valori în jos, până la capătul blocului. Dacă nici una dintre
valorile particulare specificate nu este egală cu valoarea expresiei, se execută
instrucțiunile care încep cu eticheta default, dacă aceasta există.
Iată un exemplu de instrucțiune switch:
int x = 4;
int y = 0;
switch( x + 1 ) {
case 3:
x += 2;
y++;
case 5:
x = 11;
y++;
default:
x = 4;
y += 3;
}
Dacă valoarea lui x în timpul evaluării expresiei este 2 atunci
expresia va avea valoarea 3 și instrucțiunile vor fi executate una după alta
începând cu cea etichetată cu case 3. În ordine, x va deveni 4,
y va deveni 1, x va deveni 11, y va deveni 2,
x va deveni 4 și y va deveni 5.
Dacă valoarea lui x în timpul evaluării expresiei este 4 atunci
expresia va avea valoarea 5 și instrucțiunile vor fi executate pornind de la
cea etichetată cu case 5. În ordine, x va deveni 11,
y va deveni 1, x va deveni 4 și y va deveni 4.
În fine, dacă valoarea lui x în timpul evaluării expresiei este diferită de
2 și 4, se vor executa instrucțiunile începând cu cea etichetată cu
default. În ordine, x va deveni 4 și y va deveni 3.
Eticheta default poate lipsi, caz în care, dacă nici una dintre
valorile particulare nu este egală cu valoarea expresiei, nu se va executa nici o
instrucțiune din bloc.
În cele mai multe cazuri, această comportare a instrucțiunii switch
nu ne convine, din cauza faptului că instrucțiunile de după cea etichetată cu case
5 se vor executa și dacă valoarea este 3. La fel, instrucțiunile de
după cea etichetată cu default se execută întotdeauna. Pentru a
schimba această comportare trebuie să folosim una dintre instrucțiunile de salt care
să oprească execuția instrucțiunilor din bloc înainte de întâlnirea unei noi
instrucțiuni etichetate.
Putem de exemplu folosi instrucțiunea de salt break care va opri
execuția instrucțiunilor din blocul switch. De exemplu:
char c = '\t';
String mesaj = "nimic";
switch( c ) {
case '\t':
mesaj = "tab";
break;
case '\n':
mesaj = "linie noua";
break;
case '\r':
mesaj = "retur";
default:
mesaj = mesaj + " de";
mesaj = mesaj + " car";
}
În acest caz, dacă c este egal cu caracterul tab, la terminarea instrucțiunii
switch, mesaj va avea valoarea "tab". În cazul
în care c are valoarea CR, mesaj va deveni mai întâi "retur"
apoi "retur de" și apoi "retur de car". Lipsa lui break
după instrucțiunea
mesaj = "retur";
face ca în continuare să fie executate și instrucțiunile de după cea etichetată
cu default.
5.3.2.4 Instrucțiuni de ciclare
Instrucțiunile de ciclare (sau ciclurile, sau buclele) sunt necesare atunci când
dorim să executăm de mai multe ori aceeași instrucțiune sau același bloc de
instrucțiuni. Necesitatea acestui lucru este evidentă dacă ne gândim că programele
trebuie să poată reprezenta acțiuni de forma: execută 10 întoarceri, execută 7
genoflexiuni, execută flotări până ai obosit, etc.
Desigur, sintaxa instrucțiunilor care se execută trebuie să fie aceeași, pentru că
ele vor fi în realitate scrise în Java o singură dată. Totuși, instrucțiunile nu
sunt neapărat aceleași. De exemplu, dacă executăm în mod repetat instrucțiunea:
int tablou[] = new int[10];
int i = 0;
tablou[i++] = 0;
în realitate se va memora valoarea 0 în locații diferite pentru că
variabila care participă la calculul locației își modifică la fiecare iterație
valoarea. La primul pas, se va face 0 primul element din tablou și în același
timp i va primi valoarea 1. La al doilea pas, se va face 0 al
doilea element din tablou și i va primi valoarea 2, și așa mai departe.
Lucrurile par și mai clare dacă ne gândim că instrucțiunea executată în mod
repetat poate fi o instrucțiune if. În acest caz, În funcție de
expresia condițională din if se poate executa o ramură sau alta a
instrucțiunii.
De exemplu instrucțiunea din buclă poate fi:
int i = 0;
if( i++ % 2 == 0 )
else
În acest caz, i este când par când impar și se execută alternativ cele
două ramuri din if. Desigur, comportarea descrisă este valabilă dacă
valoarea lui i nu este modificată în interiorul uneia dintre ramuri.
5.3.2.4.1 Instrucțiunea while
Această instrucțiune de buclare se folosește atunci când vrem să executăm o
instrucțiune atâta timp cât o anumită expresie condițională rămâne adevărată.
Expresia condițională se evaluează și testează înainte de execuția instrucțiunii,
astfel că, dacă expresia era de la început falsă, instrucțiunea nu se mai executa
niciodată.
Sintaxa acestei instrucțiuni este:
while( Test ) Corp
Test este o expresie booleană iar Corp este o instrucțiune normală,
eventual vidă. Dacă avem nevoie să repetăm mai multe instrucțiuni, putem înlocui
corpul buclei cu un bloc de instrucțiuni.
Iată și un exemplu:
int i = 0;
int tablou[] = new int[20];
while( i < 10 )
tablou[i++] = 1;
Bucla while de mai sus se execută de 10 ori primele 10 elemente din
tablou fiind inițializate cu 1. În treacăt fie spus, celelalte rămân la
valoarea 0 care este valoarea implicită pentru întregi. După cei 10 pași
iterativi, i devine 10 și testul devine fals (10 < 10).
În exemplul următor, corpul nu se execută nici măcar o dată:
int i = 3;
while( i < 3 )
i++;
Putem să creăm un ciclu infinit (care nu se termină niciodată) prin:
while( true )
;
Întreruperea execuției unui ciclu infinit se poate face introducând în corpul
ciclului o instrucțiune de salt.
Buclele do se folosesc atunci când testul de terminare a buclei trebuie făcut după
execuția corpului buclei. Sintaxa de descriere a instrucțiuni do este:
do Corp while( Test ) ;
Test este o expresie booleană iar Corp este o instrucțiune sau un bloc
de instrucțiuni. Execuția acestei instrucțiuni înseamnă execuția corpului în mod
repetat atâta timp cât expresia Test are valoarea adevărat. Testul se evaluează
după execuția corpului, deci corpul se execută cel puțin o dată.
De exemplu, în instrucțiunea:
int i = 3;
do
i++;
while( false );
valoarea finală a lui i este 4, pentru că instrucțiunea i++
care formează corpul buclei se execută o dată chiar dacă testul este întotdeauna
fals.
În instrucțiunea:
int i = 1;
do {
tablou[i] = 0;
i += 2;
} while( i < 5 );
sunt setate pe 0 elementele 1 și 3 din tablou. După a doua iterație, i devine
5 și testul eșuează cauzând terminarea iterației.
Instrucțiunea for se folosește atunci când putem identifica foarte
clar o parte de inițializare a buclei, testul de terminare a buclei, o parte de reluare a
buclei și un corp pentru buclă. În acest caz, putem folosi sintaxa:
for( Inițializare Test ; Reluare ) Corp
Corp și Inițializare sunt instrucțiuni normale. Test este o expresie booleană iar
Reluare este o instrucțiune căreia îi lipsește caracterul ; final.
Execuția unei bucle for începe cu execuția instrucțiunii de inițializare. Această
instrucțiune stabilește de obicei niște valori pentru variabilele care controlează
bucla. Putem chiar declara aici noi variabile. Aceste variabile există doar în
interiorul corpului buclei și în instrucțiunile de test și reluare ale buclei.
În partea de inițializare nu putem scrie decât o singură instrucțiune fie ea
declarație sau instrucțiune normală. În acest caz instrucțiunea nu se poate înlocui
cu un bloc de instrucțiuni. Putem însă să declarăm două variabile cu o sintaxă de
forma:
int i = 0, j = 1;
După execuția părții de inițializare se pornește bucla propriu-zisă. Aceasta
constă din trei instrucțiuni diferite executate în mod repetat. Cele trei instrucțiuni
sunt testul, corpul buclei și instrucțiunea de reluare. Testul trebuie să fie o
expresie booleană. Dacă aceasta este evaluată la valoarea adevărat, bucla continuă cu
execuția corpului, a instrucțiunii de reluare și din nou a testului. În clipa în care
testul are valoarea fals, bucla este oprită fără să mai fie executat corpul sau
reluarea.
Iată un exemplu:
int x = 0;
for( int i = 3; i < 30; i += 10 )
x += i;
În această buclă se execută mai întâi crearea variabilei i și
inițializarea acesteia cu 3. După aceea se testează variabila i dacă
are o valoare mai mică decât 30. Testul are rezultat adevărat (i este 0
< 30) și se trece la execuția corpului unde x primește valoarea 3 (0
+ 3). În continuare se execută partea de reluare în care i este crescut cu 10,
devenind 13. Se termină astfel primul pas al buclei și aceasta este reluată
începând cu testul care este în continuare adevărat (13 < 30). Se execută corpul, x
devenind 16 (3 + 13), și reluarea, unde x devine 23 (13 + 10).
Se reia bucla de la test care este în continuare adevărat (23 < 30). Se execută
corpul unde x devine 39 (16 + 23) și reluarea unde i devine 33
(23 + 10). Se reia testul care în acest caz devine fals (33 < 30) și se părăsește
bucla, continuându-se execuția cu prima instrucțiune de după buclă.
După ieșirea din buclă, variabila i nu mai există, deci nu se mai poate
folosi și nu putem vorbi despre valoarea cu care iese din buclă, iar variabila x
rămâne cu valoarea 39.
Pentru a putea declara variabila i în instrucțiunea de inițializare a buclei
for este necesar ca în blocurile superioare instrucțiunii for să nu existe o altă
variabilă i, să nu existe un parametru numit i și nici o etichetă cu
acest nume.
Dacă dorim un corp care să conțină mai multe instrucțiuni, putem folosi un bloc.
Nu putem face același lucru în partea de inițializare sau în partea de reluare.
Oricare dintre părțile buclei for în afară de inițializare poate
să lipsească. Dacă aceste părți lipsesc, se consideră că ele sunt reprezentate de
instrucțiunea vidă. Dacă nu dorim inițializare trebuie totuși să specificăm
implicit instrucțiunea vidă. Putem de exemplu să scriem o buclă infinită prin:
int x;
for( ;; )
x = 0;
Putem specifica funcționarea instrucțiunii for folosindu-ne de o instrucțiune while
în felul următor:
Inițializare
while( Test ) {
Corp
Reluare ;
}
În fine, schema următoare reprezintă funcționarea unei bucle for:
Figura 5.6 Schema de funcționare a buclei for.
5.3.2.5 Instrucțiuni de salt
Instrucțiunile de salt provoacă întreruperea forțată a unei bucle, a unui bloc de
instrucțiuni sau ieșirea dintr-o metodă. Instrucțiunile de salt sunt destul de
periculoase pentru că perturbă curgerea uniformă a programului ceea ce poate duce la o
citire și înțelegere eronată a codului rezultat.
5.3.2.5.1 Instrucțiunea break
Instrucțiunea break produce întreruperea unei bucle sau a unui bloc switch.
Controlul este dat instrucțiunii care urmează imediat după bucla întreruptă sau după
blocul instrucțiunii switch.
De exemplu în secvența:
int i = 1, j = 3;
int tablou[] = new int[10];
while( i < 10 ) {
tablou[i++] = 0;
if( i == j )
break;
}
i += 2;
sunt setate pe 0 elementele 1 și 2 ale tabloului. După a doua iterație i
devine 3 și testul i == j are valoarea adevărat, producându-se
execuția instrucțiunii break.
Instrucțiunea break cauzează ieșirea forțată din buclă, chiar
dacă testul buclei i < 10 este în continuare valid. La terminarea tuturor
instrucțiunilor de mai sus, i are valoarea 5.
În exemplul următor:
int i, j = 3;
int tablou[] = new tablou[10];
do {
tablou[i] = 0;
if( i++ >= j )
break;
} while( i < 10 );
sunt setate pe 0 elementele 0, 1, 2 și 3 din tablou. Dacă vă întrebați la
ce mai folosește testul buclei, atunci să spunem că este o măsură suplimentară de
siguranță. Dacă cumva j intră cu o valoare greșită, testul ne asigură în
continuare că nu vom încerca să setăm un element care nu există al tabloului. De
fapt, pericolul există încă, dacă valoarea inițială a lui i este greșită,
deci testul ar trebui mutat la început și bucla transformată într-un while,
ca cel de mai sus.
În cazul buclelor for, să mai precizăm faptul că la o ieșire
forțată nu se mai execută instrucțiunea de reluare.
Instrucțiunea break poate avea ca argument opțional o etichetă, ca
în:
break Identificator;
În acest caz, identificatorul trebuie să fie eticheta unei instrucțiuni care să
includă instrucțiunea break. Prin faptul că instrucțiunea include
instrucțiunea break, înțelegem că instrucțiunea break
apare în corpul instrucțiunii care o include sau în corpul unei instrucțiuni care se
găsește în interiorul corpului instrucțiunii care include instrucțiunea break.
Controlul revine instrucțiunii de după instrucțiunea etichetată cu identificatorul
specificat.
De exemplu, în instrucțiunile:
int i, j;
asta: while( i < 3 ) {
do {
i = j + 1;
if( i == 3 )
break asta;
} while( j++ < 3 );
}
j = 10;
instrucțiunea break termină bucla do și bucla while
controlul fiind dat instrucțiunii de după while, și anume atribuirea:
j = 10;
5.3.2.5.2 Instrucțiunea continue
Instrucțiunea continue permite reluarea unei bucle fără a mai
termina execuția corpului acesteia. Reluarea se face ca și cum corpul buclei tocmai a
fost terminat de executat. Bucla nu este părăsită.
De exemplu, în instrucțiunea:
int i;
while( i < 10 ) {
i++;
continue;
i++;
}
corpul buclei se execută de 10 ori, pentru că a doua incrementare a lui i nu
se execută niciodată. Execuția începe cu testul și apoi urmează cu prima
incrementare. După aceasta se execută instrucțiunea continue care duce
la reluarea buclei, pornind de la test și urmând cu incrementarea și din nou
instrucțiunea continue.
În exemplul următor:
int i;
do {
i++;
continue;
i++;
} while( i < 10 );
corpul se execută de 10 ori la fel ca și mai sus. Instrucțiunea continue
duce la evitarea celei de-a doua incrementări, dar nu și la evitarea testului de
sfârșit de buclă.
În sfârșit, în exemplul următor:
for( int i = 0; i < 10; i++ ) {
continue;
i++;
}
corpul se execută tot de 10 ori, ceea ce înseamnă că reluarea buclei duce la
execuția instrucțiunii de reluare a buclei for și apoi a testului. Doar
ceea ce este în interiorul corpului este evitat.
Instrucțiunea continue poate avea, la fel ca și instrucțiunea break,
un identificator opțional care specifică eticheta buclei care trebuie continuată. Dacă
există mai multe bucle imbricate una în cealaltă buclele interioare celei referite de
etichetă sunt abandonate.
De exemplu, în secvența:
asta: for( int i, j = 1; i < 10; i++ ) {
while( j < 5 ) {
j++;
if( j % 2 == 0 )
continue asta;
}
}
instrucțiunea continue provoacă abandonarea buclei while
și reluarea buclei for cu partea de reluare și apoi testul.
5.3.2.5.3 Instrucțiunea return
Instrucțiunea return provoacă părăsirea corpului unei metode. În
cazul în care return este urmată de o expresie, valoarea expresiei este
folosită ca valoare de retur a metodei. Această valoare poate fi eventual convertită
către tipul de valoare de retur declarat al metodei, dacă acest lucru este posibil.
Dacă nu este posibil, va fi semnalată o eroare de compilare.
Este o eroare de compilare specificarea unei valori de retur într-o instrucțiune return
din interiorul unei metode care este declarată void, cu alte cuvinte care
nu întoarce nici o valoare.
Instrucțiunea return fără valoare de retur poate fi folosită și
pentru a părăsi execuția unui inițializator static.
Exemple de instrucțiuni return veți găsi în secțiunea care
tratează metodele unei clase de obiecte.
5.3.2.5.4 Instrucțiunea throw
Instrucțiunea throw este folosită pentru a semnaliza o excepție de
execuție. Această instrucțiune trebuie să aibă un argument și acesta trebuie să fie
un tip obiect, de obicei dintr-o subclasă a clasei de obiecte Exception.
La execuția instrucțiunii throw, fluxul normal de execuție este
părăsit și se termină toate instrucțiunile în curs până la prima instrucțiune try
care specifică într-o clauză catch un argument formal de același tip
cu obiectul aruncat sau o superclasă a acestuia.
5.3.2.6 Instrucțiuni de protecție
Aceste instrucțiuni sunt necesare pentru tratarea erorilor și a excepțiilor precum
și pentru sincronizarea unor secvențe de cod care nu pot rula în paralel.
Instrucțiunea try inițiază un context de tratare a excepțiilor. În
orice punct ulterior inițializării acestui context și înainte de terminarea acestuia,
o excepție semnalată prin execuția unei instrucțiuni throw va returna
controlul la nivelul instrucțiunii try, abandonându-se în totalitate
restul instrucțiunilor din corpul acestuia.
La semnalarea unei excepții aceasta poate fi prinsă de o clauză catch
și, în funcție de tipul obiectului aruncat, se pot executa unele instrucțiuni care
repun programul într-o stare stabilă. De obicei, o excepție este generată atunci când
s-a produs o eroare majoră și continuarea instrucțiunilor din contextul curent nu mai
are sens.
În finalul instrucțiunii, se poate specifica și un bloc de instrucțiuni care se
execută imediat după blocul try și blocurile catch
indiferent cum s-a terminat execuția acestora. Pentru specificarea acestor instrucțiuni,
trebuie folosită o clauză finally.
Iată sintaxa unei instrucțiuni try:
try Bloc1 [catch( Argument ) Bloc2]*[finally
Bloc3]
Dacă, undeva în interiorul blocului 1 sau în metodele apelate din interiorul
acestuia, pe oricâte nivele, este apelată o instrucțiune throw,
execuția blocului și a metodelor în curs este abandonată și se revine în
instrucțiunea try. În continuare, obiectul aruncat de throw
este comparat cu argumentele specificate în clauzele catch. Dacă unul
dintre aceste argumente este instanță a aceleiași clase sau a unei superclase a clasei
obiectului aruncat, se execută blocul de instrucțiuni corespunzător clauzei catch
respective. Dacă nici una dintre clauzele catch nu se potrivește,
obiectul este aruncat mai departe. Dacă excepția nu este nicăieri prinsă în program,
acesta se termină cu o eroare de execuție.
Indiferent dacă a apărut o excepție sau nu, indiferent dacă s-a executat blocul
unei clauze catch sau nu, în finalul execuției instrucțiunii try
se execută blocul specificat în clauza finally, dacă aceasta există.
Clauza finally se execută chiar și dacă în interiorul blocului 1
s-a executat o instrucțiune throw care a aruncat un obiect care nu poate
fi prins de clauzele catch ale acestei instrucțiuni try.
În astfel de situații, execuția instrucțiunii throw se oprește
temporar, se execută blocul finally și apoi se aruncă mai departe
excepția.
Exemple de utilizare a instrucțiunii try găsiți în paragraful 9.2
5.3.2.6.2 Instrucțiunea synchronized
Instrucțiunea synchronized introduce o secvență de instrucțiuni
critică. O secvență critică de instrucțiuni trebuie executată în așa fel
încât nici o altă parte a programului să nu poată afecta obiectul cu care lucrează
secvența dată. Secvențele critice apar de obicei atunci când mai multe părți ale
programului încearcă să acceseze în același timp aceleași resurse.
Gândiți-vă, de exemplu, ce s-ar întâmpla dacă mai multe părți ale programului
ar încerca să incrementeze în același timp valoarea unei variabile. Una dintre ele ar
citi vechea valoare a variabilei, să spunem 5, ar incrementa-o la 6 și, când să o
scrie înapoi, să presupunem că ar fi întreruptă de o altă parte a programului care
ar incrementa-o la 6. La revenirea în prima parte, aceasta ar termina prima incrementare
prin scrierea valorii 6 înapoi în variabilă. Valoarea finală a variabilei ar fi 6 în
loc să fie 7 așa cum ne-am aștepta dacă cele două incrementări s-ar face pe rând.
Spunem că cele două regiuni în care se face incrementarea aceleiași variabile sunt
regiuni critice. Înainte ca una dintre ele să se execute, ar trebui să ne asigurăm că
cealaltă regiune critică nu rulează deja. Cea mai simplă cale de a face acest lucru
este să punem o condiție de blocare chiar pe variabila incrementată. Ori de câte ori o
regiune critică va încerca să lucreze, va verifica dacă variabila noastră este
liberă sau nu.
Instrucțiunea synchronized își blochează obiectul pe care îl
primește ca parametru și apoi execută secvența critică. La sfârșitul acesteia
obiectul este deblocat înapoi. Dacă instrucțiunea nu poate bloca imediat obiectul
pentru că acesta este blocat de o altă instrucțiune, așteaptă până când obiectul
este deblocat.
Mai mult despre această instrucțiune precum și exemple de utilizare veți găsi în
partea a treia, capitolul 9. Până atunci, iată sintaxa generală a acestei
instrucțiuni:
synchronized ( Expresie ) Instrucțiune
Expresia trebuie să aibă ca valoare o referință către un obiect sau un tablou care
va servi drept dispozitiv de blocare. Instrucțiunea poate fi o instrucțiune simplă sau
un bloc de instrucțiuni.
5.3.2.7 Instrucțiunea vidă
Instrucțiunea vidă este o instrucțiune care nu execută nimic. Ea este
folosită uneori, atunci când este obligatoriu să avem o instrucțiune, dar nu dorim să
executăm nimic în acea instrucțiune. De exemplu, în cazul unei instrucțiuni if,
este obligatoriu să avem o instrucțiune pe ramura de adevăr. Dacă însă nu dorim să
executăm nimic acolo, putem folosi un bloc vid sau o instrucțiune vidă.
Sintaxa pentru o instrucțiune vidă este următoarea:
;
Iată și un exemplu:
int x = 3;
if( x == 5 ) ; else x = 5;
Caracterul ; care apare după condiția din if
reprezintă o instrucțiune vidă care specifică faptul că, în cazul în care x
are valoarea 5 nu trebuie să se execute nimic. |