Capitolul V
5.2 Expresii

5.2.1 Valoarea și tipul unei expresii
5.2.2 Ordinea de evaluare
5.2.3 Operatori

5.2.3.1 Operatori unari

5.2.3.1.1 Operatorii de preincrementare și postincrementare
5.2.3.1.2 Operatorul + unar
5.2.3.1.3 Operatorul - unar
5.2.3.1.4 Operatorul de complementare
5.2.3.1.5 Operatorul de negare logică
5.2.3.1.6 Casturi

5.2.3.2 Operatori binari

5.2.3.2.1 Operatori multiplicativi: *, /, %
5.2.3.2.2 Operatori aditivi: +, -, concatenare pentru șiruri de caractere
5.2.3.2.3 Operatori de șiftare: >>, <<, >>>
5.2.3.2.4 Operatori relaționali: <, >, <=, >=, instanceof
5.2.3.2.5 Operatori de egalitate: ==, !=
5.2.3.2.6 Operatori la nivel de bit: &, |, ^
5.2.3.2.7 Operatori logici: &&, ||

5.2.3.3 Operatorul condițional ?:
5.2.3.4 Operații întregi
5.2.3.5 Operații flotante
5.2.3.6 Apeluri de metode

5.2.1 Valoarea și tipul unei expresii

Fiecare expresie a limbajului Java are un rezultat și un tip. Rezultatul poate fi:

  • o valoare
  • o variabilă
  • nimic

În cazul în care valoarea unei expresii are ca rezultat o variabilă, expresia poate apare în stânga unei operații de atribuire. De exemplu expresia:

tablou[i]

este o expresie care are ca rezultat o variabilă și anume locația elementului cu indexul i din tabloul de elemente numit tablou.

O expresie nu produce nimic doar în cazul în care este un apel de metodă și acest apel nu produce nici un rezultat (este declarat de tip void).

Fiecare expresie are în timpul compilării un tip cunoscut. Acest tip poate fi o valoare primitivă sau o referință. În cazul expresiilor care au tip referință, valoarea expresiei poate să fie și referința neinițializată, null. ^

5.2.2 Ordinea de evaluare

În Java operanzii sunt evaluați întotdeauna de la stânga spre dreapta. Acest lucru nu trebuie să ne îndemne să scriem cod care să depindă de ordinea de evaluare pentru că, în unele situații, codul rezultat este greu de citit. Regula este introdusă doar pentru a asigura generarea uniformă a codului binar, independent de compilatorul folosit.

Evaluarea de la stânga la dreapta implică următoarele aspecte:

  • în cazul unui operator binar, operandul din stânga este întotdeauna complet evaluat atunci când se trece la evaluarea operandului din dreapta. De exemplu, în expresia:
( i++ ) + i

dacă i avea valoarea inițială 2, toată expresia va avea valoarea 5 pentru că valoarea celui de-al doilea i este luată după ce s-a executat incrementarea i++.

  • în cazul unei referințe de tablou, expresia care numește tabloul este complet evaluată înainte de a se trece la evaluarea expresiei care dă indexul. De exemplu, în expresia:
( a = b )[i] 
  • indexul va fi aplicat după ce s-a executat atribuirea valorii referință b la variabila referință de tablou a. Cu alte cuvinte, rezultatul va fi al i-lea element din tabloul b.
  • în cazul apelului unei metode, expresia care numește obiectul este complet evaluată atunci când se trece la evaluarea expresiilor care servesc drept argumente.
  • în cazul unui apel de metodă, dacă există mai mult decât un parametru, la evaluarea parametrului numărul i, toți parametrii de la 1 la i-1 sunt deja evaluați complet.
  • în cazul unei expresii de alocare, dacă avem mai multe dimensiuni exprimate în paranteze drepte, dimensiunile sunt de asemenea evaluate de la stânga la dreapta. De exemplu, în expresia:
new int[i++][i] 
  • tabloul rezultat nu va fi o matrice pătratică ci, dacă i avea valoarea 3, de dimensiune 3 x 4. ^

5.2.3 Operatori

5.2.3.1 Operatori unari

Operatorii unari se aplică întotdeauna unui singur operand. Acești operatori sunt, în general exprimați înaintea operatorului asupra căruia se aplică. Există însă și două excepții, operatorii de incrementare și decrementare care pot apare și înainte și după operator, cu semnificații diferite.

