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. ^

5.3.2.3.1 Instrucțiunea if

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. ^

5.3.2.4.2 Instrucțiunea do

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. ^

5.3.2.4.3 Instrucțiunea for

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. ^

5.3.2.6.1 Instrucțiunea try

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. ^

[capitolul V]
[cuprins]
(C) IntegraSoft 1996-1998