Introducere în Java
- Detalii
- Categorie: Programare Java
- Accesări: 6,354
Limbajul Java împreuna cu mediul sau de dezvoltare si executie au fost proiectate pentru a rezolva o parte dintre problemele actuale ale programarii. Proiectul Java a pornit cu scopul declarat de a dezvolta un software performant pentru aparatele electronice de larg consum. Aceste echipamente se definesc ca: mici, portabile, distribuite si lucrând în timp real. De la aceste aparate, ne-am obisnuit sa cerem fiabilitate si usurinta în exploatare.
Limbajul luat initial în considerare a fost C++. Din pacate, atunci când s-a încercat crearea unui mediu de executie care sa respecte toate aceste conditii s-a observat ca o serie de trasaturi ale C++ sunt incompatibile cu necesitatile declarate. În principal, problema vine din faptul ca C++ este prea complicat, foloseste mult prea multe conventii si are înca prea multe elemente de definitie lasate la latitudinea celor care scriu compilatoare pentru o platforma sau alta.
În aceste conditii, firma Sun a pornit proiectarea unui nou limbaj de programare asemanator cu C++ dar mult mai flexibil, mai simplu si mai portabil. Asa s-a nascut Java. Parintele noului limbaj a fost James Gostling care este cunoscut ca autor al editorului emacs si al sistemului de ferestre grafice NeWS. Proiectul a început înca din 1990 dar Sun a facut publica specificatia noului limbaj abia în 1995 la SunWorld în San Francisco.
Numele initial al limbajului a fost Oak, numele unui copac care creste în fata biroului lui James Gostling. Ulterior, s-a descoperit ca numele fusese deja folosit în trecut pentru un alt limbaj de programare asa ca a fost abandonat si înlocuit cu Java, spre deliciul programatorilor care iubesc cafenelele si aromele exotice.
Ce este Java?
În primul rând, Java încearca sa ramâna un limbaj simplu de folosit chiar si de catre programatorii neprofesionisti, programatori care doresc sa se concentreze asupra aplicatiilor în principal si abia apoi asupra tehnicilor de implementare a acestora. Aceasta trasatura poate fi considerata ca o reactie directa la complexitatea considerabila a limbajului C++.
Au fost îndepartate din Java aspectele cele mai derutante din C++ precum supraîncarcarea operatorilor si mostenirea multipla. A fost introdus un colector automat de gunoaie care sa rezolve problema dealocarii memoriei în mod uniform, fara interventia programatorului. Colectorul de gunoaie nu este o trasatura noua, dar implementarea acestuia în Java este facuta inteligent si eficient folosind un fir separat de executie, pentru ca Java are încorporate facilitati de executie pe mai multe fire de executie. Astfel, colectarea gunoaielor se face de obicei în timp ce un alt fir asteapta o operatie de intrare-iesire sau pe un semafor.
Limbajul Java este independent de arhitectura calculatorului pe care lucreaza si foarte portabil. În loc sa genereze cod nativ pentru o platforma sau alta, compilatorul Java genereaza o secventa de instructiuni ale unei masini virtuale Java (numita bytecod). Executia aplicatiilor Java este interpretata. Singura parte din mediul de executie Java care trebuie portata de pe o arhitectura pe alta este mediul de executie cuprinzând interpretorul si o parte din bibliotecile standard care depind de sistem. În acest fel, aplicatii Java compilate pe o arhitectura SPARC de exemplu, pot fi rulate fara recompilare pe un sistem bazat pe procesoare Intel.
Una dintre principalele probleme ale limbajelor interpretate este viteza de executie, considerabil scazuta fata de cea a limbajelor compilate. Daca nu va multumeste viteza de executie a unei astfel de aplicatii, puteti cere mediului de executie Java sa genereze automat, plecând de la codul masinii virtuale, codul specific masinii pe care lucrati, obtinându-se astfel un executabil nativ care poate rula la viteza maxima. De obicei însa, în Java se compileaza doar acele parti ale programului mari consumatoare de timp, restul ramânând interpretate pentru a nu se pierde flexibilitatea. Mediul de executie însusi este scris în C, ceea ce îl face extrem de portabil.
Interpretorul Java este gândit sa lucreze pe masini mici, precum ar fi procesoarele cu care sunt dotate aparatele casnice. Interpretorul plus bibliotecile standard cu legare dinamica nu depasesc 300 Kocteti. Chiar împreuna cu interfata grafica totul ramâne mult sub 1 Moctet.
Limbajul Java este în totalitate orientat obiect. Cu el se pot crea clase de obiecte si instante ale acestora, se pot încapsula informatiile, se pot mosteni variabilele si metodele de la o clasa la alta, etc. Singura trasatura specifica limbajelor orientate obiect care lipseste este mostenirea multipla, dar pentru a suplini aceasta lipsa, Java ofera o facilitate mai simpla, numita interfata, care permite definirea unui anumit comportament pentru o clasa de obiecte, altul decât cel definit de clasa de baza. În Java orice element este un obiect, în afara de datele primare. Din Java lipsesc functiile si variabilele globale. Ne ramân desigur metodele si variabilele statice ale claselor.
Java este distribuit, având implementate biblioteci pentru lucrul în retea care ne ofera TCP/IP, URL si încarcarea resurselor din retea. Aplicatiile Java pot accesa foarte usor reteaua, folosindu-se de apelurile catre un set standard de clase.
Java este robust. În Java legarea functiilor se face în timpul executiei si informatiile de compilare sunt disponibile pâna în momentul rularii aplicatiei. Acest mod de lucru face ca sistemul sa poata determina în orice moment neconcordanta dintre tipul referit la compilare si cel referit în timpul executiei evitându-se astfel posibile intruziuni rauvoitoare în sistem prin intermediul unor referinte falsificate. În acelasi timp, Java detecteaza referintele nule daca acestea sunt folosite în operatii de acces. Indicii în tablourile Java sunt verificati permanent în timpul executiei si tablourile nu se pot parcurge prin intermediul unor pointeri asa cum se întâmpla în C/C++.
De altfel, pointerii lipsesc complet din limbajul Java, împreuna cu întreaga lor aritmetica, eliminându-se astfel una din principalele surse de erori. În plus, eliberarea memoriei ocupate de obiecte si tablouri se face automat, prin mecanismul de colectare de gunoaie, evitându-se astfel încercarile de eliberare multipla a unei zone de memorie.
Java este un limbaj cu securitate ridicata. El verifica la fiecare încarcare codul prin mecanisme de CRC si prin verificarea operatiilor disponibile pentru fiecare set de obiecte. Robustetea este si ea o trasatura de securitate. La un al doilea nivel, Java are incorporate facilitati de protectie a obiectelor din sistem la scriere si/sau citire. Variabilele protejate într-un obiect Java nu pot fi accesate fara a avea drepturile necesare, verificarea fiind facuta în timpul executiei. În plus, mediul de executie Java poate fi configurat pentru a proteja reteaua locala, fisierele si celelalte resurse ale calculatorului pe care ruleaza o aplicatie Java.
Limbajul Java are inclus suportul nativ pentru aplicatii care lucreaza cu mai multe fire de executie, inclusiv primitive de sincronizare între firele de executie. Acest suport este independent de sistemul de operare, dar poate fi conectat, pentru o performanta mai buna, la facilitatile sistemului daca acestea exista.
Java este dinamic. Bibliotecile de clase în Java pot fi reutilizate cu foarte mare usurinta. Cunoscuta problema a fragilitatii superclasei este rezolvata mai bine decât în C++. Acolo, daca o superclasa este modificata, trebuie recompilate toate subclasele acesteia pentru ca obiectele au o alta structura în memorie. În Java aceasta problema este rezolvata prin legarea târzie variabilelor, doar la executie. Regasirea variabilelor se face prin nume si nu printr-un deplasament fix. Daca superclasa nu a sters o parte dintre vechile variabile si metode, ea va putea fi refolosita fara sa fie necesara recompilarea subclaselor acesteia. Se elimina astfel necesitatea actualizarii aplicatiilor, generata de aparitia unei noi versiuni de biblioteca asa cum se întâmpla, de exemplu, cu MFC-ul Microsoft (si toate celelalte ierarhii C++).
Reprezentarea informatiilor cu obiecte
Obiecte
Informatiile pe care le reprezentam în memoria calculatorului sunt rareori atât de simple precum culorile sau literele. În general, dorim sa reprezentam informatii complexe, care sa descrie obiectele fizice care ne înconjoara sau notiunile cu care operam zilnic, în interiorul carora culoarea sau o secventa de litere reprezinta doar o mica parte. Aceste obiecte fizice sau notiuni din lumea reala trebuiesc reprezentate în memoria calculatorului în asa fel încât informatiile specifice lor sa fie pastrate la un loc si sa se poata prelucra ca un tot unitar. Sa nu uitam însa ca, la nivelul cel mai de jos, informatia atasata acestor obiecte continua sa fie tratata de catre compilator ca un sir de numere binare, singurele informatii reprezentabile direct în memoria calculatoarelor actuale.
Putem sa extindem cerintele noastre mai departe, spunând ca, atunci când analizam un obiect fizic sau o notiune pentru a le reprezenta în calculator, trebuie sa analizam nu numai proprietatile acestora dar si modul în care acestea pot fi utilizate si care sunt operatiile care pot fi executate asupra lor sau cu ajutorul lor. Uneori, setul de operatii specifice unui obiect împreuna cu modul în care acesta reactioneaza la stimuli exteriori se numeste comportamentul obiectului.
De exemplu, daca dorim sa construim un obiect care reprezinta o minge de forma sferica în spatiu, este necesar sa definim trei numere care sa reprezinte coordonatele x, y si z relativ la un sistem de axe dat, precum si o valoare pentru raza sferei. Aceste valori numerice vor face parte din setul de proprietati ale obiectului minge. Daca mai târziu vom dori sa construim o operatie care sa reprezinte mutarea în spatiu a obiectului minge, este suficient sa ne folosim de operatiile cu numere pentru a modifica valorile coordonatelor x, y si z.
Desigur, obiectul minge este insuficient descris prin aceste coordonate si, pentru a simula în calculator obiectul real este nevoie de multe proprietati suplimentare precum si de multe operatii în plus. Dar, daca problema pe care o avem de rezolvat nu necesita aceste proprietati si operatii, este preferabil sa nu le definim în obiectul folosit pentru reprezentare. Rezultatul direct al acestui mod de abordare este acela ca vom putea defini acelasi obiect real în mai multe feluri pentru a-l reprezenta în memoria interna. Modul de definire depinde de problema de rezolvat si de programatorul care a gândit reprezentarea. De altfel, aceste diferente de perceptie ale unui obiect real exista si între diversi observatori umani.
Din punctul de vedere al programarii, un obiect este o reprezentare în memoria calculatorului a proprietatilor si comportamentului unei notiuni sau ale unui obiect real.
Încapsularea informatiilor în interiorul obiectelor
Exista situatii în care accesul din exterior la proprietatile unui obiect poate sa puna probleme acestuia. Din aceste motive, este preferabil sa lasam modificarea acestor parametri în sarcina exclusiva a unor operatii definite de catre obiect, operatii care vor verifica noile valori înainte de a le schimba în interiorul obiectului. În lipsa acestui filtru, putem sa stricam coerenta valorilor memorate în interiorul unui obiect, facându-l inutilizabil.
Din acest punct de vedere, putem privi obiectul ca pe un set de valori care formeaza miezul obiectului si un set de operatii care îmbraca aceste valori, protejându-le. Vom spune ca proprietatile obiectului sunt încapsulate în interiorul acestora. Mai mult, obiectul încapsuleaza si modul de functionare a operatiilor lui specifice, din exterior neputându-se observa decât modul de apelare a acestor operatii si rezultatele apelurilor.Cu alte cuvinte, procesul de încapsulare este procesul de ascundere a detaliilor neimportante sau sensibile de constructie a obiectului.
Dar nu numai proprietatile unui obiect trebuiesc protejate ci si operatiile definite de catre acesta. Unele dintre operatiile definite pentru un obiect sunt periculos de lasat la dispozitia oricui. Este preferabil sa putem controla foarte exact cine ce operatii poate apela pentru un anumit obiect.
Aceasta protejare si încapsulare a proprietatilor si operatiilor ce se pot executa cu ajutorul unui obiect are si o alta consecinta si anume aceea ca utilizatorul obiectului respectiv este independent de detaliile constructive ale obiectului respectiv. Structura interna a obiectului poate fi astfel schimbata si perfectionata în timp fara ca functionalitatea de baza sa fie afectata.
Clase de obiecte
În lumea reala se pot identifica usor familii de obiecte. Este greu sa descriem într-un limbaj de programare fiecare minge din lume dar, pentru a putea folosi orice minge din lume, este suficient sa descriem o singura data care sunt proprietatile unei mingi în general, precum si operatiile care pot fi executate cu aceasta. Aceasta nu înseamna ca toate obiectele minge din lume sunt identice. Diferenta dintre ele se afla reprezentata în primul rând în valorile proprietatilor lor care difera de la un obiect de acelasi fel la altul. De exemplu, în fiecare obiect minge vom avea un numar natural care reprezinta culoarea mingii. Acest numar poate sa difere de la o minge la alta exact asa cum, în realitate, culoarea difera de la o minge la alta. La fel coordonatele pozitiei mingii la un moment dat sau raza mingii precum si materialul din care este confectionata au valori care variaza de la o minge la alta.
Cu alte cuvinte, fiecare minge din lume are acelasi set de proprietati, dar valorile acestora pot sa difere de la o minge la alta. Modelul de reprezentare în memorie a unui obiect este întotdeauna acelasi, dar valorile memorate în locatiile corespunzatoare proprietatilor sunt în general diferite.
În ceea ce priveste operatiile, acestea sunt întotdeauna aceleasi dar rezultatul aplicarii lor poate sa difere în functie de valorile proprietatilor obiectului asupra caruia au fost aplicate. De exemplu, atunci când aruncam o minge spre pamânt ea va ricosa din acesta ridicându-se din nou în aer. Înaltimea la care se va ridica însa, este dependenta de dimensiunile si materialul din care a fost confectionata mingea. Cu alte cuvinte, noua pozitie în spatiu se va calcula printr-o operatie care va tine cont de valorile memorate în interiorul obiectului. Se poate întâmpla chiar ca operatia sa hotarasca faptul ca mingea va strapunge podeaua în loc sa fie respinsa de catre aceasta.
Sa mai observam ca operatiile nu depind numai de proprietatile obiectului ci si de unele valori exterioare acestuia. Atunci când aruncam o minge spre pamânt, înaltimea la care va ricosa aceasta depinde si de viteza cu care a fost aruncata mingea. Aceasta viteza este un parametru al operatiei de aruncare. Nu are nici un rost sa transmitem ca parametrii ai unei operatii valorile proprietatilor unui obiect pentru ca acestea sunt întotdeauna disponibile operatiei.
Nici o operatie nu se poate aplica asupra unui obiect fara sa stim exact care este obiectul respectiv si ce proprietati are acesta. Este absurd sa ne gândim la ce înaltime se va ridica o minge în general, fara sa facem presupuneri asupra valorilor proprietatilor acesteia. Sa mai observam însa ca, daca toate mingile ar avea aceleasi valori pentru proprietatile implicate în operatia descrisa mai sus, am putea sa calculam înaltimea de ricoseu în general, fara sa fim dependenti de o anumita minge.
În concluzie, putem spune ca obiectele cu care lucram fac parte întotdeauna dintr-o familie mai mare de obiecte cu proprietati si comportament similar. Aceste familii de obiecte le vom numi în continuare clase de obiecte sau concepte în timp ce obiectele apartinând unei anumite clase le vom numi instante ale clasei de obiecte respective. Putem vorbi despre clasa de obiecte minge si despre instantele acesteia, multimea tuturor obiectelor minge care exista în lume.
Fiecare instanta a clasei minge are un loc bine precizat în spatiu si în timp, un material si o culoare. Aceste proprietati difera de la o instanta la alta, dar fiecare instanta a aceleiasi clase va avea întotdeauna aceleasi proprietati si aceleasi operatii vor putea fi aplicate asupra ei. În continuare vom numi variabile aceste proprietati ale unei clase de obiecte si vom numi metode operatiile definite pentru o anumita clasa de obiecte.
Pentru a clarifica, sa mai reluam înca o data: O clasa de obiecte este o descriere a proprietatilor si operatiilor specifice unui nou tip de obiecte reprezentabile în memorie. O instanta a unei clase de obiecte este un obiect de memorie care respecta descrierea clasei. O variabila a unei clase de obiecte este o proprietate a clasei respective care poate lua valori diferite în instante diferite ale clasei. O metoda a unei clase este descrierea unei operatii specifice clasei respective.
Sa mai precizam faptul ca, spre deosebire de variabilele unei clase, metodele acesteia sunt memorate o singura data pentru toate obiectele. Comportarea diferita a acestora este data de faptul ca ele depind de valorile variabilelor.
O categorie aparte a claselor de obiecte este categoria acelor clase care reprezinta concepte care nu se pot instantia în mod direct, adica nu putem construi instante ale clasei respective, de obicei pentru ca nu avem destule informatii pentru a le putea construi. De exemplu, conceptul de om nu se poate instantia în mod direct pentru ca nu putem “construi” un om despre care nu stim exact daca este barbat sau femeie. Putem în schimb instantia conceptul de barbat si conceptul de femeie care sunt niste subconcepte ale conceptului om.
Clasele abstracte , neinstantiabile, servesc în general pentru definirea unor proprietati sau operatii comune ale mai multor clase si pentru a putea generaliza operatiile referitoare la acestea. Putem, de exemplu sa definim în cadrul clasei de obiecte om modul în care acesta se alimenteaza ca fiind independent de apartenenta la conceptul de barbat sau femeie. Aceasta definitie va fi valabila la amândoua subconceptele definite mai sus. În schimb, nu putem decât cel mult sa precizam faptul ca un om trebuie sa aiba un comportament social. Descrierea exacta a acestui comportament trebuie facuta în cadrul conceptului de barbat si a celui de femeie. Oricum, este interesant faptul ca, indiferent care ar fi clasa acestuia, putem sa ne bazam pe faptul ca acesta va avea definit un comportament social, specific clasei lui.
Cele doua metode despre care am vorbit mai sus, definite la nivelul unui superconcept, sunt profund diferite din punctul de vedere al subconceptelor acestuia. În timp ce metoda de alimentatie este definita exact si amândoua subconceptele pot sa o foloseasca fara probleme, metoda de comportament social este doar o metoda abstracta, care trebuie sa existe, dar despre care nu se stie exact cum trebuie definita.
Fiecare dintre subconcepte trebuie sa-si defineasca propriul sau comportament social pentru a putea deveni instantiabil. Daca o clasa de obiecte are cel putin o metoda abstracta, ea devine în întregime o clasa abstracta si nu poate fi instantiata, adica nu putem crea instante ale unei clase de obiecte abstracte.
Altfel spus, o clasa abstracta de obiecte este o clasa pentru care nu s-au precizat suficient de clar toate metodele astfel încât sa poata fi folosita în mod direct.
Derivarea claselor de obiecte
O alta proprietate interesanta a claselor de obiecte este aceea de ierarhizare. Practic, ori de câte ori definim o noua clasa de obiecte care sa reprezinte un anumit concept, specificam clasa de obiecte care reprezinta conceptul original din care provine noul concept împreuna cu diferentele pe care le aduce noul concept derivat fata de cel original.Aceasta operatie de definire a unei noi clase de obiecte pe baza uneia deja existente o vom numi derivare. Conceptul mai general se va numi superconcept iar conceptul derivat din acesta se va numi subconcept. În acelasi mod, clasa originala se va numi superclasa a noii clase în timp ce noua clasa de obiecte se va numi subclasa a clasei derivate.
Uneori, în loc de derivare se foloseste termenul de extindere. Termenul vine de la faptul ca o subclasa îsi extinde superclasa cu noi variabile si metode.
În spiritul acestei ierarhizari, putem presupune ca toate clasele de obiecte sunt derivate dintr-o clasa initiala, sa-i spunem clasa de obiecte generice, în care putem defini proprietatile si operatiile comune tuturor obiectelor precum ar fi testul de egalitate dintre doua instante, duplicarea instantelor sau aflarea clasei de care apartine o anumita instanta.
Ierarhizarea se poate extinde pe mai multe nivele, sub forma arborescenta, în fiecare punct nodal al structurii arborescente rezultate aflându-se clase de obiecte. Desigur, clasele de obiecte de pe orice nivel pot avea instante proprii, cu conditia sa nu fie clase abstracte, imposibil de instantiat.
Desigur, este foarte dificil sa construim o ierarhie de clase de obiecte completa, care sa contina clase de obiecte corespunzatoare fiecarui concept cunoscut. Din fericire, pentru o problema data, conceptele implicate în rezolvarea ei sunt relativ putine si pot fi usor izolate, simplificate si definite. Restrângerea la minimum a arborelui de concepte necesar rezolvarii unei anumite probleme fara a se afecta generalitatea solutiei este un talent pe care fiecare programator trebuie sa si-l descopere si sa si-l cultive cu atentie. De alegerea acestor concepte depinde eficienta si flexibilitatea aplicatiei.
O clasa de obiecte derivata dintr-o alta clasa pastreaza toate proprietatile si operatiile acesteia din urma aducând în plus proprietati si operatii noi. De exemplu, daca la nivelul clasei de obiecte om am definit forma bipeda a acestuia si capacitatea de a vorbi si de a întelege, toate acestea vor fi mostenite si de catre clasele derivate din clasa om, si anume clasa barbatilor si cea a femeilor. Fiecare dintre aceste clase de obiecte derivate îsi vor defini propriile lor proprietati si operatii pentru a descrie diferenta dintre ele si clasa originala.
Unele dintre proprietatile si operatiile definite în superclasa pot fi redefinite în subclasele de obiecte derivate. Vechile proprietati si operatii sunt disponibile în continuare, doar ca pentru a le putea accesa va trebui sa fie specificata explicit superclasa care detine copia redefinita. Operatia de redefinire a unor operatii sau variabile din interiorul unei clase în timpul procesului de derivare o vom numi rescriere.
Aceasta redefinire ne da de fapt o mare flexibilitate în constructia ierarhiei unei probleme date pentru ca nici o proprietate sau operatie definita într-un punct al ierarhiei nu este impusa definitiv pentru conceptele derivate din acest punct direct sau nu.
Revenind pentru un moment la protejarea informatiilor interne ale unui obiect sa precizam faptul ca gradul de similitudine de care vorbeam mai sus este marit în cazul în care vorbim de doua clase derivate una din cealalta. Cu alte cuvinte, o subclasa a unei clase are acces de obicei la mult mai multe informatii memorate în superclasa sa decât o alta clasa de obiecte oarecare. Acest lucru este firesc tinând cont de faptul ca, uneori, o subclasa este nevoita sa redefineasca o parte din functionalitatea superclasei sale.
Interfete spre obiecte
Un obiect este o entitate complexa pe care o putem privi din diverse puncte de vedere. Omul de exemplu poate fi privit ca un mamifer care naste pui vii sau poate fi privit ca o fiinta gânditoare care învata sa programeze calculatoare sau poate fi privit ca un simplu obiect spatio-temporal care are propria lui forma si pozitie în functie de timp.
Aceasta observatie ne spune ca trebuie sa dam definitii despre ce înseamna cu adevarat faptul ca un obiect poate fi privit ca un mamifer sau ca o fiinta gânditoare sau ca un obiect spatio-temporal. Aceste definitii, pe care le vom numi în continuare interfete, sunt aplicabile nu numai clasei de obiecte om dar si la alte clase de obiecte derivate sau nu din acesta, superclase sau nu ale acesteia. Putem sa gasim o multime de clase de obiecte ale caror instante pot fi privite ca obiecte spatio-temporale dar care sa nu aiba mare lucru în comun cu omul. Practic,atunci când construim o interfata, definim un set minim de operatii care trebuie sa apartina obiectelor care respecta aceasta interfata . Orice clasa de obiecte care declara ca respecta aceasta interfata va trebui sa defineasca toate operatiile.
Operatiile însa, sunt definite pe cai specifice fiecarei clase de obiecte în parte. De exemplu, orice obiect spatial trebuie sa defineasca o operatie de modificare a pozitiei în care se afla. Dar aceasta operatie este diferita la un om, care poate sa-si schimbe singur pozitia, fata de o minge care trebuie ajutata din exterior pentru a putea fi mutata. Totusi, daca stim cu siguranta ca un obiect este o instanta a unui clase de obiecte care respecta interfata spatio-temporala, putem linistiti sa executam asupra acestuia o operatie de schimbare a pozitiei, fara sa trebuiasca sa cunoastem amanunte despre modul în care va fi executata aceasta operatie. Tot ceea ce trebuie sa stim este faptul ca operatia este definita pentru obiectul respectiv.
În concluzie, o interfata este un set de operatii care trebuiesc definite de o clasa de obiecte pentru a se înscrie într-o anumita categorie. Vom spune despre o clasa care defineste toate operatiile unei interfete ca implementeaza interfata respectiva.
Cu alte cuvinte, putem privi interfetele ca pe niste reguli de comportament impuse claselor de obiecte. În clipa în care o clasa implementeaza o anumita interfata, obiectele din clasa respectiva pot fi privite în exclusivitate din acest punct de vedere. Interfetele pot fi privite ca niste filtre prin care putem privi un anumit obiect, filtre care nu lasa la vedere decât proprietatile specifice interfetei, chiar daca obiectul în vizor este mult mai complicat în realitate.
Interfetele creaza o alta împartire a obiectelor cu care lucram. În afara de împartirea normala pe clase, putem sa împartim obiectele si dupa interfetele pe care le implementeaza. si, la fel cu situatia în care definim o operatie doar pentru obiectele unei anumite clase, putem defini si operatii care lucreaza doar cu obiecte care implementeaza o anumita interfata, indiferent de clasa din care acestea fac parte.
Structura lexicala Java
Setul de caractere
Limbajului Java lucreaza în mod nativ folosind setul de caractere Unicode. Acesta este un standard international care înlocuieste vechiul set de caractere ASCII. Motivul acestei înlocuiri a fost necesitatea de a reprezenta mai mult de 256 de caractere. Setul de caractere Unicode, fiind reprezentat pe 16 biti are posibilitati mult mai mari.
Vechiul standard ASCII este însa un subset al setului Unicode, ceea ce înseamna ca vom regasi caracterele ASCII cu exact aceleasi coduri ca si mai înainte în noul standard.
Java foloseste setul Unicode în timpul rularii aplicatiilor ca si în timpul compilarii acestora. Folosirea Unicode în timpul executiei nu înseamna nimic altceva decât faptul ca o variabila Java de tip caracter este reprezentata pe 16 biti iar un sir de caractere va ocupa fizic în memorie de doua ori mai multi octeti decât numarul caracterelor care formeaza sirul.
În ceea ce priveste folosirea Unicode în timpul compilarii, compilatorul Java accepta la intrare fisiere sursa care pot contine orice caractere Unicode. Se poate lucra si cu fisiere ASCII obisnuite în care putem introduce caractere Unicode folosind secvente escape. Fisierele sursa sunt fisiere care contin declaratii si instructiuni Java. Aceste fisiere trec prin trei pasi distincti la citirea lor de catre compilator:
- sirul de caractere Unicode sau ASCII, memorat în fisierul sursa, este transformat într-un sir de caractere Unicode. Caracterele Unicode pot fi introduse si ca secvente escape folosind doar caractere ASCII.
- sirul de caractere Unicode este transformat într-un sir de caractere în care sunt evidentiate separat caracterele de intrare fata de caracterele de sfârsit de linie.
- sirul de caractere de intrare si de sfârsit de linie este transformat într-un sir de cuvinte ale limbajului Java.
În primul pas al citirii fisierului sursa, sunt generate secvente escape. Secventele escape sunt secvente de caractere ASCII care încep cu caracterul backslash \. Pentru secventele escape Unicode, al doilea caracter din secventa trebuie sa fie u sau U. Orice alt caracter care urmeaza dupa backslash va fi considerat ca fiind caracter nativ Unicode si lasat nealterat. Daca al doilea caracter din secventa escape este u, urmatoarele patru caractere ASCII sunt tratate ca si cifre hexazecimale (în baza 16) care formeaza împreuna doi octeti de memorie care reprezinta un caracter Unicode.
Se pot folosi la intrare si fisiere ASCII normale, pentru ca ASCII este un subset al Unicode. De exemplu, putem scrie:
int f\u0660 = 3;
Numele variabilei are doua caractere si al doilea caracter este o cifra codificata Unicode.
Exemple de secvente Unicode:
\uaa08 \U0045 \u6abe
În al doilea pas al citirii fisierului sursa, sunt recunoscute ca si caractere de sfârsit de linie caracterele ASCII CR si ASCII LF. În acelasi timp, secventa de caractere ASCII CR-ASCII LF este tratata ca un singur sfârsit de linie si nu doua. În acest mod, Java suporta în comun standardele de terminare a liniilor folosite de diferite sisteme de operare: MacOS, Unix si DOS.
Este important sa separam caracterele de sfârsit de linie de restul caracterelor de intrare pentru a sti unde se termina comentariile de o singura linie (care încep cu secventa //) precum si pentru a raporta odata cu erorile de compilare si linia din fisierul sursa în care au aparut acestea.
În pasul al treilea al citirii fisierului sursa, sunt izolate elementele de intrare ale limbajului Java, si anume: spatii, comentarii si unitati lexicale.
Spatiile pot fi caracterele ASCII SP (spatiu), FF (avans de pagina) sau HT (tab orizontal) precum si orice caracter terminator de linie.
Unitati lexicale
Unitatile lexicale sunt elementele de baza cu care se construieste semantica programelor Java. În sirul de cuvinte de intrare, unitatile lexicale sunt separate între ele prin comentarii si spatii. Unitatile lexicale în limbajul Java pot fi:
- Cuvinte cheie
- Identificatori
- Literali
- Separatori
- Operatori
Cuvinte cheie
Cuvintele cheie sunt secvente de caractere ASCII rezervate de limbaj pentru uzul propriu. Cu ajutorul lor, Java îsi defineste unitatile sintactice de baza. Nici un program nu poate sa utilizeze aceste secvente altfel decât în modul în care sunt definite de limbaj. Singura exceptie este aceea ca nu exista nici o restrictionare a aparitiei cuvintelor cheie în siruri de caractere sau comentarii.
Cuvintele cheie ale limbajului Java sunt:
|
|
|
Dintre acestea, cele îngrosate sunt efectiv folosite, iar restul sunt rezervate pentru viitoare extensii ale limbajului.
Identificatori
Identificatorii Java sunt secvente nelimitate de litere si cifre Unicode, începând cu o litera. Identificatorii nu au voie sa fie identici cu cuvintele rezervate.
Cifrele Unicode sunt definite în urmatoarele intervale:
Reprezentare Unicode | Caracter ASCII | Explicatie |
\u0030-\u0039 | 0-9 | cifre ISO-LATIN-1 |
\u0660-\u0669 | cifre Arabic-Indic | |
\u06f0-\u06f9 | cifre Eastern Arabic-Indic | |
\u0966-\u096f | cifre Devanagari | |
\u09e6-\u09ef | cifre Bengali | |
\u0a66-\ u0a6f | cifre Gurmukhi | |
\u0ae6-\u0aef | cifre Gujarati | |
\u0b66-\u0b6f | cifre Oriya | |
\u0be7-\u0bef | cifre Tamil | |
\u0c66-\u0c6f | cifre Telugu | |
\u0ce6-\u0cef | cifre Kannada | |
\u0d66-\u0d6f | cifre Malayalam | |
\u0e50-\u0e59 | cifre Thai | |
\u0ed0-\u0ed9 | cifre Lao | |
\u1040-\u1049 | cifre Tibetan |
Tabelul 1 Cifrele Unicode.
Un caracter Unicode este o litera daca este în urmatoarele intervale si nu este cifra:
Reprezentare Unicode | Caracter ASCII | Explicatie |
\u0024 | $ | semnul dolar (din motive istorice) |
\u0041-\u005a | A-Z | litere majuscule Latin |
\u005f | _ | underscore (din motive istorice) |
\u0061-\u007a | a-z | litere minuscule Latin |
\u00c0-\u00d6 | diferite litere Latin cu diacritice | |
\u00d8-\u00f6 | diferite litere Latin cu diacritice | |
\u00f8-\u00ff | diferite litere Latin cu diacritice | |
\u0100-\u1fff | alte alfabete si simboluri non-CJK | |
\u3040-\u318f | Hiragana, Katakana, Bopomofo, si Hangul | |
\u3300-\u337f | cuvinte patratice CJK | |
\u3400-\u3d2d | simboluri Hangul coreene | |
\u4e00-\u9fff | Han (Chinez, Japonez, Corean) | |
\uf900-\ufaff | compatibilitate Han |
Tabelul 2. Literele Unicode.
Literali
Un literal este modalitatea de baza de exprimare în fisierul sursa a valorilor pe care le pot lua tipurile primitive si tipul sir de caractere. Cu ajutorul literalilor putem introduce valori constante în variabilele de tip primitiv sau în variabilele de tip sir de caractere.
În limbajul Java exista urmatoarele tipuri de literali:
- literali întregi
- literali flotanti
- literali booleeni
- literali caracter
- literali sir de caractere
Literali întregi
Literalii întregi pot fi reprezentati în baza 10, 16 sau 8. Toate caracterele care se folosesc pentru scrierea literalilor întregi fac parte din subsetul ASCII al setului Unicode.
Literalii întregi pot fi întregi normali sau lungi. Literalii lungi se recunosc prin faptul ca se termina cu sufixul l sau L. Un literal întreg este reprezentat pe 32 de biti iar unul lung pe 64 de biti.
Un literal întreg în baza 10 începe cu o cifra de la 1 la 9 si se continua cu un sir de cifre de la 0 la 9. Un literal întreg în baza 10 nu poate sa înceapa cu cifra 0, pentru ca acesta este semnul folosit pentru a semnaliza literalii scrisi în baza 8.
Exemple de literali întregi în baza 10:
12356L 234871 2345678908
Exemplul al d oilea si al patrulea sunt literali întregi lungi.
Pentru a exprima un literal întreg în baza 16 trebuie sa definim cifrele de la 10 la 15. Conventia va fi urmatoarea:
10 - a, A 13 - d, D
11 - b, B 14 - e, E
12 - c, C 15 - f, F
În plus, pentru ca un literal întreg în baza 16 poate începe cu o litera, vom adauga prefixul 0x sau 0X. Daca nu am adauga acest sufix, compilatorul ar considera ca este vorba despre un identificator.
Exemple de literali întregi în baza 16:
0xa34 0X123 0x2c45L 0xde123abccdL
Ultimele doua exemple sunt literali întregi lungi.
Pentru a reprezenta un literal întreg în baza 8, îl vom preceda cu cifra 0. Restul cifrelor pot fi oricare între 0 si 7. Cifrele 8 si 9 nu sunt admise în literalii întregi în baza 8.
Exemple de literali întregi în baza 8:
0234500123001234567712345677L
Valoarea maxima a unui literal întreg normal este de 2147483647 (231-1), scrisa în baza 10. În baza 16, cel mai mare literal pozitiv se scrie ca 0x7fffffff iar în baza 8 ca 017777777777. Toate trei scrierile reprezinta de fapt aceeasi valoare, doar ca aceasta este exprimata în baze diferite.
Cea mai mica valoare a unui literal întreg normal este -2147483648 (-231), respectiv 0x80000000 si 020000000000. Valorile 0xffffffff si 037777777777 reprezinta amândoua valoarea -1.
Specificarea în sursa a unui literal întreg normal care depaseste aceste limite reprezinta o eroare de compilare. Cu alte cuvinte, daca folosim în sursa numarul: 21474836470 de exemplu, fara sa punem sufixul de numar lung dupa el, compilatorul va genera o eroare la analiza sursei.
Valoarea maxima a unui literal întreg lung este, în baza 10, 9223372036854775807L (263-1). În octal, asta înseamna 0777777777777777777777L iar în baza 16 0x7fffffffffffffffL. În mod asemanator, valoarea minima a unui literal întreg lung este -9223372036854775808L (-263-1), în octal aceasta valoare este 0400000000000000000000L iar în baza 16 este 0x8000000000000000L.
La fel ca si la literalii întregi normali, depasirea acestor limite este o eroare de compilare.
Literali flotanti
Literalii flotanti reprezinta numere reale. Ei sunt formati dintr-o parte întreaga, o parte fractionara, un exponent si un sufix de tip. Exponentul, daca exista, este introdus de litera e sau E urmata optional de un semn al exponentului.
Este obligatoriu sa existe macar o cifra fie în partea întreaga fie în partea zecimala si punctul zecimal sau litera e pentru exponent.
Sufixul care indica tipul flotantului poate fi f sau F în cazul în care avem o valoare flotanta normala si d sau D daca avem o valoare flotanta dubla. Daca nu este specificat nici un sufix, valoarea este implicit dubla.
Valoarea maxima a unui literal flotant normal este 3.40282347e+38f iar valoarea cea mai mica reprezentabila este 1.40239846e-45f, ambele reprezentate pe 32 de biti.
Valoarea maxima reprezentabila a unui literal flotant dublu este de 1.79769313486231570e+308 iar valoarea cea mai mica reprezentabila este 4.94065645841246544e-324, ambele reprezentate pe 64 de biti.
La fel ca si la literalii întregi, este o eroare sa avem exprimat în sursa un literal mai mare decât valoarea maxima reprezentabila sau mai mic decât cea mai mica valoare reprezentabila.
Exemple de literali flotanti:
1.0e45f -3.456f 0. .01e-3
Primele doua exemple reprezinta literali flotanti normali, iar celelalte literali flotanti dubli.
Literali booleeni
Literalii booleeni nu pot fi decât true sau false, primul reprezentând valoarea booleana de adevar iar celalalt valoarea booleana de fals. True si false nu sunt cuvinte rezervate ale limbajului Java, dar nu veti putea folosi aceste cuvinte ca identificatori.
Literali caracter
Un literal de tip caracter este utilizat pentru a exprima caracterele codului Unicode. Reprezentarea se face fie folosind o litera, fie o secventa escape. Secventele escape ne permit reprezentarea caracterelor care nu au reprezentare grafica si reprezentarea unor caractere speciale precum backslash si însasi caracterul apostrof.
Caracterele care au reprezentare grafica pot fi reprezentate între apostrofe, ca în exemplele:
'a' 's' ','
Pentru restul caracterelor Unicode trebuie sa folosim secvente escape. Dintre acestea, câteva sunt predefinite în Java, si anume:
Secventa escape | Caracterul reprezentat |
'\b' | caracterul backspace BS \u0008 |
'\t' | caracterul tab orizontal HT \u0009 |
'\n' | caracterul linefeed LF \u000a |
'\f' | caracterul formfeed FF \u000c |
'\r' | caracterul carriage return CR \u000d |
'\"' | caracterul ghilimele \u0022 |
'\'' | caracterul apostrof \u0027 |
'\\' | caracterul backslash \u005c |
Tabelul 3 Secvente escape predefinite în Java.
În forma generala, o secventa escape se scrie sub una din formele:
'\o' '\oo' '\too'
unde o este o cifra octala iar t este o cifra octala între 0 si 3.
Nu este corect sa folositi ca valori pentru literale caracter secventa '\u000d' (caracterul ASCII CR), sau altele care reprezinta caractere speciale, pentru ca acestea fiind secvente escape Unicode sunt transformate foarte devreme în timpul procesarii sursei în caractere CR si sunt interpretate ca terminatori de linie.
Exemple de secvente escape:
'\n' '\u23a' '\34'
daca dupa caracterul backslash urmeaza altceva decât: b, t, n, f, r, ", ', \, 0, 1, 2, 3, 4, 5, 6, 7 se va semnala o eroare de compilare.
În acest moment secventele escape Unicode au fost deja înlocuite cu caractere Unicode native. Daca u apare dupa \, se semnaleaza o eroare de compilare.
Literali sir de caractere
Un literal sir de caractere este format din zero sau mai multe caractere între ghilimele. Caracterele care formeaza sirul de caractere pot fi caractere grafice sau secvente escape ca cele definite la literalii caracter.
Daca un literal sir de caractere contine în interior un caracter terminator de linie va fi semnalata o eroare de compilare. Cu alte cuvinte, nu putem avea în sursa ceva de forma:
"Acesta este
gresit! "
chiar daca aparent exprimarea ar reprezenta un sir format din caracterele A, c, e, s, t, a, spatiu, e, s, t, e, linie noua, g, r, e, s, i, t, !. Daca dorim sa introducem astfel de caractere terminatoare de linie într-un sir va trebui sa folosim secvente escape ca în:
Acesta este\ngresit
Daca sirul de caractere este prea lung, putem sa-l spargem în bucati mai mici pe care sa le concatenam cu operatorul +.
Fiecare sir de caractere este în fapt o instanta a clasei de obiecte String declarata standard în pachetul java.lang.
Exemple de siruri de caractere:
"" "\"""sir de caractere" "unu" + "doi"
Primul sir de caractere din exemplu nu contine nici un caracter si se numeste sirul vid. Ultimul exemplu este format din doua siruri distincte concatenate.
Separatori
Un separator este un caracter care indica sfârsitul unei unitati lexicale si începutul alteia. Separatorii sunt necesari atunci când unitati lexicale diferite sunt scrise fara spatii între ele. Acestea se pot totusi separa daca unele dintre ele contin caractere separatori. În Java separatorii sunt urmatorii:
( ) { } [ ] ; , .
Exemple de separare:
a[i] sin(56)
În primul exemplu nu avem o singura unitate lexicala ci patru: a, [, i, ]. Separatorii [ si ] ne dau aceasta informatie. În al doilea exemplu, unitatile lexicale sunt tot 4 sin, (, 56, ).
Atentie, separatorii participa în acelasi timp si la constructia sintaxei limbajului. Ei nu sunt identici cu spatiile desi, ca si acestea, separa unitati lexicale diferite.
Operatori
Operatorii reprezinta simboluri grafice pentru operatiile elementare definite de limbajul Java. Lista tuturor operatorilor limbajului Java este:
= > < ! ~ ? :
= = <= >= != && || ++ --
+ - * / & | ^ % << >> >>>
+= -= *= /= &= |= ^= %= <<= >>= >>>=
Sa mai precizam deocamdata ca toti operatorii joaca si rol de separatori. Cu alte cuvinte, din secventa de caractere:
vasile+gheorghe
putem extrage trei unitati lexicale, vasile, + si gheorghe.
Comentarii
Un comentariu este o secventa de caractere existenta în fisierul sursa dar care serveste doar pentru explicarea sau documentarea sursei si nu afecteaza în nici un fel semantica programelor.
În Java exista trei feluri de comentarii:
-
Comentarii pe mai multe linii, închise între /* si */. Toate caracterele dintre cele doua secvente sunt ignorate.
-
Comentarii pe mai multe linii care tin de documentatie, închise între /** si */. Textul dintre cele doua secvente este automat mutat în documentatia aplicatiei de catre generatorul automat de documentatie.
-
Comentarii pe o singura linie care încep cu //. Toate caracterele care urmeaza acestei secvente pâna la primul caracter sfârsit de linie sunt ignorate.
În Java, nu putem sa scriem comentarii în interiorul altor comentarii. La fel, nu putem introduce comentarii în interiorul literalilor caracter sau sir de caractere. Secventele /* si */ pot sa apara pe o linie dupa secventa // dar îsi pierd semnificatia. La fel se întâmpla cu secventa // în comentarii care încep cu /* sau /**.
Ca urmare, urmatoarea secventa de caractere formeaza un singur comentariu:
/* acest comentariu /* // /* se termina abia aici: */