Operatorii unari sunt următorii:

  • ++ preincrement
  • -- predecrement
  • ++ postincrement
  • -- postdecrement
  • + unar
  • - unar
  • ~ complementare
  • ! negație logică
  • cast ^
5.2.3.1.1 Operatorii de preincrementare și postincrementare

Operatorii ++ preincrement și postincrement au același rezultat final, și anume incrementează variabila asupra căreia acționează cu 1. Operandul asupra căruia sunt apelați trebuie să fie o variabilă de tip aritmetic. Nu are sens să apelăm un operand de incrementare asupra unei valori (de exemplu valoarea unei expresii sau un literal), pentru că aceasta nu are o locație de memorie fixă în care să memorăm valoarea după incrementare.

În cazul operatorului prefix, valoarea rezultat a acestei expresii este valoarea variabilei după incrementare în timp ce, la operatorul postfix, valoarea rezultat a expresiei este valoarea de dinainte de incrementare. De exemplu, după execuția următoarei secvențe de instrucțiuni:

int i = 5;
int j = i++;

valoarea lui j este 5, în timp ce, după execuția următoarei secvențe de instrucțiuni:

int i = 5;
int j = ++i;

valoarea lui j va fi 6. În ambele cazuri, valoarea finală a lui i va fi 6.

În cazul operatorilor -? predecrement și postdecrement, sunt valabile aceleași considerații ca mai sus, cu diferența că valoarea variabilei asupra căreia se aplică operandul va fi decrementată cu 1. De exemplu, următoarele instrucțiuni:

int i = 5;
int j = i--;

fac ca j să aibă valoarea finală 5 iar următoarele instrucțiuni:

int i = 5;
int j = --i;

fac ca j să aibă valoarea finală 4. În ambele cazuri, valoarea finală a lui i este 4.

Operatorii de incrementare și de decrementare se pot aplica și pe variabile de tip flotant. În asemenea cazuri, se convertește valoarea 1 la tipul variabilei incrementate sau decrementate, după care valoarea rezultată este adunată respectiv scăzută din vechea valoare a variabilei. De exemplu, următoarele instrucțiuni:

double f = 5.6;
double g = ++f;

au ca rezultat final valoarea 6.6 pentru f și pentru g. ^

5.2.3.1.2 Operatorul + unar

Operatorul + unar se aplică asupra oricărei valori primitive aritmetice. Valoarea rămâne neschimbată. ^

5.2.3.1.3 Operatorul - unar

Operatorul - unar se aplică asupra oricărei valori primitive aritmetice. Rezultatul aplicării acestui operand este negarea aritmetică a valorii. În cazul valorilor întregi, acest lucru este echivalent cu scăderea din 0 a valorii originale. De exemplu, instrucțiunile:

int i = 5;
int j = -i;

îi dau lui j valoarea -5 . În cazul valorilor speciale definite de standardul IEEE pentru reprezentarea numerelor flotante, se aplică următoarele reguli:

  • Dacă operandul este NaN rezultatul negării aritmetice este tot NaN pentru că NaN nu are semn.
  • Dacă operandul este unul dintre infiniți, rezultatul este infinitul opus ca semn.
  • Dacă operandul este zero de un anumit semn, rezultatul este zero de semn diferit. ^
5.2.3.1.4 Operatorul de complementare

Operatorul de complementare ~ se aplică asupra valorilor primitive de tip întreg. Rezultatul aplicării operandului este complementarea bit cu bit a valorii originale. De exemplu, dacă operandul era de tip byte având valoarea, în binar, 00110001, rezultatul va fi 11001110. În realitate, înainte de complementare se face și extinderea valorii la un întreg, deci rezultatul va fi de fapt: 11111111 11111111 11111111 11001110. ^

5.2.3.1.5 Operatorul de negare logică

Operatorul de negare logică ! se aplică în exclusivitate valorilor de tip boolean. În cazul în care valoarea inițială a operandului este true rezultatul va fi false și invers. ^

5.2.3.1.6 Casturi

Casturile sunt expresii de conversie dintr-un tip într-altul, așa cum deja am arătat la paragraful destinat conversiilor. Rezultatul unui cast este valoarea operandului convertită la noul tip de valoare exprimat de cast. De exemplu, la instrucțiunile:

double f = 5.6;
int i = ( int )f;
double g = -5.6;
int j = ( int )g;

