Pentru a compensa lipsa moștenirii multiple, în
limbajul Java s-a admis că o clasă poate avea mai multe interfețe
și că mai multe clase pot implementa aceeași interfață. S-a introdus
astfel o nouă categorie de componente, numite interfețe, care se
declară în mod asemănător cu niște clase abstracte, dar nu sunt
înglobate, așa cum sunt clasele abstracte, în ierarhia unică de clase
.
Interfața este o specificație care descrie metodele publice și variabilele finale publice pe care trebuie sa le aibă o clasă care implementeaza interfața respectivă. Dacă o clasă implementează mai multe interfețe, ea conține toate metodele publice și variabilele finale publice ale acestora. |
Interfața nu este o clasă, dar poate fi utilizată de programator ca și când ar fi o clasă abstractă. Se pot declara variabile referință la o interfață în mod asemănător cu declararea variabilelor referința la obiecte aparținând unei clase, adică sub forma:
interfața nume_variabila1[=initializare1], ..., nume_variabilaN[=initializareN];
în care interfața este numele unei interfețe, iar celelalte elemente ale declarației sunt aceleași ca în cazul declarării de referințe la obiecte. Interfețele pot fi și ele organizate ierarhic, aplicându-se principiul moștenirii. În schimb, pentru interfețe, ierarhia nu mai este unică, așa cum este în cazul claselor, și se admite moștenirea multiplă.
Remarcam că, la fel ca și clasele abstracte, interfețele nu pot fi instanțiate. În schimb, unei variabile referință la o interfață i se pot da ca valori referințe către obiecte din orice clasă care implementează acea interfață sau este descendentă a unei astfel de clase.
Pentru a le distinge mai ușor, în documentația Java API numele interfețelor sunt scrise cu litere cursive (stil italic), în timp ce numele claselor sunt scrise cu litere normale (drepte). De exemplu, în pachetul java.lang există interfața Cloneable care este implementată de toate clasele din SDK care implementează metoda clone(), deci ale căror obiecte pot fi clonate.
Sa considerăm acum exemplul din Figura 1, în care clasele B și C implementeaza interfețele I1 și I2, iar clasele C si D implementează interfețele I2 și I3.
- Figura 1 -
În acest exemplu, interfetele I1 și I2 sunt derivate din interfata I0 deci moștenesc variabilele finale și metodele acesteia, putând avea fiecare, de asemenea, date finale, câmpuri și metode proprii. Clasa B implementează interfața I1, deci conține variabilele finale și metodele interfeței I1 și pe cele mostenite de aceasta de la interfața I0. Clasa C implementează interfețele I1 și I2, deci conține variabilele finale și metodele interfețelor I1 și I2, inclusiv pe cele moștenite de acestea de la interfața I0. Totodată, clasele A, B și C moștenesc datele și metodele clasei A. Faptul că variabilele finale și metodele interfeței I0 se propagă la clasa C atât pe traseul I0-I1-C, cât și pe traseul I0-I2-C nu mai produce dificultăți, ca în cazul moștenirii multiple a claselor, deoarece implementarea efectivă a acestor variabile și metode se face în clasa C și nu in interfețe.
Pentru exemplul din Figura 1, să considerăm acum următoarele declarații și instrucțiuni de atribuire valabile, în care A(), B(),C() si D() sunt constructori ai claselor respective:
A v1=new A(), v2;
B v3=new B(), v4;
C v5=new C(), v6;
I0 w1=new B(),w2;
I1 w3=new C(), w4;
I2 w5, w6;
v2=v5;
w2=new D();
v4=v3;
w4=v3;
v6=new C();
w5=v6;
w2=w4;
Constatăm că variabilele referință la interfețe (w1, w2, w3 etc.)
sunt utilizate la fel ca și variabilele referinta la obiecte ale
claselor. Dacă, insă, știind că variabila w1 are ca valoare o referință
la un obiect din clasa B, vom dori sa folosim instrucțiunea
v4=w1;
vom constata că apare o eroare de compilare, deoarece v4 este referință
la un obiect al unei clase, în timp ce w1 este o referință la
interfață. În această situație este necesară o conversie explicită
(cast), astfel că vom folosi instrucțiunea
v4=(B)w1;
Deși nu se încadrează în ierarhia claselor, interfețele se pot constitui in diverse ierarhii de interfețe, aplicându-li-se principiul mostenirii. O clasă poate implementa mai multe interfețe.
Cel mai important avantaj al folosirii interfețelor este că mai multe clase, de pe diferite ramuri ale arborelui ierarhic al claselor, pot fi "văzute" printr-o singură interfață. Se pot declara variabile referință la interfață la fel cum se pot declara variabile referință la clasă. Interfața este abstractă și deci nu poate fi instanțiată. În schimb, unei variabile referință la interfață i se pot atribui ca valori referințe la obiecte din orice clasă care implementează interfața respectivă.
Declarația de interfață are forma generală următoare:
[public]
interface NumeInterfata [extends lista_superinterfete] {
declaratii_de_membri_ai_interfetei
}
În afară de modificatorul public, se poate folosi și modificatorul abstract, numai că acesta este implicit, deci folosirea lui este de prisos.
Spre deosebire de clase, la care moștenirea multiplă este interzisă (o clasă poate avea numai o singura superclasă), în cazul interfețelor moștenirea multiplă este permisă. În consecință, clauza extends poate conține o listă de nume de interfețe separate prin virgule.
Corpul interfeței conține una sau mai multe declarații de membru al interfeței.
Declarațiile de membri ai interfeței pot fi:
a/ Declarația de câmpuri statice finale, numită și declarație de constante, sub forma:
tip NUME_CAMP1=valoare1,NUME_CAMP2=valoare2,... , NUME_CAMP_N=valoareN;
în care tip este un tip
primitiv. Modificatorii public, static și final
pentru câmpurile interfeței sunt impliciți, deci folosirea lor este de
prisos. Aceasta înseamnă că toate câmpurile unei interfețe sunt
publice, statice și finale, deci ele trebuie să fie
inițializate la declarare și nu mai pot fi modificate ulterior. Se
obisnuiește ca numele de câmp ale interfețelor să fie scrise numai cu
majuscule.
Exemplu: interface CuloareDeBaza { int ROSU=1, VERDE=2, ALBASTRU=4; } interface Culoare extends CuloareDeBaza { int GALBEN=3, ORANGE=5, INDIGO=6, VIOLET=7; } |
b/ Declarația de metodă abstractă, care
constă din antetul metodei urmat de simbolul ';' (punct și virgulă). Ea
este deci la fel ca o declarație de metodă abstractă din corpul unei
clase, cu următoarele observații:
- modificatorii public și abstract sunt
impliciti, deci folosirea lor este de prisos (dar este permisă);
- modificatorul final nu poate fi folosit, deoarece se
declară o metodă abstractă;
- corpul metodei este înlocuit prin simbolul punct și virgulă,
ca la orice metodă abstractă.
Exemplu: interface Interf1 { double metoda1(); int metoda2(int a, int b, double c); } Ambele metode din exemplul de mai sus sunt implicit publice și abstracte. Orice clasă, care implementează aceasta interfață, trebuie sa conțină declarații de metode cu aceeași semnătură cu cele din interfață. La definirea acestor metode îm cadrul claselor, folosirea identificatorului public este obligatorie. |
Exemplu de declarare, implementare și utilizare a unor interfețeConsiderăm că dorim să realizăm diferite clase de integratoare numerice, care calculează prin diferite metode valoarea integralei pe intervalul [inf, sup] a funcției reale f(x). Dorim ca, în oricare din aceste integratoare, să existe metoda de calculare a integralei:double integrala(double inf, double sup, FunctieReala func) în care inf și sup sunt, respectiv,
marginea inferioara și cea superioara a intervalului de integrare, iar func
este o referință la un obiect care contine funcția de integrat f(x).
Având în vedere că nu avem, deocamdată, în vedere o metoda de integrare
numerică particulară, vom declara interfața
în care metoda de integrare apare ca o metoda abstractă. Cu aceasta ocazie remarcam că, spre deosebire de limbajele de
programare tradiționale, cum sunt Pascal sau C/C++, în limbajul Java nu
există posibilitatea ca o funcție (metodă) să aibă ca argument o
referința la altă funcție (metodă). În schimb, putem să-i oferim ca
argument o referința la un obiect care conține funcția respectivă.
Astfel, în cazul nostru, func este referință la un obiect care
conține funcția de integrat f(x). Având în vedere că dorim ca
integratoarele noastre să fie valabile pentru orice funcție reală f(x),
vom declara o interfață:
care conține metoda abstractă f(x) pe care trebuie să o conțină orice obiect pe care îl oferim ca argument metodei de integrare din interfața Integrator. Să realizăm acum un integrator concret, pentru o anumita metodă particulară de integrare. Pentru simplitate, vom implementa integrarea prin metoda trapezelor. Știind că integrala poate fi interpretată geometric ca aria suprafeței cuprinse între graficul funcției respective și axa ox, putem împărți intervalul de integrare [inf, sup] în n subintervale de lungime egala h=(sup-inf)/n, ca în Figura 1.
- Figura 1 - Înlocuind pe fiecare din aceste subintervale curba f(x) prin coarda ei, obtinem n trapeze. Considerand că ariile cuprinse între curbă și coarda curbei în fiecare din subintervale sunt mici, aproximăm aria de sub curbă prin suma ariilor celor n trapeze. Se obține astfel pentru calcularea integralei I cunoscuta formulă a integrării numerice prin metoda trapezelor dată în Figura 2.
- Figura 2 - Pentru calcularea integralei este necesar să se impună și
numărul de pași n, pe care îl vom nota în continuare cu nrPasi.
Putem astfel declara clasa IntegrareTrapeze, care implementeaza
interfața Integrator, în modul următor:
La calcularea integralei s-a aplicat formula din Figura 2, luând în considerație că xi=inf+i*h. Remarcăm că funcția f(x) nu a fost încă precizată. S-a indicat numai sub forma func.f(inf+i*h) că se folosește funcția (metoda) f(x) a obiectului func, care implementează interfața FunctieReala. Integratorul nostru are deci o utilizare foarte largă, putând integra funcția f(x) din orice obiect care implementează această interfață. Remarcăm, de asemenea, că putem aplica și alte metode de integrare numerică, dacă creem pentru fiecare din aceste metode o clasa care implementează interfața Integrator. Să considerăm acum că dorim să integrăm funcția
Putem acum să scriem o aplicație în care se calculează integrala acestei funcții. În fișierul Integrare.javasunt date atât declarațiile de interfețe și clase prezentate mai sus, cât și cea a clasei-aplicație în care se folosesc acestea. |