In lumea in care traim
suntem obisnuiti sa numim obiecte acele
entitati care sunt caracterizate prin
masa, adica materie. Prin extensie,
pot fi definite alte obiecte fara masa,
care sunt mai degraba concepte decat entitati fizice. Tot prin extensie, obiectele
pot apartine unei lumi virtuale.
Obiectul este unitatea atomica formata din reuniunea
unei stari si a
unui comportament. El prezinta o relatie de incapsulare care asigura o coeziune
interna foarte puternica
si un cuplaj slab cu exteriorul.
Obiectul isi indeplineste rolul si responsabilitatea sa cu adevarat doar in momentul in care, prin intermediul trimiterii unor mesaje,
el participa
la un scenariu de comunicatie.
Obiectele informatice definesc o reprezentare abstracta a entitatilor unor
lumi reale sau virtuale, cu scopul de
a conduce sau simula. Aceasta reprezentare abstracta reda o imagine simplificata a unui obiect care
exista in lumea perceputa de utilizator. Obiectele informatice, pe care le
numim in general obiecte, incapsuleaza o
parte din cunoasterea lumii in care ele
evolueaza.
Utilizatorii tehnologiilor
OO obisnuiesc sa considere obiectele ca fiind animate de o viata proprie,
astfel incat ele li se arata adesea intr-o perspectiva antropomorfa. Fiind
vii, obiectele lumii reale se nasc, traiesc si mor. Modelarea OO permite reprezentarea
ciclului de viata al obiectelor de-a lungul interactiunii lor.
In
cele ce urmeaza vom utiliza anumite notatii definite in limbajul UML (Unified
Modeling Language), limbaj ce va fi prezentat in capitolele urmatoare, pentru a
ilustra anumite concepte. Deoarece anumite concepte vor fi illustrate si in
limbajul Java, vor fi facute si anumite paralele intre simbolurile UML si
constructiile sintactice Java.
Clasa este
o colectie de elemente de date numite
atribute (variabile membru, proprietati, campuri, etc.) si de operatii (functii membru, metode,
etc.). Simbolul UML al unei clase poate
contine maximum 3 casete dreptunghiulare suprapuse, dintre care cea de sus
contine numele clasei, cea din mijloc atributele, iar cea de jos operatiile:
Simbolului UML anterior ii va corespunde codul Java:
1 2 3 4 5 6 7 |
class Nume { tip
atribut;
tipReturnat operatie(tipParametru parametruFormal) { //
corpul operatiei returneaza valoare de tipul tipReturnat } } |
Formatul declaratiei (semnaturii) unei operatii UML (cu un singur argument) este (ca in limbajul Pascal):
numeOperatieUML(numeArgumentUML
: tipArgumentUML) : tipReturnatUML
pe cand formatul declaratiei unei metode Java (cu un singur argument) este
(ca in C si C++):
tipReturnatJava numeMetodaJava(tipArgumentJava
numeArgumentJava)
Formatul declaratiei unui atribut UML este
(ca in limbajul Pascal):
numeAtributUML : tipAtributUML
pe cand formatul declaratiei unui atribut Java este (ca in limbajele C si C++):
tipAtributJava numeAtributJava
Clasa reprezinta tipul (domeniul de definitie) pentru variabile numite obiecte.
Un
obiect este reprezentat in UML, ca un dreptunghi in care este
plasat numele obiectului subliniat, urmat de simbolul :, si de numele clasei
careia ii apartine, de asemenea subliniat:
ceea ce in Java are ca echivalent declaratia unei variabile obiect numita numeObiect de tipul NumeClasa:
|
NumeClasa numeObiect; |
In diagrama UML, sub numele obiectului pot fi plasate perechi atribut-valoare sub forma:
numeAtribut
= valoare
Obiectul
este īncapsularea unei stări
şi a unui comportament. Obiectul reprezinta mai mult insa decat simpla insumare a acestora. Pot exista si obiecte fara stare sau fara comportament, insa toate obiectele au o
identitate. Putem asadar spune ca obiectul este o reprezentare abstractă a unor entităţi reale sau virtuale,
caracterizată de:
· identitate, prin care e deosebit de alte obiecte, implementata in general ca variabila obiect,
· comportament vizibil, accesibil altor obiecte, implementata in general ca set de functii (membru),
·
stare
internă ascunsă, proprie obiectului, implementata in general ca set de variabile (membru).
Starea obiectului regrupeaza valorile instantanee ale tuturor
atributelor unui obiect.
Atributul fiind o informatie
care califica (spune ceva despre)
obiectul caruia ii apartine.
Informatia este stocata
intr-o variabila numita si variabila
membru (sau proprietate, camp, etc.) a obiectului.
Fiecare atribut poate lua
valori intr-un domeniu definitie dat (tipul
variabilei membru).
Starea unui obiect, la un moment dat, corespunde unei selectii de valori posibile ale diferitelor
atribute.
Pentru ilustrare, sa
consideram exemplul unui obiect care
incapsuleaza informatiile privind pozitia unui punct intr-un plan (obiect
care poate fi folosit, de exemplu, pentru a pastra informatiile privind un
element de imagine).
Mai jos este definitia Java a tipului (clasei) unui
astfel de obiect.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class Point { // atribute (variabila membru) private int x; private int y; // operatie care initializeaza atributele = constructor Java public Point(int abscisa, int ordonata) { x = abscisa; y = ordonata; } // operatie care modifica atributele = metoda (functie membru) Java public void moveTo(int abscisaNoua, int ordonataNoua) { x = abscisaNoua; y = ordonataNoua; } // operatie care modifica atributele = metoda (functie membru) Java public void moveWith(int deplasareAbsc, int deplasareOrd) { x = x + deplasareAbsc; y = y + deplasareOrd; } // operatii prin care se obtin valorile atributelor = metode Java public int getX() { return x; } public int getY() { return y; } } |
| Declaratii
|(specificare) | variabile |
| | | Semnaturi | (declaratii, | specificari) | operatii | + | Implementari | (corpuri) | operatii | | | | | | | | | | | |
Simbolul UML corespunzator definitiei de clasa Java de mai sus este:
Declaratia Java:
|
Point p1 = new Point(3, 4);
|
va conduce la crearea unui obiect p1 de tip Point ale carui atribute au
valorile x=3 si respectiv y=4.
Echivalentul
UML:
Astfel,
obiectul p1
de tip Point
incapsuleaza informatiile privind un punct in plan de coordonate {3, 4}. Starea obiectului p1
este asadar
perechea de coordonate {3, 4}.
Starea unui obiect evolueaza in decursul timpului. Totusi anumite componente
ale starii pot ramane nemodificate. Starea obiectului este variabila si poate
fi vazuta ca o consecinta a
comportamentului sau trecut.
Secventa Java:
|
Point p1 = new Point(3, 4); p1.moveTo(3, 5);
|
va schimba
starea obiectului p1 in perechea de coordonate
{3, 5}, ceea ce este echivalent cu deplasarea ordonatei punctului (departarea
cu 1 a punctului de abscisa).
Echivalentul
UML:
Comportamentul regrupeaza toate posibilitatile de evolutie a unui obiect adica
actiunile si reactiile acestui obiect.
Un atom de comportament
este denumit operatie. Operatiile
sunt implementate ca functii membru ale
obiectului sau metode.
Operatiile unui obiect sunt declansate ca urmare a unei
stimulari externe, reprezentate sub forma unui mesaj care este trimis
de catre un alt obiect (care ii apeleaza/invoca functiile membru/metodele).
Exemplu de comportament (utilizare obiect), in Java. Sa luam exemplul clasei Java UtilizarePoint care exemplifica operatii asupra obiectelor clasei Point:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class UtilizarePoint { private static Point punctA; // atribut de tip Point
public static void main(String[] args) { // declaratie metoda // corp metoda punctA = new Point(3, 4); // alocare si initializare atribut punctA |
punctA.moveTo(3, 5); // trimitere mesaj moveTo() catre punctA
|
punctA.moveWith(3, 5); // trimitere mesaj moveWith() catre punctA
} } |
Echivalentul UML:
Interactiunile intre
obiecte pot fi reprezentate prin intermediul unor
diagrame (numite diagrame de colaborare intre obiecte) in care obiectele care
interactioneaza sunt legate intre ele prin linii continue denumite legaturi.
In diagrama UML urmatoare, in functie de valoarea mesajului, se
declanseaza fie operatia moveTo() fie operatia moveWith().
Fig. x. Mesajul serveste ca selector al
operatiei de executat
Prezenta
unei legaturi (linii)
semnifica faptul ca un obiect cunoaste
sau vede un alt obiect, ca ii poate apela/invoca functiile
membru/metodele, ca poate comunica
cu acesta prin intermediul mesajelor declansate de
apelurile/invocarile functiilor membru/metodelor.
Mesajele navigheaza de-a lungul legaturilor, implicit in ambele
directii.
Starea si comportamentul sunt dependente.
Comportamentul la un moment dat depinde de starea curenta. Starea poate fi modificata prin comportament.
Cat timp un
avion este La sol, nu e posibil a-l face sa aterizeze. Altfel spus, in starea La sol comportamentul
Aterizare nu e valid.
Fig. x. Dependenta
comportamentului (realizarii operatiilor) de stare (valorilor atributelor).
In
starea In zbor comportamentul Aterizare e posibil. El conduce la schimbarea starii, din In zbor in La sol.
Dupa aterizare, starea fiind La sol, operatia Aterizare
nu mai are sens.
Existenta
proprie a unui obiect este
caracterizata de identitate,
care permite distingerea tuturor
obiectelor intr-o maniera non-ambigua si independenta de starea lor.
Astfel pot fi tratate distinct
doua obiecte ale caror atribute au valori identice.
In faza de modelare identitatea nu se reprezinta
intr-o forma specifica, obiectul avand o identitate
implicita. In faza de implementare,
identitatea este adesea construita utilizand un identificator (variabila
obiect) rezultat natural din domeniul problemei.
Sistemele informatice OO pot fi vazute ca societăţi
de obiecte care conlucrează (colaborează)
pentru a realiza funcţiile aplicaţiei.
Conlucrarea se bazează pe comunicaţia īntre obiectele componente.
Mesajul este forma de reprezentare a stimulului extern care declanşează o operaţie.
Mesajul este unitatea
de comunicaţie īntre
obiecte, suportul unei relaţii de comunicaţie care leagă īn mod dinamic obiecte care au fost separate
prin procesul de descompunere.
Legătura este o cale
īntre obiectele care se cunosc (văd)
unul pe altul (īşi pot transmite mesaje), pentru aceasta avand
referinţe unul către celălalt.
Legătura statică este realizată la compilare (compile-time),
legatura dinamică este
creată īn timpul execuţiei (run-time).
Interacţiunea este rezultatul
(posibilităţii) transmiterii mesajelor īntre obiecte legate.
Mesajul permite interacţiunea flexibilă, fiind simultan agent de
cuplaj şi agent de
decuplare. Mesajul este un integrator dinamic care compune o funcţie a aplicaţiei
prin punerea īn colaborare a unui grup
de obiecte.
Abstractizarea este capacitatea
(aptitudinea, caracteristica) umană
care constă īn concentrarea
gāndirii pe un element sau aspect al unei reprezentări sau al
unei noţiuni, atenţia
īndreptāndu-se īn special asupra acestuia şi neglijāndu-le pe toate celelalte.
Abstractizarea OO īnseamnă
identificarea caracteristicilor comune ale unui ansamblu de elemente (obiecte), urmată de descrierea īntr-o formă condensată a acestor
caracteristici īn ceea ce se
numeşte clasă.
Abstractizarea este arbitrară, deoarece ea se
defineşte īn raport cu un punct de vedere.
Generalităţile (elementele comune obtinute prin abstractizare) sunt descrise īn clasă iar particularităţile
(elementele distincte) sunt descrise īn
obiecte.
Sa analizam, pentru exemplificare, o parte din codul clasei DatagramPacket (am compactat continuturile anumitor metode, pentru simplificarea intelegerii codului):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// Cod care face parte din pachetul claselor Java pentru comunicatii (retea)
package java.net;
// Clasa care incapsuleaza pachete UDP (datagrame). // Clasa finala, nu poate fi extinsa prin mostenire
public final class DatagramPacket {
// Atribute, accesibile tuturor claselor in pachetul java.net
byte[] buf; // tabloul de octeti care contine datele pachetului int offset; // indexul de la care incep datele pachetului int length; // numarul de octeti de date din tabloul de octeti int bufLength; // lungimea tabloului de octeti InetAddress address; // adresa IP a masinii sursa/destinatie a pachetului int port; // portul UDP al masinii sursa/destinatie a pachetului
// Constructori initializeaza obiectele de tip DatagramPacket
// Initializeaza un DatagramPacket pentru pachete de receptionat, public DatagramPacket(byte buf[], int length) { this.buf = buf; this.length = length; this.bufLength = length; this.offset = 0; this.address = null; this.port = -1; } // Initializeaza un DatagramPacket pentru pachete de trimis public DatagramPacket(byte buf[], int length, InetAddress address, int port) { this.buf = buf; this.length = length; this.bufLength = length; this.offset = 0; this.address = address; this.port = port; }
// Alti constructori, alte metode...
// Metoda - Returneaza adresa IP a masinii sursa/destinatie a acestui pachet public synchronized InetAddress getAddress() { return this.address; } // Metoda - Returneaza portul UDP al masinii sursa/destinatie a acestui pachet public synchronized int getPort() { return this.port;
|
}
} |
Dupa cum se poate observa, orice obiect din clasa (de tipul) DatagramPacket are 6 atribute:
- buf un tablou de octeti in care sunt plasate datele care formeaza pachetul,
- offset indexul in tabloul buf de la care sunt plasate datele,
- length numarul de octeti de date din
tabloul buf,
- bufLength lungimea tabloului buf (numarul de octeti
de date care pot fi plasati in buf),
- address adresa IP (incapsulata intr-un
obiect de tip InetAddress) a masinii destinatie,
- port portul UDP al masinii catre care se
trimite pachetul (destinatie).
Acele obiecte de tip DatagramPacket care sunt folosite pentru
trimiterea pachetelor au nevoie la initializare de
specificarea adresei
IP si portului UDP ale masinii
destinatie a pachetului.
De exemplu:
|
byte[] bufferDate = new byte[1024]; DatagramPacket packetT = new DatagramPacket(bufferDate, bufferDate.length, InetAddress.getByName(nume.elcom.pub.ro), 2000);
|
obiectul packetT este initializat pentru a putea trimite cel mult 1024 octeti plasati in tabloul de octeti bufferDate catre portul UDP 2000 al masinii nume.elcom.pub.ro.
Obiectele de
tip DatagramPacket care sunt folosite pentru receptia pachetelor nu au nevoie la initializare de specificarea adresei IP si portului UDP ale masinii sursa a pachetului.
De exemplu:
|
byte[] bufferDate = new byte[1024]; DatagramPacket packetR = new DatagramPacket(bufferDate, bufferDate.length);
|
obiectul packetR este
initializat pentru a putea primi cel mult 1024 octeti in tabloul de octeti bufferDate de la
orice masina. Dupa receptia pachetului UDP, prin utilizarea metodelor getAddress() si getPort() pot fi obtinute adresa IP si
portul UDP ale masinii sursa a pachetului.
Clasa DatagramPacket contine
in definitia sa elementele
comune (cele 6 atribute) ale
tuturor obiectelor pachet UDP.
Obiectele pachet UDP create ca variabile avand ca tip
clasa
DatagramPacket contin detalii care le particularizeaza. Dupa cum tocmai
am aratat, felul in care sunt
initializate atributele adresa IP si port UDP ale obiectelor clasei DatagramPacket, diferentiaza
aceste obiecte in pachete utilizabile pentru receptie si pachete utilizabile pentru transmisie.
Clasa descrie domeniul de
definiţie al unui ansamblu de obiecte, fiind tipul de date abstract al
ansamblului de obiecte.
Construirea obiectelor informatice
pornind de la clase poartă numele de instanţiere sau exemplificare,
obiectele fiind instanţe (de
la instance) sau exemple ale unor clase.
Descrierea
claselor cuprinde:
- specificaţia (interfaţa) clasei, care:
- descrie domeniul de
definiţie şi proprietăţile
instanţelor clasei,
- corespunde noţiunii de tip,
aşa cum este el definit īn limbajele de programare clasice;
- implementarea (realizarea) clasei, care:
- descrie modul
īn care este implementată specificarea,
- descrie conţinutul corpului fiecărei operaţii şi datele necesare funcţionării acesteia.
Clasa stabileşte un contract cu alte clase, īn
care se angajează să
furnizeze serviciile publice care formează specificaţia
sa (API = Application
Programming Interface), celelalte clase angajāndu-se să nu utilizeze alte
cunoştinţe decāt cele descrise īn acestă specificaţie.
Separarea īntre specificaţie şi implementare participă la ridicarea nivelului de abstracţie al clasei:
· informaţiile importante sunt descrise īn specificaţie (vizibilă, accesibilă, publică), şi
· detaliile sunt precizate īn implementare (ascunsă, inaccesibilă, privată).
Exemplul numerelor complexe ilustrează bine separarea specificatiei de implementare. Specificatia generala a unei clase Complex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class Complex {
// Atribute private (ascunse, inaccesibile din exteriorul clasei)
// Constructor initializeaza obiectele de tip Complex public Complex(float real, float imag) { // Implementare } // Constructor initializeaza obiectele de tip Complex public Complex(double modul, double faza) { // Implementare } // Returneaza partea reala public double getReal() { // Implementare } // Returneaza partea imaginara public double getIumag() { // Implementare } // Returneaza modulul public double getModul() { // Implementare } // Returneaza faza public double getFaza() {
|
// Implementare
}
} |
Implementarea carteziana (atributele
ascunse sunt coordonatele
carteziene) a clasei Complex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class Complex {
// Atribute private (ascunse, inaccesibile din exteriorul clasei) private double real; // partea reala (abscisa) private double imag; // partea imaginara (ordonata)
public Complex(float real, float imag) { this.real = real; this.imag = imag; } public Complex(double modul, double faza) { this.real = modul * Math.cos(faza); this.imag = modul * Math.sin(faza); } public double getReal() { return this.real; } public double getImag() { return this.imag;
|
}
public double getModul() { return Math.sqrt(this.real*this.real + this.imag*this.imag); }
|
public double getFaza() {
return Math.atan2(this.real, this.imag);
}
} |
Implementarea polara (atributele
ascunse sunt coordonatele polare)
a clasei Complex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class Complex {
// Atribute private (ascunse, inaccesibile din exteriorul clasei) private double modul; // modulul (raza) private double faza; // faza (unghiul)
public Complex(float real, float imag) { this.modul = Math.sqrt(real*real + imag*imag); this.faza = Math.atan2(real, imag); } public Complex(double modul, double faza) { this.modul = modul; this.faza = modul; } public double getReal() { return this.modul*Math.cos(this.faza); } public double getImag() {
|
return this.modul*Math.sin(this.faza);
}
public double getModul() { return this.modul; } public double getFaza() {
|
return this.faza;
}
} |
In cazul de mai sus:
·
specificaţia nu este afectată de schimbarea reprezentării interne (polară
sau carteziană),
·
obiectele utilizator al
obiectelor instanţe ale clasei numerelor complexe cunosc doar specificaţia şi nu sunt nici ele afectate de schimbarea reprezentării interne.
Incapsularea inseamna, pe langa regruparea unor
elemente (de date atributele - si de comportament - operatiile) si ascunderea si protectia detaliilor.
Avantajele īncapsulării:
·
datele īncapsulate īn obiecte sunt protejate de accesul nedorit, fiindu-le garantată integritatea,
·
utilizatorii unei abstracţii nu depind de implementarea acestei
abstracţii ci de
specificaţia ei, ceea ce reduce
cuplajul īntre modele.
Exemplul numerelor complexe ilustrează si ascunderea detaliilor.
Implicit:
·
valorile atributelor unui obiect sunt ascunse īn obiecte şi nu pot fi
manevrate direct de către alte obiecte (atributele sunt implicit private),
·
operatiile sunt publice, toate interacţiunile īntre obiecte sunt
efectuate declanşānd
diversele operaţii declarate īn specificaţia clasei şi
accesibile altor obiecte.
Urmatorul cod Java:
|
Complex c1 = new Complex(2, -2); System.out.println(Coordonatele carteziene ale lui c1 sunt { + c1.getReal() + , + c1.getImag() + }); System.out.println(Coordonatele polare ale lui c1 sunt { + c1.getModul() + , + c1.getFaza() + });
|
va conduce la acelasi rezultat,
indiferent de implementarea clasei Complex.
Utilizatorii clasei Complex nu pot afla cum arata implementarea interna a clasei,
care este ascunsa.
Metodele getReal(), getImag(), getModul() si respectiv getFaza() permit accesul la
informatia incapsulata, pe cand atributele sunt inaccesibile in mod direct, iar detaliile privind implementarea
fiind complet inaccesibile.
Legăturile
particulare care unesc obiectele pot fi văzute la modul abstract
īn lumea claselor, fiecărei
familii de legături
īntre obiecte ale
aceloraşi clase corespunzāndu-i o relaţie īntre
clasele acelor obiecte.
Legăturile sunt instanţe
ale relaţiilor īntre clase.
Asocierea este abstracţia
legăturilor care există īntre obiectele instanţe
ale claselor asociate, este implicit bidirecţională, nu este conţinută şi
nici subordonată claselor.
Relaţia exprimă o formă de cuplaj īntre abstracţii, forţa acestui cuplaj depinzānd
de natura relaţiei īn doemniul problemei.
Implicit, asocierea exprimă un cuplaj redus,
clasele asociate rămānānd relativ independente una de alta.
Clasele Point si UtilizarePoint sunt de exemplu
intr-o relatie de asociere
(cu navigabilitate) unidirectionala, clasa UtilizarePoint avand un atribut pointA de tip Point care permite
clasei UtilizarePoint sa trimita mesaje unui obiect (pointA) al clasei Point.
|
private Point punctA; // atribut de tip Point |
Clasa Point in schimb nu are nici o referinta
catre clasa UtilizarePoint care sa ii permita trimiterea de mesaje (invocari de metode). Legatura intre obiectele celor
doua clase fiind
unidirectionala, asocierea dintre ele este tot unidirectionala.
In UML asocierile unidirectionale se reprezinta prin
sageti indreptate pe
directia pe care se pot trimite mesaje (catre care exista referinta).
Asocierile bidirectionale intre doua clase corespund
situatiei in care ambele
clase au referinte una catre cealalta.
Diagrama UML:
are drept corespondent codul
Java:
|
public class ConstructiePachet { LivrarePachet consumator; public void crearePachet( /* eventuali parametri */ ) { // creare consumator.trimiterePachet() } } |
|
public class LivrarePachet { ConstructiePachet producator; public void trimiterePachet( /* eventuali parametri */ ) { // trimitere producator.crearePachet() } } |
Agregarea este o formă particulară
de asociere care exprimă un cuplaj strāns īntre clase,
·
una dintre clase joacă un rol mai important decāt cealaltă,
·
reprezintă
relaţii de subordonare
de tip master-slave, īntreg-părţi
sau ansamblu-componentă
Notatia UML pentru agregare o linie
care uneste simbolul claselor, terminata
cu un romb in capatul dinspre clasa cu rol mai important (agregat).
Un bun exemplu este obiectul out, un atribut cu caracter
global (static, de clasa) al clasei java.lang.System. Clasa System incapsuleaza o parte din resursele hardware si software ale
sistemului de executie.
Obiectul out, al carui tip este clasa java.io.PrintStream, incapsuleaza informatiile
privind consola standard de
iesire, una dintre resursele sistemului de executie incapsulata
in clasa System.
Generalizarea:
· extragerea elementelor comune (atribute, operaţii şi constrāngeri) ale unui ansamblu de clase īntr-o nouă clasă mai generală, denumită superclasă,
·
superclasa este o abstracţie a subclaselor ei,
·
arborii de clase sunt construiţi pornind de la frunze
· utilizată din momentul īn care elementele modelului au fost identificate, pentru a obţine o descriere detaşată a soluţiilor
Generalizarea semnifică "este
un" sau "este
un fel de", şi priveşte doar clasele,
adică nu este
instanţiabilă. De asemenea, generalizarea nu este o relaţie reflexivă, este asimetrică şi tranzitivă.
De fapt, generalizarea actioneaza in OO la doua niveluri:
- clasele sunt generalizari ale ansamblurilor de obiecte (un obiect este de felul specificat de o clasa),
- superclasele sunt generalizari ale unor clase (obiectele de felul specificat intr-o clasa sunt in acelasi timp si de felul specificat in superclasa).
Orientarea spre obiecte (OO) presupune ambele tipuri de generalizare, iar limbajele orientate spre obiecte sunt acelea care ofera ambele mecanisme de
generalizare. Limbajele
care ofera doar constructii numite obiecte (si eventual clase) se pot
numi limbaje care lucreaza cu
obiecte (si eventual clase).
Specializarea:
· inseamna capturarea particularităţilor unui ansamblu de obiecte nediscriminate ale unei clase existente, noile caracteristici fiind reprezentate īntr-o nouă clasă mai specializată, denumită subclasă,
·
este utilă pentru extinderea coerentă a unui ansamblu de clase
·
este bază a
programării prin extindere şi a reutilizării, noile
cerinţe fiind īncapsulate īn subclase care extind īn mod armonios
(coerent) funcţiile existente
Īn elaborarea
unei ierarhii de clase, se cer diferite aptitudini sau competenţe:
· capacitate de abstractizare, independentă de cunoştinţele tehnice, pentru identificarea superclaselor (pentru generalizare),
· experienţă şi cunoştinţe aprofundate īntr-un domeniu particular, pentru implementarea subclaselor (pentru specializare).
Pe de altă parte, există
un paradox:
·
e dificil de găsit superclase, dar programele scrise cu
ajutorul lor sunt mai uşor
de dezvoltat,
·
e destul de uşor de găsit subclase, dar dificil de implementat.
Ierarhiile de clase, sau clasificările, obtinute prin generalizare (sau
specializare) servesc managementului complexităţii prin ordonarea obiectelor īn arborescenţe de clase
abstracte.
Problema
clasificării (J.J.Rousseau, 1755):
"Fiecare obiect primeşte mai īntāi un nume particular,
fără a se ţine seama de gen sau de tip. Dacă un copac se
numeşte A, un altul se numeşte B, căci prima idee care vine privind
două lucruri, este că ele nu sunt aceleaşi, şi este necesar adesea să
trecă mult timp pentru a observa ceea ce au īn comun, astfel
īncāt multe cunoştinţe
rămān limitate şi multe definiţii devin prea īntinse. Aglomerarea tuturor
acestor denumiri nu poate fi
clasificată uşor, căci pentru a le dispune sub
denumiri comune şi generice, este
necesară cunoaşterea proprietăţilor şi a
diferenţelor, sunt
necesare observaţii şi definiţii, adică de
istorie şi metafizică, mult mai mult decāt timpul de care
dispune omul."
Clasificarea trebuie să discrimineze obiectele, să fie stabilă, să fie extensibilă.
Clasificarea se realizează conform unor criterii care depind de diverse perspective,
aşa īncāt nu există
clasificare, ci clasificări, fiecare potrivită unei
utilizări date.
Există
numeroase moduri de realizare a
clasificării, īn programarea OO tehnica cea mai utilizată fiind moştenirea īntre clase.
Moştenirea este o tehnică de generalizare oferită
de limbajele de programare OO pentru a construi o clasă pornind de la una sau mai multe
alte clase, partajānd
atributele, operaţiile şi uneori constrāngerile, īntr-o ierarhie de clase.
In limbajul Java, orice clasa care nu extinde (prin mostenire) in
mod explicit o alta clasa Java, extinde (prin mostenire) in mod implicit clasa Object (radacina ierarhiei de clase Java), clasa
care contine metodele necesare tuturor obiectelor Java.
Urmatoarea declaratie de clasa:
|
class NumeClasa { // urmeaza
corpul clasei ... |
este echivalenta cu:
|
class NumeClasa extends Object { // urmeaza
corpul clasei ... |
Notatia UML pentru extinderea prin mostenire este o linie care uneste clasa extinsa (de
baza, superclasa) de clasa care
extinde (subclasa), linie terminata
cu un triunghi in capatul dinspre clasa de baza. Diagrama UML corespunzatoare codului Java
anterior:
Printre metodele declarate in clasa Object este si toString(), metoda care are ca scop returnarea sub forma de String a informatiilor pe care le incapsuleaza obiectul caruia i se aplica aceasta metoda.
In cazul claselor de biblioteca Java, metoda toString() returneaza ansamblul valorilor curente ale atributelor obiectului.
In cazul claselor scrise de programator, in mod implicit metoda toString() returneaza numele clasei careia ii apartine obiectul urmat de un cod alocat acelui obiect (hashcode). Implementarea implicita a metodei toString() este urmatoarea:
1 2 3 4 5 6 7 |
// Implementarea
implicita a metodei toString(), // mostenita de la clasa
Object public String toString()
{ // (nu returneaza continutul ci
numele clasei si codul obiectului!) return getClass().getName()
+ "@" + Integer.toHexString(hashCode()); } |
In cazul in care programatorul doreste returnarea informatiilor incapsulate in obiect, trebuie specificat in mod explicit un nou cod (o noua implementare) pentru metoda toString(). Acest lucru se obtine adaugand clasei din care face parte acel obiect o metoda cu declaratia:
|
public String toString() { // urmeaza corpul metodei ... |
metoda care se spune ca rescrie (overrides) codul metodei cu acelasi nume din clasa extinsa (in acest caz clasa Object).
Dupa adaugarea acestei metode, apelul toString() va conduce la executia noului cod, pe cand apelul super.toString() va conduce la executia codului din clasa extinsa (superclasa, in acest caz codul
implicit din clasa Object).
Printre metodele declarate in
clasa Object este
si metoda equals(), metoda
care are ca scop compararea
obiectului caruia i se aplica aceasta metoda cu un obiect pasat ca
parametru, returnand valoarea booleana true in cazul egalitatii si valoarea
booleana false in
cazul inegalitatii celor doua obiecte.
In cazul claselor de biblioteca Java, metoda equals() compara ansamblul valorilor
curente ale atributelor obiectului (continutul sau starea obiectului).
Iata, de exemplu, implementarea metodei equals() in cazul clasei String.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Implementarea explicita a
metodei equals() in clasa String
public boolean equals(Object
obj) {
// se verifica existenta unui parametru
(obiect) non-null // si faptul ca parametrul e obiect al
clasei String
if ((obj != null)
&& (obj instanceof
String)) {
String otherString
= (String)obj; // conversie de tip
int n = this.count;
if (n == otherString.count)
{ // se compara numarul
de caractere
char v1[] = this.value;
char v2[] = otherString.value;
int i = this.offset;
int j = otherString.offset;
while (n-- != 0) if (v1[i++] != v2[j++])
return false; // se compara caracterele
return true;
}
}
return false;
} |
In cazul claselor scrise de programator, in mod implicit metoda equals() compara referinta obiectului caruia i se aplica aceasta metoda cu referinta obiectului pasat ca parametru. Implementarea implicita a metodei equals() este urmatoarea:
1 2 3 4 5 6 |
// Implementarea
implicita a metodei equals(), // mostenita de la clasa
Object public boolean equals(Object obj) {
return (this == obj); // (nu compara
continutul ci referintele!!!) } |
In cazul in care programatorul doreste compararea informatiilor incapsulate in obiect, (ansamblul valorilor curente ale atributelor obiectului) trebuie specificat in mod explicit un nou cod (o noua implementare) pentru metoda equals(). Acest lucru se obtine adaugand clasei din care face parte acel obiect o metoda cu declaratia:
|
public boolean equals(Object obj) { // urmeaza corpul metodei ... |
metoda care rescrie (overrides) codul metodei cu acelasi nume din clasa extinsa (in acest caz clasa Object).
Dupa adaugarea acestei metode, apelul equals() va conduce la executia noului cod, pe cand apelul super.equals()duce la executia codului din clasa extinsa (in acest caz codul din Object).
Simbolul UML
pentru o metoda abstracta
(declarata abstract) prevede scrierea numelui ei inclinat (font Italic). Clasele abstracte sunt reprezentate in UML prin nume scris
inclinat (Italic).
Orice clasa Java care contine cel putin o
metoda abstracta trebuie neaparat declarata abstract (in caz contrar nu poate fi compilata!).
Codul Java corespunzator diagramei UML de
mai sus este
urmatorul:
1 2 3 |
public
abstract class ClasaAbstracta { public abstract void metodaNeimplementata(); } |
Pentru a se putea crea obiecte avand ca tip o clasa abstracta, ea trebuie extinsa prin mostenire, si toate metodele ei neimplementate trebuie implementate in subclasa concreta respectiva.
Codul Java corespunzator diagramei UML de
mai sus este
urmatorul:
1 2 3 4 5 6 7 |
public class ClasaConcreta
extends ClasaAbstracta { public void metodaNeimplementata()
{ // metoda trebuie sa nu fie abstracta pentru a nu fi abstracta si clasa } } |
Exemplul
urmator ilustreaza situatiile
de mostenire, ascundere si adaugare de atribute, si de mostenire,
rescriere si adaugare de metode, in subclase.
Diagramei UML:
ii corespunde codul Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public
class ClasaDeBaza { protected int atributMostenit;
// atribut partajat cu
subclasa protected byte atributAscuns; // atribut corespunzator clasei de
baza public void metodaMostenitaSupraincarcata() { // implementare corespunzatoare
lipsei parametrilor } public void metodaMostenitaSupraincarcata(int argument) { // implementare corespunzatoare parametrului de
tip int } public
void metodaRescrisa()
{ // implementare de baza } } |
Diagramei UML:
ii corespunde codul Java:
1 2 3 4 5 6 7 8 9 10 11 12 |
public
class ClasaCareExtinde extends
ClasaDeBaza { protected byte atributAscuns; // atribut corespunzator subclasei protected long atributNou; // atribut nou, nepartajat cu
clasa de baza public void metodaRescrisa() { // reimplementare (rescriere a
codului) } public void metodaNoua() { // metoda noua, nepartajata cu clasa de baza } } |
Urmatorul cod Java ilustreaza modul de utilizare a atributelor si
metodelor de mai sus.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 58 |
Public
class UtilizareClase { public static void main(String[] args) { ClasaDeBaza obiectDeBaza = new ClasaDeBaza(); // clasa de baza (extinsa) obiectDeBaza.atributMostenit // utilizarea atributului partajat cu subclasa obiectDeBaza.atributAscuns // utilizarea atributului din clasa de baza // apelul metodei din
clasa de baza (corespunzatoare lipsei parametrilor) obiectDeBaza.metodaMostenitaSupraincarcata() // apelul metodei din
clasa de baza (corespunzatoare parametrului tip int) obiectDeBaza.metodaMostenitaSupraincarcata(1000) // apelul metodei din
clasa de baza (implementare de baza) obiectDeBaza.metodaRescrisa() ClasaCareExtinde obiectExtins = new ClasaCareExtinde(); // subclasa obiectExtins.atributNou // utilizarea
atributului nou obiectExtins.atributMostenit // utilizarea atributului partajat obiectExtins.atributAscuns // utilizarea atributului din subclasa super.atributAscuns // utilizarea
atributului din clasa de baza // apelul metodei noi
din subclasa obiectExtins.metodaNoua() // apelul metodei din
clasa de baza (corespunzatoare lipsei parametrilor) obiectExtins.metodaMostenitaSupraincarcata() // apelul metodei din clasa
de baza (corespunzatoare parametrului tip int) obiectExtins.metodaMostenitaSupraincarcata(1000) // apelul metodei din
subclasa (implementarea noua, rescrisa) obiectExtins.metodaRescrisa() // apelul metodei din
clasa de baza (implementare de baza) super.metodaRescrisa() } } |
Sa reluam exemplul claselor care
incapsuleaza informatiile privind numere complexe.
Vom considera o clasa Java abstracta (din care nu pot fi create in mod direct instante, obiecte), ComplexAbstract, care contine partea comuna a
interfetei publice a claselor concrete (din care pot fi create
in mod direct instante, obiecte) care reprezinta numerele complexe.
1 2 3 4 5 6 7 8 9 10 11 12 |
//
Clasa abstracta (din care nu
pot fi create direct obiecte) //
care reprezinta partea comuna
a interfetei publice a claselor //
ComplexPolar si ComplexCartezian public abstract class ComplexAbstract { // Metode abstracte, neimplementate public abstract double getReal(); public abstract double getImag(); public abstract double getModul(); public abstract double getFaza(); } |
Clasa abstracta ComplexAbstract poate fi extinsa prin mostenire (extends) de o
clasa concreta care reprezinta numerele complexe in format cartezian,
ComplexCartezian.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//
Clasa concreta, din care pot
fi create direct obiecte, care extinde //
(mosteneste) clasa abstracta
ComplexAbstract public class ComplexCartezian extends ComplexAbstract { // Atribute private (ascunse,
inaccesibile din exteriorul clasei) private double real; private double imag; public void Complex(float real, float imag) { this.real = real; this.imag = imag; } public void Complex(double
modul, double faza) { this.real = modul * Math.cos(faza); this.imag = modul *
Math.sin(faza); } public double getReal() { return this.real; } public double getImag() { return this.imag; } public double getModul() { return Math.sqrt(this.real*this.real + this.imag*this.imag); } public double getFaza() { return Math.atan2(this.real, this.imag); } } |
Clasa abstracta ComplexAbstract poate fi extinsa prin mostenire si de o clasa concreta care reprezinta
numerele complexe in format polar, ComplexPolar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//
Clasa concreta, din care pot
fi create direct obiecte, care extinde //
(mosteneste) clasa abstracta
ComplexAbstract public class ComplexPolar extends ComplexAbstract { // Atribute private (ascunse,
inaccesibile din exteriorul clasei) private double modul; private double faza; public void Complex(float real,
float imag) { this.modul = Math.sqrt(real*real + imag*imag); this.faza = Math.atan2(real,
imag); } public void Complex(double modul, double faza) { this.modul = modul; this.faza = modul; } public double getReal() { return this.modul*Math.cos(this.faza); } public double getImag() { return this.modul*Math.sin(this.faza); } public double getModul() { return this.modul; } public double getFaza() { return this.faza; } } |
Diagrama UML echivalenta:
Moştenirea multiplă
·
poate conduce la conflicte
(coliziuni) de nume, fapt pentru care Ada95 şi Java nu oferă moştenire multiplă,
·
nu trebuie să fie modalitatea
prin care să se realizeze o fuziune īntre două ansambluri de
clase construite īn mod independent,
· utilizarea ei trebuie anticipată.
Moştenirea nu este o necesitate absolută, ea putānd fi īnlocuită īntotdeauna prin delegare.
Implementarea interfetelor este
alternativa Java la mostenirea
multipla (extinderea mai multor clase), deoarece in Java mostenirea
multipla nu este permisa.
In Java, dar si in UML, exista notiunea de interfata,
care semnifica un caz
particular de clasa, care poate
contine doar metode (nu si atribute, ci doar cel mult variabile
finale constante) abstracte
(neimplementate).
Astfel, clasa ComplexAbstract ar putea fi inlocuita cu o interfata ComplexInterface:
1 2 3 4 5 6 7 8 9 10 11 |
// Interfata
(colectie de metode neimplementate) // care reprezinta un contract privind interfata publica public interface
ComplexInterface { // Metode abstracte, neimplementate public abstract double getReal(); public abstract double getImag(); public abstract double getModul(); public abstract double getFaza(); } |
Reprezentarea in UML este (se observa linia intrerupta care semnifica implementarea unei interfete, spre deosebire de cea continua care semnifica extinderea unei clase):
Interfata reprezinta un contract privind setul de metode pe care trebuie sa le implementeze clasele concrete care implementeaza (concretizeaza) interfata.
Interfata ComplexInterface poate fi concretizata, implementata (implements), de
o clasa concreta care reprezinta numerele complexe in format cartezian,
ComplexCartezianI.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//
Clasa concreta, din care pot
fi create direct obiecte, care //
implementeaza (concretizeaza) interfata
ComplexInterface public class ComplexCartezianI implements
ComplexInterface { // Atribute private (ascunse,
inaccesibile din exteriorul clasei) private double real; private double imag; public void Complex(float real, float imag) { this.real = real; this.imag = imag; } public void Complex(double
modul, double faza) { this.real = modul * Math.cos(faza); this.imag = modul *
Math.sin(faza); } public double getReal() { return this.real; } public double getImag() { return this.imag; } public double getModul() { return Math.sqrt(this.real*this.real + this.imag*this.imag); } public double getFaza() { return Math.atan2(this.real, this.imag); } } |
Interfata ComplexInterface poate fi concretizata, implementata, si de o clasa concreta care
reprezinta numerele complexe in format polar, ComplexPolarI.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//
Clasa concreta, din care pot
fi create direct obiecte, care //
implementeaza (concretizeaza) interfata
ComplexInterface public class ComplexPolarI implements
ComplexInterface { // Atribute private (ascunse,
inaccesibile din exteriorul clasei) private double modul; private double faza; public void Complex(float real,
float imag) { this.modul = Math.sqrt(real*real + imag*imag); this.faza = Math.atan2(real, imag); } public void Complex(double modul, double faza) { this.modul = modul; this.faza = modul; } public double getReal() { return this.modul*Math.cos(this.faza); } public double getImag() { return this.modul*Math.sin(this.faza); } public double getModul() { return this.modul; } public double getFaza() { return this.faza; } } |
Un
alt bun exemplu de implementare
a unei interfete este cel al unei stive. Interfata specifica acele metode pe care trebuie sa le implementeze o clasa pentru a avea
comportamentul dorit (de stiva in acest caz).
1 2 3 4 5 6 |
public
interface StackInterface { boolean empty(); void push( Object x); Object pop() throws EmptyStackException; Object peek() throws EmptyStackException; } |
Iata
codul unei clase stiva (Stack) care implementeaza interfata de mai sus, utilizand un obiect lista
inlantuita (Vector) sub
forma unui atribut privat
(inaccesibil vreunui cod extern).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Stack implements StackInterface { private Vector v = new Vector(); // utilizeaza clasa java.util.Vector public void push(Object item) { v.addElement(item); } public Object pop() { Object obj = peek(); v.removeElementAt(v.size()
- 1); return obj; } public Object peek() throws EmptyStackException { if (v.size() == 0) throw
new EmptyStackException(); return v.elementAt(v.size()
- 1); } public boolean empty() { return v.size() == 0; } } |
Iata
si codul unei clase stiva (Stack) care implementeaza interfata de mai sus si extinde clasa lista inlantuita (Vector).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public
class Stack extends Vector implements StackInterface { public Object push(Object item) { addElement(item);
return item; } public Object pop() { Object obj; int len = size(); obj = peek(); removeElementAt( len - 1); return obj; } public Object peek() { int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt( len -
1); } public boolean empty() { return size() == 0;} } |
Toate metodele publice ale clasei Vector, printre care si metode de inserare, pot fi invocate pentru obiectele clasei stiva in aceasta implementare. In acest fel, pe langa comportamentul tipic stivei, utilizatorii pot declansa comportamente atipice (invocand metode de inserare, etc.), care incalca principiul de functionare.
De exemplu, codul:
|
Vector
v = new Stack(); //
cod legal referinta la clasa
de baza // poate fi initializata cu obiect
din subclasa v.insertElementAt(x, 2);
// cod legal dar inserarea unui obiect in stiva // incalca principiul de functionare al stivei |
O
alternativa la mostenire este
delegarea unor sarcini catre
obiecte ale altor clase. Intre clasa care deleaga sarcinile si
clasa care le ofera exista relatia
de compunere.
Delegarea unor sarcini catre obiecte ale altor clase poate reduce cuplajul īn model:
- clientul (utilizatorul) nu cunoaşte direct furnizorul,
- furnizorul poate fi modificat pe parcurs (dinamic).
Exemplul
urmator ilustreaza mai intai mostenirea, ascunderea, rescrierea si adaugarea de membri (atribute
si metode) in subclase,
ca si o parte a elementelor speciale
utilizate in declararea claselor, atributelor si metodelor.
Apoi
sunt ilustrate doua moduri de
reutilizare a codului:
- prin
mostenire (extindere),
- prin
delegare (comunere).
Diagramei UML:
ii corespunde codul Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public
abstract class Multime { // clasa declarata abstract protected Object[] elemente; protected byte numarElem; public Multime(Object[]
elemente) { // parametru generic tip Object[] this.elemente = elemente; // acces la obiectul
curent cu this numarElem = (byte) elemente.length; // conversie de
tip de la int la byte } public abstract Multime
intersectieCu(Multime
m); // metoda declarata abstract //
valoare returnata generica
tip Multime public Object[] obtinereElemente()
{ // valoare returnata generica tip Object[] return elemente; } public byte numarElemente() { // implementare de
baza return numarElem; } } |
Diagramei UML:
ii corespunde codul Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public
class MultimeIntregi extends
Multime { public MultimeIntregi(Integer[]
elemente) { // parametru
concret tip Integer[] super(elemente); //
apelul constructorului clasei de baza Multime cu super } public final Multime intersectieCu(Multime
m) { // implementarea metodei // declarata abstract in clasa de
baza Multime mNoua;
Integer[] elementeIntersectie; int nrElemente = 0; for (int i=0; i<
elemente.length; i++) { for (int j=0; j<
m.elemente.length; j++) { if (elemente[i].equals(m.elemente[j]))
{ nrElemente++; } } } int index = 0; elementeIntersectie = new Integer[nrElemente]; for (int i=0; i<
elemente.length; i++) { for (int j=0; j<
m.elemente.length; j++) { if (elemente[i].equals(m.elemente[j]))
{
elementeIntersectie[index++] = new Integer(elemente[i].toString()); } } } mNoua = new MultimeIntregi(elementeIntersectie);
return mNoua; } public byte numarElemente() { // reimplementare (rescriere
cod) return (byte) elemente.length; //
conversie de tip de la int la byte } public boolean contine(int intr) { // metoda noua for (int i=0; i<
elemente.length; i++) { Integer inte = (Integer)
elemente[i]; if
(inte.intValue() == intr)
{ return true; } } return false; } } |
Diagramei UML:
ii corespund codurile Java:
- ale unei clase care extinde
prin mostenire (extindere):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public
class MultimeIntregiExtinsaPrinMostenire
extends MultimeIntregi { public MultimeIntregiExtinsaPrinMostenire(Integer[] elemente) { super(elemente);
} public int sumaElemente() { // metoda noua int suma = 0; Integer[] ti = (Integer[])
elemente; // utilizare atribut
mostenit elemente for (int i=0; i< ti.length;
i++) { suma = suma + ti[i].intValue(); } return suma; } } |
- ale unei clase care extinde
prin delegare (compunere):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public
class MultimeIntregiExtinsaPrinCompunere
{ public MultimeIntregi intregi; // obiect componenta public MultimeIntregiExtinsaPrinCompunere(Integer[] elemente) { intregi = new
MultimeIntregi(elemente);
} public int sumaElemente() { // metoda noua int suma = 0; Integer[] ti = (Integer[])
intregi.obtinereElemente(); for (int i=0; i< ti.length;
i++) { suma = suma + ti[i].intValue(); } return suma; } } |
- ale unei clase care permite
testarea comportamentului (si compararea modului de utilizare) in cele
doua cazuri:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public
class TestMultimeIntregi { public MultimeIntregi intregi; public static MultimeIntregiExtinsaPrinMostenire intregiDerivati; public static MultimeIntregiExtinsaPrinCompunere intregiCompusi; public static void main(String[] args) { int i; Integer[] tablouA = { new Integer(1), new Integer(3), new
Integer(5) }; MultimeIntregi multimeA = new MultimeIntregi(tablouA); intregiCompusi = new
MultimeIntregiExtinsaPrinCompunere(tablouA); int suma = intregiCompusi.sumaElemente(); System.out.println("Suma
elementelor " + suma); intregiDerivati = new MultimeIntregiExtinsaPrinMostenire(tablouA); suma = intregiDerivati.sumaElemente(); System.out.println("Suma elementelor
" + suma); i = 2; if (multimeA.contine(i))
System.out.println("Multimea
contine " + i); else System.out.println("Multimea
nu contine " + i); Integer[] tabloulB = { new Integer(2), new Integer(3), new
Integer(4) }; MultimeIntregi multimeaB = new MultimeIntregi(tabloulB); MultimeIntregi intersectiaAcuB = (MultimeIntregi) multimeA.intersectieCu(multimeaB);
// necesara conversie Integer[] tabloulAB = (Integer[]) intersectiaAcuB.obtinereElemente(); // necesara conversie Double[] tabloulD = { new Double(1.1), new Double(3.3),
new Double(5.5) }; MultimeDouble multimeaD
= new MultimeDouble(tabloulD); } } |
Subclasele (clasele
care extind prin mostenire) pot sa:
- mareasca gradul de detaliere al obiectelor:
- adaugand noi atribute, inexistente in clasa de baza, ceea
ce inseamna introducerea unui
grad mai inalt de detaliere
a starilor obiectelor
din subclasa fata de cele din clasa de baza,
- adaugand noi metode, inexistente in clasa de baza, ceea ce
inseamna introducerea unui
grad mai inalt de detaliere
a comportamentului obiectelor din subclasa fata de cele din
clasa de baza,
- mareasca gradul de concretete a
obiectelor:
- implementand eventualele metode abstracte din clasa de
baza, ceea ce inseamna un
grad mai mare de
concretete a comportamentului obiectelor din subclasa fata de cele
din
clasa de baza,
- introduca diferentieri ale obiectelor:
- redeclarand unele dintre atributele existente in clasa de
baza (schimbandu-le tipul),
ceea ce inseamna ascunderea (hiding) atributelor cu acelasi nume din clasa de baza,
adica introducerea unei diferentieri a starii obiectelor din subclasa fata de cele din
clasa de baza,
- reimplementand unele dintre metodele existente in clasa de
baza, ceea ce inseamna
rescrierea (overriding) metodelor cu acelasi nume din clasa de baza, adica introducerea unei
diferentieri a comportamentului obiectelor din subclasa fata de cele din clasa de baza.
Subclasele mostenesc toate:
- atributele din clasa de baza care nu sunt ascunse prin redeclarare,
- metodele din clasa de baza care nu sunt rescrise prin reimplementare.
Altfel spus, obiectele din subclasa poseda toate atributele declarate in
clasa de baza, si pot
utiliza toate metodele declarate in clasa de baza. Aceasta reutilizare a codului
clasei de baza de catre subclase este principalul beneficiu al
utilizarii extinderii prin mostenire.
Subclasele nu mostenesc constructorii clasei de baza (au constructori proprii), dar pot face apel la constructorii
clasei de baza (daca se intampla acest lucru, apelul la
constructorul clasei de baza, realizat prin apelul super(), trebuie sa fie prima declaratie
din corpul constructorului subclasei).
Subclasele nu mostenesc nici:
- atributele cu caracter global (static),
- metodele cu caracter global (static).
Altfel spus, membrii globali (statici) ai clasei de baza nu pot fi mosteniti de obiectele din
subclasa (ei tin strict de clasa
in care au fost declarati).
In
continuare vor fi exemplificate
in UML si Java cateva dintre aspectele discutate anterior. Diagrama de
clase de mai jos, corespunzatoare
codului Java care urmeaza, este un exemplu de model UML al relatiilor dintre clase si al
ierarhiilor de clase obtinute prin mostenire.
Clasa de baza a ierarhiei persoanelor (care modeleaza identitatea unei persoane):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Persoana { protected String nume; protected String prenume; public Persoana(String nume, String prenume) { this.nume = nume; // this = referinta la
obiectul curent
this.prenume
= prenume; System.out.println("A fost
creata persoana " + this.toString()); } public String getNume() { return this.nume; } public String getPrenume() { return this.prenume; } public String toString() { return (this.nume +
" " + this.prenume); } } |
Atributele nume si prenume sunt protected, adica sunt accesibile doar in clasa Persoana, in subclasele acesteia si in clasele care se afla in acelasi director (pachet) cu clasa Persoana.
Cuvantul cheie this este referinta la obiectul curent.
Metodele publice getNume() si getPrenume() ofera tuturor claselor externe posibilitatea de a obtine valoarea atributelor. Clasele aflate in alte directoare nu pot insa modifica aceste atribute.
Metoda publica toString() ofera claselor externe posibilitatea de a obtine continutul unui obiect de tip Persoana, sub forma unui sir de caractere reprezentand concatenarea atributelor obiectului.
O clasa care extinde clasa Persoana, modeland informatiile privind un angajat al unei organizatii (modelata de clasa Organizatie), este clasa Angajat.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Angajat extends Persoana { protected static int numarAngajati
= 0; protected int identificator; protected Organizatie organizatie; public Angajat(String nume, String prenume, Organizatie
organizatie) { super(nume, prenume); // apelul constructorului superclasei this.numarAngajati++; // echivalent cu: Angajat.numarAngajati++; this.identificator = numarAngajati; System.out.println("A fost
creat angajatul cu ID " + identificator); } public int getID() { return this.identificator; } } |
Codul clasei Organizatie este urmatorul.
1 2 3 4 5 6 7 8 9 10 11 |
public class Organizatie { public Departament departamente[]; public Organizatie(int numarDepartamente) { // Creare structura departamente this.departamente
= new Departament[numarDepartamente]; System.out.println("A
fost creata structura organizatiei"); } } |
Angajat adauga un atribut cu caracter protected si global (static), numarAngajati, si alte doua atribute cu
caracter protected.
De fiecare data cand se creaza un nou obiect de tip Angajat, constructorul incrementeaza atributul global numarAngajati, care este folosit pentru a contoriza numarul de
obiecte de tip Angajat
create.
Desigur, constructorul
initializeaza atributele non-statice
(acelea care sunt variabile distincte pentru fiecare obiect). Printre
ele, atributul identificator foloseste informatia actualizata a atributului global numarAngajati pentru a aloca un cod unic fiecarui obiect de tip Angajat.
Apelul super() reprezinta apelul unui
constructor al clasei de baza (supraclasei), Persoana.
Atat atributele nume si prenume cat si metodele toString(), getNume() si getPrenume() sunt reutilizate
in codul clasei Angajat.
Altfel spus, un obiect al clasei Angajat are si acele atribute si metode, pe langa cele suplimentar
declarate chiar in clasa Angajat.
Atributele nume si prenume si metodele toString(), getNume() si getPrenume() reprezinta codul comun tuturor obiectelor de tip Persoana, inclusiv al obiectelor de tip Angajat. Clasa Angajat este o specializare a clasei Persoana, obiectele de tip Angajat avand in plus (fata de cele de
tip simplu Persoana) trei
atribute si o metoda.
O clasa care extinde clasa Angajat,
modeland informatiile privind un manager de department (modelat in
clasa Departament)
din cadrul unei organizatii, este clasa Manager.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Manager extends Angajat { public Subordonat subordonati[]; public Departament departament; public Manager(Persoana persoana, Organizatie
organizatie) { super(persoana.nume,
persoana.prenume,
organizatie); // se foloseste noul cod al metodei
toString() System.out.println("A fost
creat un manager, " + this.toString()); } public void organizeazaLucrare(int numarZile) { this.subordonati
= departament.sub; int orePePersoana = numarZile * 8
/ subordonati.length; for (int no=0; no < subordonati.length ; no++) { subordonati[no].executaSarcina(orePePersoana); } } // rescrierea codului
(reimplementarea) metodei toString() public String toString() { // cu super.toString() se
apeleaza codul metodei din clasa Persoana // astfel incat noul cod este o specializare
a codului initial return (super.toString() + ", Manager de
Departament"); } } |
Atributele care
privesc relatiile cu organizatia (departament) si subordonatii (subordonati), si metodele care modeleaza activitatile unui manager (organizeazaLucrare()) specializeaza obiectele de tip Manager.
Codul metodei toString() este rescris, dar el apeleaza codul original, ceea ce constituie atat o specializare cat si o reutilizare.
O clasa care extinde clasa Angajat, modeland informatiile privind unui simplu membru al unui department din cadrul unei organizatii, este clasa Subordonat.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Subordonat extends Angajat
{ public Departament dep; public Subordonat(Persoana persoana, Organizatie
organizatie,
Departament departament) { super(persoana.nume,
persoana.prenume,
organizatie); this.dep = departament; System.out.println("A fost
creat un subordonat, " + this.toString()); } public void executaSarcina(int numarOre) {
System.out.println("Angajatul " + nume + " " + prenume + " (ID = "
+ identificator + ") a executat sarcina in " +
numarOre + " ore");
} } |
Atributele care privesc relatiile cu organizatia (dep) si metodele care modeleaza
activitatile unui simplu membru (executaSarcina()) specializeaza obiectele de tip Subordonat.
Codul
clasei Departament este urmatorul.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class Departament { private String denumire; private int numarSubordonati = 0; public Organizatie organizatie; public Subordonat sub[]; public Manager manager; public Departament(String denumire, Organizatie
organizatie, Manager
manager, int numarSubordonati) { // Stabilirea denumirii this.denumire
= denumire; // Crearea legaturii cu
organizatia this.organizatie
= organizatie; // Crearea legaturilor cu
managerul // (navigabilitatea asocierii este
bidirectionala) this.manager
= manager; manager.departament = this; // Crearea structurii
subordonatilor this.sub
= new Subordonat[numarSubordonati]; System.out.println("A fost
creata structura dept. " + denumire); } public void angajare(Persoana persoana) { sub[numarSubordonati++]
= new Subordonat(persoana, organizatie, this); } public boolean posturiOcupate() { return (numarSubordonati == sub.length); } } |
Codul clasei CreareOrganizatie, care ilustreaza modul de initializare al obiectelor din clasele anterioare, este urmatorul.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public class CreareOrganizatie { public static Organizatie org; public static Departament dept[]; public static void main(java.lang.String[]
args) { Persoana persoana; Manager manager; // Creare organizatie org = new Organizatie(4); //
4 departamente // Creare departamente dept = org.departamente; persoana = new Persoana("G.",
"N.T."); // Persoana e angajata si devine
subordonat in departament Personal manager = new Manager(persoana,
org); // Creare departament Personal dept[0] = new Departament("Personal",
org, manager, 3); // Creare persoana persoana = new Persoana("F.",
"L."); // Persoana e angajata si devine
subordonat in departament Personal if (!dept[0].posturiOcupate()) dept[0].angajare(persoana); persoana = new Persoana("T.",
"N."); // Persoana e angajata si devine
subordonat in departament Personal if (!dept[0].posturiOcupate()) dept[0].angajare(persoana); persoana = new Persoana("H.",
"L."); // Persoana e angajata si devine
subordonat in departament Personal if (!dept[0].posturiOcupate()) dept[0].angajare(persoana); // O noua persoana nu poate fi angajata in departament Personal if (!dept[0].posturiOcupate()) dept[0].angajare(persoana); // Creare departament Tehnic persoana = new Persoana("W.",
"D."); manager = new Manager(persoana,
org); // Creare departament Vanzari persoana = new Persoana("E.",
"V."); manager = new Manager(persoana,
org); dept[2] = new Departament("Vanzari",
org, manager, 7); // Creare departament Financiar persoana = new Persoana("R.",
"M."); manager = new Manager(persoana,
org); dept[3] = new Departament("Financiar",
org, manager, 4); System.out.println("A
fost creata organizatia");
} } |
Efectul executiei programului CreareOrganizatie:
Structura
codului unei clase Java este urmatoarea:
|
declaratie
clasa { implementare
(corp) clasa } |
sau, detaliind elementele corpului
clasei:
|
declaratie
clasa { declaratii
atribute (variabile membru) declaratii
constructori (functii de initializare a obiectelor) declaratii
metode (functii membru) } |
Exemplu de clasa
Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import java.util.Vector; import
java.util.EmptyStackException; public class Stack
// declaratia clasei { // inceputul corpului clasei private Vector elemente; // atribut
(variabila membru) public Stack()
{ // constructor elemente = new
Vector(10); // (functie de initializare) } public Object push(Object element)
{ // metoda elemente.addElement(element); // (functie membru) return element; } public synchronized Object pop(){ // metoda int lungime = elemente.size(); // (functie membru) Object element = null; if (lungime == 0) throw new
EmptyStackException(); element = elemente.elementAt(lungime
- 1); elemente.removeElementAt(lungime
- 1); return element; } public boolean isEmpty(){ // metoda if (elemente.size() ==
0) // (functie membru) return true; else return false; } } // sfarsitul corpului clasei |
Formatul general al declaratiei unei clase Java este:
|
[public]
[abstract] [final] class NumeClasa [extends NumeSuperclasa] [implements
NumeInterfata [, NumeInterfata]] { // Corp clasa } |
Elementele opţionale se afla intre paranteze drepte, identificatorii sunt scrisi cu
text inclinat (italic), iar cuvintele cheie cu text drept.
Prin convenţie, numele de clase Java īncep cu literă mare.
Declaratia minimala
a unei clase Java (fara elemente optionale) este:
|
class NumeClasa { // Corp clasa } |
Dacă elementele opţionale nu sunt declarate compilatorul Java presupune implicit despre clasa curent declarata ca:
·
doar clasele din acelasi director (pachet) cu clasa curenta au
acces la membrii clasei curente (prietenie
de pachet),
·
este instantiabila (se
pot crea obiecte avand ca tip clasa curenta),
·
poate avea subclase (create extinzand clasa
curenta),
·
extinde clasa Object (radacina ierarhiei de clase Java)
si nu implementeaza nici o
interfata.
In tabelul urmator sunt descrise elementele declaratiei de clasa Java.
Element al
declaratiei clasei |
Semnificatie |
public |
Orice cod exterior are acces la membrii clasei |
abstract |
Clasa
nu poate fi instantiata (din ea nu pot fi create direct obiecte, ci
doar din subclasele ei non-abstracte) |
final |
Clasa
nu poate avea subclase |
class NumeClasa |
Numele clasei este NumeClasa |
extends NumeSuperClasa |
Clasa
extinde o superclasa NumeSuperClasa (este o subclasa a clasei NumeSuperClasa) |
implements NumeInterfata |
Clasa implementează o
interfata NumeInterfata |
{ //
Corp clasa } |
|
Se observa ca o clasa nu poate fi declarata in acelasi timp si abstract si final, deoarece o clasa declarata abstract trebuie extinsa pentru a avea subclase concrete (tipuri din care pot fi create obiecte) pe cand unei clase declarate final ii este interzis sa fie extinsa.
Corpul clasei īn Java conţine:
·
constructori, functii pentru iniţializarea obiectelor,
·
declaraţii ale
variabilelor care
compun starea clasei şi obiectelor,
·
metode care implementează
comportamentul clasei şi obiectelor.
Variabilele
şi metodele Java sunt denumite īmpreună membri Java. Constructorii nu
sunt membri.
Formatul general al
declaratiei unui atribut (variabile membru) Java este:
|
[nivelAcces] [static] [final] [transient]
[volatile] tipAtribut numeAtribut; |
unde nivelAcces poate fi public, protected sau private. Prin convenţie, numele de variabile Java (inclusiv atributele) īncep cu
literă mică.
Declaratia minimala
a unui atribut Java (fara elemente optionale) este:
|
tipAtribut numeAtribut; |
Dacă elementele opţionale nu sunt declarate compilatorul Java presupune implicit ca:
·
doar clasele din acelasi director
cu clasa curenta au acces la atributul curent,
·
atributul are caracter de obiect (fiecare obiect din clasa curenta
are un astfel de atribut nepartajat cu alte obiecte, creat dinamic in
momentul crearii obiectului),
·
valoarea atributul poate fi
modificata oricand (este o variabila).
In tabelul urmator sunt descrise elementele declaratiei unui atribut Java.
Element al
declaratiei atributului |
Semnificatie |
public |
Orice cod exterior clasei are acces la atribut |
protected |
Doar codul exterior din sublcase sau aflat in acelasi director are acces la atribut |
private |
Nici un
cod exterior nu are acces la atribut |
static |
Are caracter global, de clasa
(este o variabila creata static, odata cu clasa, a carei locatie unica este partajata de toate obiectele
clasei) |
final |
Valoarea atributului nu poate fi
modificata dupa initializare (este o |
transient |
Semnificatia nu este complet specificata (dar tine de serializarea obiectelor) |
volatile |
Previne
compilatorul de la efectua anumite optimizari asupra atributului |
tipAtribut
numeAtribut |
Tipul este tipAtribut iar numele este numeAtribut |
[ =
valoareInitiala]; |
Eventuala initializare |
In aceeasi clasa nu pot fi declarate mai multe atribute cu acelaşi nume.
Un
atribut dintr-o subclasă
ascunde un atribut cu
acelaşi nume (si de acelasi tip) din superclasă.
Īn plus, o variabilă membru şi o metodă membru pot avea acelaşi nume. De exemplu, următorul cod Java este legal:
|
public class Stack { private Vector elemente; // atribut
(variabila membru) public Vector elemente() { // metoda (functie
membru) cu acelasi nume return elemente; } // ... } |
Scopul
variabilelor Java (vizibilitatea lor in interiorul clasei):
· reprezintă portiunea de cod al clasei īn care
variabila este accessibilă si
· determină momentul īn care variabila este creată şi distrusă.
Exista 4 categorii
de scop al variabilelor Java:
· variabilă membru (member variable) sau atribut,
· variabilă locală (local variable),
· parametru al unei metode (method parameter),
· parametru al unei proceduri de control al exceptiilor (exception-handler parameter),
Variabila membru
(atributul):
·
este membrul unei clase sau al unui obiect,
·
poate fi declarată oriunde īn clasă, dar nu
īntr-o metodă,
·
e disponibilă īn tot codul
clasei.
Variabila locală:
· poate fi declarată oriunde īntr-o metodă sau īntr-un bloc de cod al unei metode,
· e disponibilă īn codul metodei, din locul de declarare şi pānă la sfārşitul codului metodei, sau pānă la sfārşitul blocului de cod al metodei.
Parametrul unei
metode:
· este argumentul formal al metodei,
·
este utilizat pentru a se pasa valori metodei,
·
e disponibil īn īntreg codul metodei.
Parametrul unui handler
de excepţie:
·
este argumentul formal al handler-ului
de excepţie,
·
este utilizat pentru a se pasa valori handler-ului de excepţie,
·
e disponibil īn īntreg codul handler-ului de excepţie.
Urmatorul cod Java ilustreaza
diferentele intre atribute, variabile locale si parametri ai unor metode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class Complex // declaratia clasei { // inceputul corpului clasei private double real; // real = atribut private double imag; // imag = atribut public void setReal(double real) { //
metoda, real = parametru
this.real = real; // real = atributul,
real = parametrul } public void.setImag(double imag) { //
metoda, imag = parametru this.imag = imag; //
imag = atributul, imag
= parametrul } public static void main(String[] args)
{ //
metoda, args = parametru double real = Double.parseDouble(args[0]); //
real = variabila
locala double imag = Double.parseDouble(args[1]); //
imag = variabila
locala Complex c = new Complex(); // c = variabila locala c.setReal(real); // c, real
= variabilele locale c.setImag(imag); // c, imag
= variabilele locale System.out.println("{"
+ c.real + // c.real = atributul lui c ", "
+ c.imag + "}"); // c.imag = atributul lui c } } // sfarsitul corpului clasei |
Constructorul Java este un tip special de funcţie Java:
·
este utilizata pentru a iniţializa un nou obiect de acel tip (la Java īn momentul creării dinamice a obiectului);
· are acelaşi nume cu numele clasei a cărei membră este;
· nu returnează nici o valoare;
·
are aceleaşi grade de
accesibilitate, reguli de
implementare a corpului şi reguli de supraīncărcare a numelui cu funcţiile membre/metodele obişnuite.
Formatul general al declaratiei unui constructor Java este:
|
[nivelAcces] NumeClasa( listaParametri ) { // Corp constructor } |
unde nivelAcces poate fi public, protected sau private.
Declaratia minimala
a unui constructor Java (fara elemente optionale) este:
|
NumeClasa() { // Corp constructor } |
Dacă elementele opţionale
nu sunt declarate compilatorul Java presupune implicit despre constructorul curent declarat ca doar
clasele din acelasi director cu clasa curenta au acces la el.
In tabelul urmator sunt descrise elementele declaratiei unui constructor Java.
Element al
declaratiei constructorului |
Semnificatie |
public |
Orice
cod exterior clasei are acces la constructor |
protected |
Doar codul
exterior din sublcase sau aflat in acelasi director are acces la constructor |
private |
Nici un cod exterior nu are acces la constructor |
NumeClasa |
Numele
constructorului este NumeClasa |
( listaParametri ) |
Lista de parametri primiti de constructor, despartiti prin
virgule, cu formatul tipParametru numeParametru |
Īn
Java nu este neapărat necesară scrierea unor constructori
pentru clase. Un constructor
implicit este generat
automat de sistemul de execuţie pentru orice clasă care nu
conţine constructori. Acest constructor nu face nimic (nici o
iniţializare). De aceea, orice initializare dorită impune
scrierea unor constructori.
Java
suportă supraīncărcarea numelor pentru
constructori astfel īncāt o clasă poate avea orice număr de
constructori, toţi avānd acelaşi nume, dar liste de parametri diferite.
|
public Stack() { elemente = new Vector(10); } |
|
public Stack(int lungimeInitiala) { elemente = new Vector(lungimeInitiala); } |
Tipic,
un constructor utilizează
argumentele sale pentru a initializa starea noului obiect. Īn
momentul creării unui obiect, compilatorul alege constructorul ale
cărui argumente se potrivesc modului de iniţializare utilizat
de programator pentru a initializa noul obiect.
Compilatorul
īi diferenţiază pe constructori pe baza numărului de
parametri din listă şi a tipului lor. Compilatorul ştie
că atunci cānd găseşte codul următor, el trebuie
să utilizeze constructorul care cere un singur argument īntreg
(utilizat pentru stabilirea lungimii maxime a stivei):
new Stack(10);
De asemenea, scriind codul următor, compilatorul alege constructorul implicit (fără argumente):
new Stack();
Formatul general al declaratiei unei metode (functii membru) Java este:
|
[nivelAcces] [static] [abstract]
[final] [native] [synchronized] tipReturnat numeMetoda ( [listaDeParametri] ) [throws NumeExceptie
[,NumeExceptie] ] { // Corp metoda } |
unde nivelAcces poate fi public, protected sau private. Prin convenţie, numele de metode Java īncep cu
literă mică.
Declaratia minimala
a unei metode Java (fara elemente optionale) este:
|
tipReturnat numeMetoda()
{ // Corp metoda } |
Dacă elementele opţionale nu sunt declarate compilatorul Java presupune implicit ca:
·
doar codurile claselor din acelasi
director cu clasa curenta au acces la metoda curenta,
·
metoda are caracter de
obiect (este
creata dinamic in momentul crearii obiectului),
·
metoda este implementata (are corp),
·
metoda poate fi rescrisa
(reimplementata) in subclase (create extinzand clasa curenta),
·
metoda este implementata in
Java
·
metoda nu are protectie la accesul
concurent la informatii partajate
·
metoda nu are parametri,
·
metoda nu arunca
(declanseaza) exceptii.
In tabelul urmator sunt descrise elementele declaratiei unei metode Java.
Element al
declaratiei metodei |
Semnificatie |
public |
Orice cod exterior clasei are acces la metoda |
protected |
Doar codul exterior din sublcase sau aflat in acelasi director are acces la metoda |
private |
Nici un
cod exterior nu are acces la metoda |
static |
Are caracter global, de clasa (este creata static, odata cu clasa) |
abstract |
Nu are implementare (trebuie implementata in subclase) si impune declararea abstract a clasei din care face parte (prin urmare clasa din care face parte nu poate avea instante) |
final |
Nu
poate fi rescrisa implementarea metodei |
native |
Metoda implementata in alt limbaj |
synchronized |
Are
protectie la accesul concurent la informatii partajate |
tipReturnat
numeMetoda |
Tipul returnat este tipReturnat iar
numele numeMetoda |
( listaParametri ) |
Lista
de parametri primiti de metoda, despartiti prin virgule, cu formatul tipParametru
numeParametru |
throws NumeExceptie; |
Metoda arunca exceptia NumeExceptie |
Metodele Java pot returna:
· tip primitiv (int, char, boolean, real, etc.);
·
tip referinţă (obiect, tablou,
interfaţă).
Cānd metoda Java returnează un obiect, clasa obiectului returnat trebuie să fie:
·
fie o subclasă,
·
fie chiar
clasa tipului returnat.
Java suportă supraīncărcarea numelor (name overloading) funcţiilor membru/metodelor astfel īncāt mai multe metode po partaja acelaşi nume, prin utilizarea unor liste de parametri diferite (prin numărul / ordinea / tipul parametrilor).
O clasă Java poate utiliza rescrierea (overriding) unei funcţii membru/metode a superclasei sale.
Funcţia membru/metoda rescrisă trebuie să aibă exact acelaşi nume, tip
returnat şi listă de parametri ca metoda pe care o rescrie.
Forma generală a declaraţiilor din lista de parametri formali (care poate fi nulă, dar şi formată din mai mulţi parametri - declaraţiile lor apărānd despărţite prin virgulă):
type
name
Tipurile
argumentelor Java pot fi:
· tip primitiv (int, char, boolean, real, etc.);
·
tip referinţă (la
obiect, tablou, interfaţă),
dar nu pot fi metode.
Numele
argumentelor Java sunt utilizate īn corpul
metodei pentru referirea la informaţia pasată.
Un argument al unei metode poate
avea acelaşi nume cu una dintre variabilele membru ale clasei. Īn acest caz, se spune că argumentul ascunde (hides)
variabila membru.
Exemplu Java:
class Circle {
int x, y, radius;
public Circle(int
x, int y, int radius) {
// . . .
}
}
x, y sau radius īn corpul constructorului referă argumentul cu
numele respectiv, nu referă
variabila membru.
Pentru accesul la
variabila membru, ea trebuie referită prin intermediul this.
class Circle {
int x, y, radius;
public Circle(int x, int
y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
}
Pasarea informaţiilor īn Java este totdeauna prin valoare, ceea ce īnseamnă că argumentele de tip primitiv sau referinţă nu pot fi modificate, dar valorile "interne" ale tipurilor referinţă (elementele tablourilor şi membrii obiectelor) pot fi modificate.
Astfel, pot fi invocate metodele şi modifica variabilele accesibile ale obiectelor ale căror referinţe sunt pasate.
Pe lāngă elementele
obişnuite de limbaj Java, pot fi utilizate īn corpul metodei cuvinte cheie:
·
pointerul this - pentru a referi obiectul
curent;
·
super - referirea/apelul
membrilor superclasei.
Numele argumentelor au precedenţă faţă de cele ale
variabilelor membru cu acelaşi nume şi le ascund pe acestea
din urmă. Pointerul this este utilizat īn acest caz pentru a deosebi membrii obiectelor
curente de argumente.
Pointerul this mai poate fi utilizat, redundant, pentru a preciza mai clar clasa
obiectului.
Se poate utiliza super, dacă metoda curentă ascunde (hides) o variabilă membru a superclasei, pentru a referi variabila
ascunsă.
De
asemenea, se poate utiliza super,
dacă metoda curentă rescrie
(overrides) o metodă membru a
superclasei, pentru a invoca
metoda rescrisă.
Clasele permit protecţia variabilelor
şi metodelor membru de accesul altor obiecte.
Regulile de vizibilitate completează / precizează noţiunea
de īncapsulare. Astfel, este
posibilă obţinerea unui grad
de īncapsulare mai suplu, dar şi protejarea, īn beneficiul
anumitor clase utilizator particulare, desemnate īn specificaţia
clasei furnizor.
Un scop al
īncălcării īncapsulării poate fi reducerea timpului de acces şi a atributelor,
obţinută prin eliminarea necesităţii de a recurge
la operaţii de selecţie.
Efectul aplicării specificatorilor/modificatorilor Java:
acces specificator |
cod clasă |
pachet Java |
cod
subclasă |
oricine |
Observaţii |
public |
da |
da |
da |
da |
"nici un secret" |
protected |
da |
da |
da |
|
"secrete de familie
(şi grup)" |
(package īn Java) |
da |
da |
|
|
"secrete de grup" |
private |
da |
|
|
|
"secrete absolute"
(dar obiectele din aceeaşi clasă au acces) |
Variabilele de instanţă (declarate fără static) īn Java:
·
sunt alocate şi
proprii fiecărui obiect.
Variabilele de clasă (declarate cu static) īn Java:
·
alocate la nivel de clasă, sunt locaţii unice, partajate de toate obiectele clasei şi
de clasă,
·
pot fi referite atāt cu numele instanţelor (obiectelor) cāt şi cu numele
clasei.
Metodele de instanţă (declarate fără static) īn Java:
·
au acces atāt la variabilele
obiectelor cāt şi la variabilele claselor.
Metodele de clasă (declarate cu static) īn Java:
·
nu pot accesa variabilele de
instanţă ale obiectelor (decāt dacă crează ele
obiectele respective),
·
pot fi invocate atāt cu numele instanţelor (obiectelor) cāt şi cu numele
clasei.