valoarea variabilei f este convertită la o valoare întreagă, anume 5, și noua valoare este atribuită variabilei i. La fel, j primește valoarea -5.

Să mai precizăm că nu toate casturile sunt valide în Java. De exemplu, nu putem converti o valoare întreagă într-o valoare de tip referință. ^

5.2.3.2 Operatori binari

Operatorii binari au întotdeauna doi operanzi. Operatorii binari sunt următorii:

  • Operatori multiplicativi: *, /, %
  • Operatori aditivi: +, -, + (concatenare) pentru șiruri de caractere
  • Operatori de șiftare: >>, <<, >>>
  • Operatori relaționali: <, >, <=, >=, instanceof
  • Operatori de egalitate: ==, !=
  • Operatori la nivel de bit: &, |, ^
  • Operatori logici: &&, || ^
5.2.3.2.1 Operatori multiplicativi: *, /, %

Operatorii multiplicativi reprezintă respectiv operațiile de înmulțire (*), împărțire (/) și restul împărțirii (%). Prioritatea acestor operații este mai mare relativ la operațiile aditive, deci acești operatori se vor executa mai întâi. Exemple:

10 * 5 == 50
10.3 * 5.0 == 51.5
10 / 2.5 == 4.0// împărțire reală
3 / 2 == 1// împărțire întreagă
7 % 2 == 1// restul împărțirii întregi
123.5 % 4 == 3.5 // 4 * 30 + 3.5
123.5 % 4.5 == 2.0 // 4.5 * 27 + 2.0

După cum observați, operanzii sunt convertiți mai întâi la tipul cel mai puternic, prin promovare aritmetică, și apoi se execută operația. Rezultatul este de același tip cu tipul cel mai puternic.

În cazul operatorului pentru restul împărțirii, dacă lucrăm cu numere flotante, rezultatul se calculează în felul următor: se calculează de câte ori este cuprins cel de-al doilea operand în primul (un număr întreg de ori) după care rezultatul este diferența care mai rămâne, întotdeauna mai mică strict decât al doilea operand. ^

5.2.3.2.2 Operatori aditivi: +, -, concatenare pentru șiruri de caractere

Operatorii aditivi reprezintă operațiile de adunare (+), scădere (-) și concatenare (+) de șiruri. Observațiile despre conversia tipurilor făcute la operatorii multiplicativi rămân valabile. Exemple:

2 + 3 == 5
2.34 + 3 == 5.34
34.5 - 23.1 == 11.4
"Acesta este" + " un sir" == "Acesta este un sir"
"Sirul: " + 1 == "Sirul: 1"
"Sirul: " + 3.4444 == "Sirul: 3.4444"
"Sirul: " + null = "Sirul: null"
"Sirul: " + true = "Sirul: true"
Object obiect = new Object();
"Sirul: " + obiect == "java.lang.Object@1393800"

La concatenarea șirurilor de caractere, lungimea șirului rezultat este suma lungimii șirurilor care intră în operație. Caracterele din șirul rezultat sunt caracterele din primul șir, urmate de cele dintr-al doilea șir în ordine.

Dacă cel de-al doilea operand nu este de tip String ci este de tip referință, se va apela metoda sa toString, și apoi se va folosi în operație rezultatul. Metoda toString este definită în clasa Object și este moștenită de toate celelalte clase.

Dacă cel de-al doilea operand este un tip primitiv, acesta este convertit la un șir rezonabil de caractere care să reprezinte valoarea operandului. ^

5.2.3.2.3 Operatori de șiftare: >>, <<, >>>

Operatorii de șiftare se pot aplica doar pe valori primitive întregi. Ei reprezintă respectiv operațiile de șiftare cu semn stânga (<<) și dreapta (>>) și operația de șiftare fără semn spre dreapta (>>>).

Șiftările cu semn lucrează la nivel de cifre binare. Cifrele binare din locația de memorie implicată sunt mutate cu mai multe poziții spre stânga sau spre dreapta. Poziția binară care reprezintă semnul rămâne neschimbată. Numărul de poziții cu care se efectuează mutarea este dat de al doilea operand. Locația de memorie în care se execută operația este locația în care este memorat primul operand.

Șiftarea cu semn la stânga reprezintă o operație identică cu înmulțirea cu 2 de n ori, unde n este al doilea operand. Șiftarea cu semn la dreapta reprezintă împărțirea întreagă. În acest caz, semnul este copiat în mod repetat în locurile rămase goale. Iată câteva exemple:

255 << 3 == 2040
// 00000000 11111111 -> 00000111 11111000
255 >> 5 == 7
// 00000000 11111111 -> 00000000 00000111

Șiftarea fără semn la dreapta, mută cifrele binare din operand completând spațiul rămas cu zerouri:

0xffffffff >>> -1 == 0x00000001
0xffffffff >>> -2 == 0x00000003
0xffffffff >>> -3 == 0x00000007
0xffffffff >>> 3 == 0x1fffffff
0xffffffff >>> 5 == 0x07ffffff ^
5.2.3.2.4 Operatori relaționali: <, >, <=, >=, instanceof

Operatorii relaționali întorc valori booleene de adevărat sau fals. Ei reprezintă testele de mai mic (<), mai mare (>), mai mic sau egal (<=), mai mare sau egal (>=) și testul care ne spune dacă un anumit obiect este sau nu instanță a unei anumite clase (instanceof). Iată câteva exemple:

1 < 345 == true
1 <= 0 == false
Object o = new Object();
String s = new String();
o instanceof Obiect == true
s instanceof String == true
o instanceof String == false
s instanceof Object == true

Să mai observăm că String este derivat din Object. ^

5.2.3.2.5 Operatori de egalitate: ==, !=

Acești operatori testează egalitatea sau inegalitatea dintre două valori. Ei reprezintă testul de egalitate (==) și de inegalitate (!=). Rezultatul aplicării acestor operatori este o valoare booleană.

Exemple:

( 1 == 1.0 ) == true
( 2 != 2 ) == false
Object o = new Object();
String s1 = "vasile";
String s2 = s1;
String s4 = "e";
String s3 = "vasil" + s4;
( o == s1 ) == false
( s1 == s2 ) == true // același obiect referit
( s3 == s1 ) == false // același șir de caractere
// dar obiecte diferite

Să observăm că egalitatea a două obiecte de tip String reprezintă egalitatea a două referințe de obiecte și nu egalitatea conținutului șirului de caractere. Două referințe sunt egale dacă referă exact același obiect, nu dacă obiectele pe care le referă sunt egale între ele. Egalitatea conținutului a două șiruri de caractere se testează folosind metoda equals, definită în clasa String. ^

5.2.3.2.6 Operatori la nivel de bit: &, |, ^

Operatorii la nivel de bit reprezintă operațiile logice obișnuite, dacă considerăm că 1 ar reprezenta adevărul și 0 falsul. Operatorii la nivel de bit, consideră cei doi operanzi ca pe două șiruri de cifre binare și fac operațiile pentru fiecare dintre perechile de cifre binare corespunzătoare în parte. Rezultatul este un nou șir de cifre binare. De exemplu, operația de și (&) logic are următorul tabel de adevăr:

1 & 1 == 1
1 & 0 == 0
0 & 1 == 0
0 & 0 == 0

Dacă apelăm operatorul & pe numerele reprezentate binar:

00101111
01110110

rezultatul este:

00100110

Primul număr reprezintă cifra 47, al doilea 118 iar rezultatul 38, deci:

47 & 118 == 38

În mod asemănător, tabela de adevăr pentru operația logică sau (|) este:

1 | 1 == 1
1 | 0 == 1
0 | 1 == 1
0 | 0 == 0

iar tabela de adevăr pentru operația logică de sau exclusiv (^) este:

1 ^ 1 == 0
1 ^ 0 == 1
0 ^ 1 == 1
0 ^ 0 == 0

Iată și alte exemple:

1245 ^ 2345 == 3572
128 & 255 == 128
127 & 6 == 6
128 | 255 == 255
127 | 6 == 127
32 ^ 64 == 96 ^
5.2.3.2.7 Operatori logici: &&, ||

Operatorii logici se pot aplica doar asupra unor operanzi de tip boolean. Rezultatul aplicării lor este tot boolean și reprezintă operația logică de și (&&) sau operația logică de sau (||) între cele două valori booleene. Iată toate posibilitățile de combinare:

true && true == true
true && false == false
false && true == false
false && false == false
true || true == true
true || false == true
false || true == true
false || false == false

În cazul operatorului && este evaluat mai întâi operandul din stânga. Dacă acesta este fals, operandul din dreapta nu mai este evaluat, pentru că oricum rezultatul este fals. Acest lucru ne permite să testăm condițiile absolut necesare pentru corectitudinea unor operații și să nu executăm operația decât dacă aceste condiții sunt îndeplinite.

De exemplu, dacă avem o referință și dorim să citim o valoare de variabilă din obiectul referit, trebuie să ne asigurăm că referința este diferită de null. În acest caz, putem scrie:

String s = "sir de caractere";
if( s != null && s.length < 5 ) ?

În cazul în care s este null, a doua operație nu are sens și nici nu va fi executată.

În mod similar, la operatorul ||, se evaluează mai întâi primul operand. Dacă acesta este adevărat, nu se mai evaluează și cel de-al doilea operand pentru că rezultatul este oricum adevărat. Faptul se poate folosi în mod similar ca mai sus:

if( s == null || s.length == 0 ) ?

În cazul în care s este null, nu se merge mai departe cu evaluarea. ^

5.2.3.3 Operatorul condițional ?:

Este singurul operator definit de limbajul Java care acceptă trei operanzi. Operatorul primește o expresie condițională booleană pe care o evaluează și alte două expresii care vor fi rezultatul aplicării operandului. Care dintre cele două expresii este rezultatul adevărat depinde de valoarea rezultat a expresiei booleene. Forma generală a operatorului este:

ExpresieCondițională ? Expresie1 : Expresie2

Dacă valoarea expresiei condiționale este true, valoarea operației este valoarea expresiei 1. Altfel, valoarea operației este valoarea expresiei 2.

Cele două expresii trebuie să fie amândouă aritmetice sau amândouă booleene sau amândouă de tip referință.

Iată și un exemplu:

int i = 5;
int j = 4;
double f = ( i < j ) ? 100.5 : 100.4;

Parantezele nu sunt obligatorii.

După execuția instrucțiunilor de mai sus, valoarea lui f este 100.4. Iar după:

int a[] = { 1, 2, 3, 4, 5 };
int b[] = { 10, 20, 30 };
int k = ( ( a.length < b.length ) ? a : b )[0];

valoarea lui k va deveni 10. ^

5.2.3.4 Operații întregi

Dacă amândoi operanzii unei operator sunt întregi atunci întreaga operație este întreagă.

Dacă unul dintre operanzi este întreg lung, operația se va face cu precizia de 64 de biți. Operanzii sunt eventual convertiți. Rezultatul este un întreg lung sau o valoare booleană dacă operatorul este un operator condițional.

Dacă nici unul dintre operanzi nu e întreg lung, operația se face întotdeauna pe 32 de biți, chiar dacă cei doi operanzi sunt întregi scurți sau octeți. Rezultatul este întreg sau boolean.

Dacă valoarea obținută este mai mare sau mai mică decât se poate reprezenta pe tipul rezultat, nu este semnalată nici o eroare de execuție, dar rezultatul este trunchiat. ^

5.2.3.5 Operații flotante

Dacă un operand al unei operații este flotant, atunci întreaga operație este flotantă. Dacă unul dintre operanzi este flotant dublu, operația este pe flotanți dubli. Rezultatul este un flotant dublu sau boolean.

În caz de operații eronate nu se generează erori. În loc de aceasta se obține rezultatul NaN. Dacă într-o operație participă un NaN rezultatul este de obicei NaN.

În cazul testelor de egalitate, expresia

NaN == NaN

are întotdeauna rezultatul fals pentru că un NaN nu este egal cu nimic. La fel, expresia:

NaN != NaN

este întotdeauna adevărată.

În plus, întotdeauna expresia:

-0.0 == +0.0

este adevărată, unde +0.0 este zeroul pozitiv iar -0.0 este zeroul negativ. Cele două valori sunt definite în standardul IEEE 754.

În alte operații însă, zero pozitiv diferă de zero negativ. De exemplu 1.0 / 0.0 este infinit pozitiv iar 1.0 / -0.0 este infinit negativ. ^

5.2.3.6 Apeluri de metode

La apelul unei metode, valorile întregi nu sunt automat extinse la întreg sau la întreg lung ca la operații. Asta înseamnă că, dacă un operand este transmis ca întreg scurt de exemplu, el rămâne întreg scurt și în interiorul metodei apelate.

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