-
Tipurile generice de date, Reflexia si Manipularea
evenimentelor
Visul reprezint reflexia realitii n oglinda sufletului nostru.
Mihaela Baci
Obiective Manipularea si utilizarea tipurilor si claselor
generice, manipularea arhitecturii claselor n timpul executrii
programului (Reflexia) i manipularea evenimentelor.
Cuprins 1.1. Tipurile generice de date 1.2. Reflexia 1.3.
Manipularea evenimentelor
1.1. Tipurile generice de date
Manipularea tipurilor generice de date reprezint cel mai nalt
grad de abstractizare i dinamic n crearea unei arhitecturi de clase
a programului, arhitectur care s fie orientat pe obiect. Aceast
descriere are la baz un concept simplu. Vom crea o clas, dar n ea
nu vom evidenia explicit tipurile, ci doar vom manipula variabile,
indiferent de tip. n acest mod, permitem utilizatorului clasei s-i
aleag singur tipul cu care va manipula clasa. Aceasta influeneaz
optimizarea clasei i ntreaga aplicaie.
Iat un exemplu de ArrayList: ... ArrayList al = new ArrayList();
al.add("Petru"); ...
Acest ArrayList conine un element de tip String. Dar, n cadrul
clasei ArrayList acest obiect nu exist ca String, ci ca Object.
Acest lucru nseamn c o anumit perioad de timp va fi consumat pentru
transformarea String-ului introdus n obiect n timpul introducerii i
retransformrii obiectului n String, n timpul extragerii datelor din
list.
n plus, odat cu extragerea datelor din list, nu este posibil s
tratm obiectul direct ca pe un tip surs, ci trebuie s-l convertim
explicit din obiect.
Acest exemplu nu va funciona: Acest exemplu va funciona:
ArrayList al = new ArrayList(); al.add("Petru"); String dinLista
= al.get(0); ...
ArrayList al = new ArrayList(); al.add("Petru"); String dinLista
= (String)al.get(0); System.out.println(dinLista);
Observm c singura diferen ntre aceste exemple este faptul c n al
doilea exemplu exist conversiunea explicit n String. O astfel de
abordare nu este de dorit din dou motive. n primul rnd, este
necesar ca de fiecare dat s se efectueze conversiunea n tipul
dorit, iar n al doilea rnd, pot aprea erori i n tipuri.
Urmtorul exemplu va cauza o eroare n execuie, dei va fi tradus
cu regularitate, deoarece, pur i simplu nu poate converti String-ul
care nu conine numere n Integer:
ArrayList al = new ArrayList(); al.add(1); al.add("Petru");
Integer dinLista = (Integer)al.get(1);
System.out.println(dinLista);
-
1.1.1. Utilizarea tipurilor generice. n locul abordrii mai sus
menionate am putut s crem un ArrayList generic. Spre deosebire de
un ArrayList obinuit, aceast clas accept i tipul obiectului pe care
dorim s-l manipulm n ea. Datorit acestui fapt, se exclude
posibilitatea de eroare n timpul manipulrii tipului din list,
precum i conversiunea lui explicit. Iat cum ar arta exemplul cu
lista generic de valori ntregi:
ArrayList al = new ArrayList(); al.add(1); Integer dinLista =
(Integer)al.get(0); System.out.println(dinLista);
Dac ntr-o astfel de list am ncerca s introducem o valoare de alt
tip (de exemlu al.add("Petru");), s-ar ajunge la o greeal n
traducere. Acest lucru se va ntmpla deoarece am evideniat n mod
explicit c tipul pe care dorim s manipulm n list este -
Integer.
Avnd n vedere faptul c listele generice cer un tip explicit de
date, spre deosebire de manipularea listelor de obiecte, aceste
liste cer i un tratament special al datelor introduse.
n urmtorul exemplu exist dou clase: Persoana i Student. n timp
ce, clasa Student motenete clasa Persoana:
... public class Persoana { public String nume; }
... public class Student extends Persoana { public String
numarulCarnetuluiDeStudent; } ...
Doarece ambele clase au numele de proprietate, poate c dorim s
efectum o metod care va prezenta numele tuturor persoanelor din
list. ns, aceast metod ar trebui s accepte ori tipul ArryList ori
tipul ArryList (ori, bineneles, lista obiectelor, ceea ce nu este o
opiune). Dac am ncerca s trecem lista studenilor pe lista Persoane
sau invers, ar aprea o eroare n traducere. n loc de aceasta, putem
accepta simbolul Wildcard (?) care simbolizeaz cea mai veche clas
printe, sau mai exact, orice clas. Acest exemplu raporteaz o
eroare:
static void afisari(ArrayList os) { for(Persoana o : os)
System.out.println(o.nume); } public static void main(String[]
args) {
ArrayList listaStudenti = new
ArrayList(); ArrayList listaPersoane = new
ArrayList(); Persoana o = new Persoana(); o.nume="Vasile";
Student s = new Student(); s.nume="Elena";
s.numarulCarnetuluiDeStudent="25/25"; listaPersoane.add(o);
listaStudenti.add(s); afisari(listaStudenti); }
Dar cu urmtoarea modificare, exemplul va fi complet
funcional:
static void afisari(ArrayList
-
for(Persoana o : os) System.out.println(o.nume); }
Incercai s adugai linia; afisari(listaPersoane); Ce va afia?
Este posibil s facem un pas nainte i s introducem o list de orice
tip.
static void afise(ArrayList os)
{ for(Object o : os) System.out.println(((Persoana)o).nume);
}
Esena utilizrii n acest mod a simbolului Wildcard (?) este
evident dac se ia n considerare faptul c pentru acelai exemplu nu
se poate ntrebuina tipul Object, ceea ce nseamn c acesta este
unicul mod n care putem introduce dinamic o list generic n metod.
Exemplu care nu funcioneaz:
static void afisari(ArrayList os) { for(Object o : os)
System.out.println(((Persoana)o).nume); }
Motivul pentru care exemplul precedent nu funcioneaz este faptul
c lista obiectelor (dei Object este n josul ierarhiei obiectelor)
nu reprezint lista care este la sfritul ierarhiei listelor
generice.
1.1.2. Crearea claselor generice. Pn acum, am folosit clase care
au posibilitatea acceptrii generice a tipurilor. S mai lum cteva
exemple:
LinkedList ll = new LinkedList(); ll.add("membrul meu al
listei");
Hashtable ht = new Hashtable(); ht.put("Stringul meu", 25);
HashMap hm = new HashMap(); hm.put(15, 33);
System.out.println(ll.get(0));
System.out.println(ht.get("Stringul meu"));
System.out.println(hm.get(15));
In urma executiei aobinem:
-
Pentru a ne crea propria clas generic, este necesar s evidenim
acest lucru n definiia clasei:
public class MyClass { public T t;
}
La crearea unei astfel de clase, Java trateaz automat fiecare
tip definit drept generic (n acest caz este tipul T) ca Object.
Astfel, dac iniiem aceast clas n felul urmtor:
MyClass mk = new MyClass();
vom avea proprietatea t, care va fi de tip Object:
Pentru ca clasa s fie strict tipizat, la iniierea ei trebuie
adugat i tipul dorit:
MyClass mk = new MyClass();
Acum clasa va fi tipizat i nu va putea s accepte niciun alt tip
n afar de Integer.
n acelai mod, putem aduga clasei i alte tipuri generice:
public class MyClass {
public T t; public T1 t1; public T2 t2; public MyClass(T t, T1
t1, T2 t2) { this.t = t; this.t1 = t1; this.t2 = t2; } } ...
Apelarea clasei poate arta astfel: MyClass mk = new MyClass(35,
"My text", true); System.out.println(mk.t+" "+mk.t1+" "+mk.t2);
Sau astfel:
MyClass mk = new MyClass(35, 15, 22);
System.out.println(mk.t+mk.t1+mk.t2);
Dou instane din exemplele de mai sus au tipuri de arhitectur
total diferite. Dar, trebuie s inem minte c aceste instane rmn n
continuare instanele acelorai clase, iar
-
compararea lor va da rezultatul corect n ciuda diferenei de
tipuri: MyClass mk = new MyClass(35, 15, 22); MyClass mk1 = new
MyClass(35, "", true);
System.out.println(mk.getClass().equals(mk1.getClass()));
Comparaia n ultima linie a codului d rezultatul true/adevrat.
Bineneles, congruena n instane nu exist, astfel compararea lor va
da rezultatul false/fals:
System.out.println(mk.equals(mk1));
Se pune ntrebarea: Ce se ntmpl dac dorim s ne asigurm c tipul pe
care utilizatorul l va introduce va fi fr ndoial tipul adecvat? De
exemplu, dorim ca o clas s execute operaiuni aritmetice, care
evident nu se pot realiza cu tipuri non-numerice. Atunci trebuie s
ne asigurm c tipul pe care utilizatorul l va introduce va fi
limitat doar la tipuri numerice. n acest caz, putem limita tipul
generic astfel nct el s devin subtipul unui anumit tip, prin
cuvntul cheie extends:
public class MyClass { public T t; public void showResult() {
if(t.getClass().equals(Integer.class))
System.out.println(t.intValue()*t.intValue());
if(t.getClass().equals(Double.class))
System.out.println(t.doubleValue()*t.doubleValue()); } }
Pentru o astfel de definiie a clasei, urmtoarele dou instanieri
ar fi valide deoarece conin tipurile motenite din clasa Number:
MyClass mk = new MyClass(); mk.t=15;
MyClass mk1 = new MyClass(); mk1.t=15.0;
mk1.showResult(); Raspuns: 255.0 (15.0*15.0)
Urmtoarea instaniere NU ar fi valid, deoarece conine tipul
String care nu
motenete clasa Number: MyClass mk2 = new MyClass();
1.2. Reflexia
Reflexia este capacitatea limbajului de programare s manipuleze
arhitectura
claselor n timpul executrii programului. De obicei, acest lucru
pare i sun cam complicat, dar de fapt, este vorba despre un concept
foarte simplu. Aadar, cnd crem un program, manipulm clasele prin
instanele sau elementele lor statice. Dar, de fapt, nou ne este pus
la dispoziie doar acea parte a clasei care este menit
utilizatorului. Structura lor, metodele, cmpurile i altele nu ne
stau la dispoziie n sensul c nu putem privi corpul unei metode sau
tipul unui cmp.
S spunem c clasa MyClass are metoda: int add(int a,int b) {
return a+b; }
Bineneles, ntotdeauna putem activa aceast metod prin comanda
mc.add(2,3); dar problema apare dac am dori s schimbm corpul
metodei astfel nct s nu mai trimit napoi a+b, ci a*b. n acest caz,
singurul lucru pe care am putea s-l facem este s folosim
reflexia.
Pentru a identifica clasa, folosim clasa Class i metoda ei
static forName:
-
Class c = Class.forName("MyClass");
n linia precedent a fost iniializat obiectul clasei Class cu
denumirea c. Acest obiect este creat din clasa MyClass, care este o
clas identificat n sistem. Dac am introduce drept parametru o clas
care nu exist, ar aprea excepia ClassNotFoundException.
1.2.1. Manipularea cmpurilor
Odat ce am identificat clasa, cu ajutorul metodei obiectului
Class i putem accesa cmpurile. De exemplu, dac dorim s vedem toate
cmpurile clasei (atributele) sau s identificm explicit un cmp,
folosim metoda getFields.
System.out.println(c.getFields()[0].getName());
Exemplul arat denumirea primului cmp al clasei (clasa MyClass
conine un singur cmp public). Avnd n vedere c metoda getFields
trimite napoi un ir de cmpuri, ar fi simplu s se treac prin lista
tuturor cmpurilor de clas.
De asemenea, putem cuta cmpul dup nume, prin metoda getField:
c.getField("myField")
n ambele cazuri, vom obine unul sau mai multe obiecte de tip
Field. Pentru a schimba valoarea cmpului, folosim metoda setField.
Urmtorul exemplu
gsete cmpul cerut n instana clasei i i atribuie o anumit
valoare: Class c = Class.forName("MyClass"); MyClass mc = new
MyClass(); c.getField("myField").set(mc, 123);
System.out.println(mc.myField);
Logica ntrebrii de ce nu mc.myField=123; se pierde dac dorim s
atribuim mai multor cmpuri o anumit valoare, netiind unde se afl
toate cmpurile pe care le posed clasa. n acest caz, soluia ar fi
urmtorul cod i nu ar exista alt mod de a realiza acest lucru:
Class c = Class.forName("MyClass"); MyClass mc = new MyClass();
int newVal = 10; Field[] flds = c.getFields(); for(Field f : flds)
f.set(mc, newVal);
n exemplu va fi schimbat doar un cmp deoarece doar un singur cmp
exist, dar n practic, este clar c vor fi schimbate mai multe
cmpuri. De fapt, exact attea cte posed clasa. Bineneles, pentru o
astfel de abordare este necesar s se acorde atenie tipurilor de
cmpuri.
Urmtoarea modificare simplic verificarea tipului de cmp, nainte
de a i se atribui valoarea. n acest mod nu poate aprea o eroare n
tipurile:
Field[] flds = c.getFields(); for(Field f : flds) {
if(f.getType().equals(int.class)) f.set(mc, newVal); }
1.2.2. Manipularea metodelor n mod similar se pot manipula i
metodele claselor. n urmtorul exemplu sunt
prezentate toate metodele clasei selectate. Mai nti, cu ajutorul
metodei clasei Class
getMethods se extrage lista tuturor metodelor clasei selectate
(MyClass). Aceast metod trimite napoi irul de obiecte al clasei
Method. Prin acest ir trecem n ultimele dou linii ale codului artnd
denumirea tuturor metodelor, prin metoda getName:
-
Class c = Class.forName("MyClass"); Method[] methods =
c.getMethods();
for(int i=0;i
-
1.3. Manipularea evenimentelor
Vom acorda atenie conceptului de evenimente. Acesta este unul
din conceptele cheie n programarea aplicaiilor grafice, att n Java,
ct i n cazul n care este vorba de celelalte platforme, adic limbaje
de programare.
Programarea care implic urmrirea i prelucrarea evenimentelor n
timpul executrii unei aplicaii se numete programare Event Based.
Acest mod de manipulare a programului este cunoscut n toate
aplicaiile care conin comenzi de utilizator (taste, ferestre,
cmpuri pentru text...).
Conceptul evenimentelor este simplu, ca i implementarea lor, dac
au fost studiate cu atenie. S spunem c dorim s conducem o main al
crui rezervor nu este plin. Avem dou moduri de a ne asigura c nu ne
vom opri. Unul este s verificm constant starea rezervorului, iar al
doilea este s ne bazm pe indicatorul rezervei de combustibil.
Bineneles, a doua variant este mult mai galant dect prima i acesta
este modul n care funcioneaz i evenimentele obiectelor n
programare.
n momentul n care crem un obiect, n majoritatea cazurilor acesta
are o funcionalitate care efectueaz ceva. De asemenea, n acea
funcionalitate putem instala i indicatoare de evenimente, astfel
nct atunci cnd se ntmpl ceva cu el, obiectul emite un semnal.
Problema apare atunci cnd trebuie s captm acel semnal n lumea
exterioar, n afara obiectului. Nu putem face acest lucru verificnd
dac obiectul a emis sau nu un semnal dup fiecare linie a codului n
care ateptm ceva de la obiect. n loc de acest lucru, vom nregistra
un asculttor pentru un anumit eveniment pe un anumit obiect i n
cazul n care asculttorul detecteaz un anumit eveniment, vom seta
programul s activeze o anumit funcionalitate.
1.3.1. Crearea clasei Event n Java, clasa de baz care reprezint
un eveniment este EventObject. Aceast clas
se gsete n pachetul java.util i n Java nu exist niciun eveniment
care, la sfritul listei de motenire, s nu conin aceast clas.
Pentru a crea un eveniment utilizabil, mai nti se creeaz o clas
Event. Aceasta este clasa pe care obiectul o va transmite
asculttorului evenimentului. Aceast clas este unicul mod de a
transmite asculttorului informaii legate de cine a cauzat
evenimentul i cu ce parametri.
Dac am dori s crem un eveniment pentru exemplul precedent cu
rezervorul mainii, mai nti ar trebui s crem obiectul care va
reprezenta acel eveniment. Acest obiect trebuie s conin obiectul
care a cauzat evenimentul (i el se transmite constructorului
printe), iar opional, poate c conin i oricare alt parametru. De
asemenea, obiectul trebuie s moteneasc clasa EventObject:
import java.util.EventObject; public class ReservoirEvent
extends EventObject
{ String msg; public ReservoirEvent(Object source, String msg) {
super(source); this.msg = msg; } }
-
Clasa pe care am creat-o ndeplinete cerinele descrise. Conine
obiectul care a cauzat evenimentul. Acesta este obiectul source. De
asemenea, conine i parametrul suplimentar msg. Acesta este valoarea
pe care o va transmite obiectul care a cauzat evenimentul.
Cuvntul super este un omolog al lui this i reprezint referina la
partea motenit a
unui obiect.
1.3.2. Crearea interfeei pentru asculttorul evenimentului/Event
Listener A doua etap n crearea obiectului este crearea interfeei
asculttorului. n aceast
interfa plasm metodele pe care dorim s le implementeze
asculttorul. Pentru necesitile evenimentului nostru, este suficient
metoda pentru schimbarea strii din rezervor:
public interface ReservoirListener { public void
statusChanged(ReservoirEvent event);
}
1.3.3. Crearea clasei principale (generatorul evenimentului)
Ultima etap este crearea clasei care va difuza evenimentele.
Aceast clas va reprezenta parametrul Source din clasa Event. De
asemenea, aceasta este clasa n care trebuie
s implementm sistemul pentru adugarea i eliminarea unui anumit
asculttor i sistemul care va trece prin lista tuturor evenimentelor
i le va difuza secvenial.
Pentru a realiza acest lucru, trebuie creat o list a tuturor
evenimentelor unui anumit tip, n cadrul clasei:
private ArrayList allListeners = new ArrayList();
iar apoi i metodele pentru punerea asculttorului n list i
eliminarea din list:
public synchronized void addResListener( ReservoirListener rl )
{ allListeners.add( rl ); } public synchronized void
removeResListener( ReservoirListener rl ) { allListeners.remove( rl
); }
n practic, toate metodele care funcioneaz cu evenimente sunt
marcate cu modificatorul synchronized, pentru a preveni apariia
operaiunilor ncruciate n timpul executrii multi thread.
n momentul n care dorim, pe care l vom alege n timpul proiectrii
clasei, activm evenimentul. Putem face acest lucru de cte ori dorim
i n orice moment. Activarea evenimentului, de fapt, reprezint
trecerea prin lista de asculttori nregistrai i activarea metodelor
lor prevzute de interfa. n cazul nostru este nregistrat o singur
metod, statusChanged, astfel nct o activm doar pe ea. Transmitem
obiectul clasei ReservoirEvent metodei, sau i transmitem obiectul
curent (this) i anumii parametrii opionali. n cazul clasei noastre,
acesta este statusul String:
private synchronized void resEvent() { ReservoirEvent resEvent =
new ReservoirEvent(this, status); Iterator listeners =
allListeners.iterator(); while( listeners.hasNext() ) {
((ReservoirListener) listeners.next()).statusChanged( resEvent); }
}
Pe lng logica de ascultare a evenimentului, clasa posed i logica
sa primar. n
-
exemplu, acestea pot fi metodele de umplere sau golire a
rezervorului: fillRes i emptyRes. public void fillRes() {
this.status = "full"; resEvent(); }
public void emptyRes() { this.status = "empty"; resEvent();
}
Observm c ambele metode apeleaz metoda resEvent. Acest lucru se
ntmpl deoarece dup executarea fiecreia dintre ele se schimb
statusul obiectului. Dar, chiar i atunci cnd statusul nu este
schimbat, putem provoca evenimentul oricnd dorim.
Urmtorul cod conine clasa Reservoir complet:
import java.util.ArrayList; import java.util.Iterator; public
class Reservoir { private ArrayList allListeners = new ArrayList();
private String status = "full"; public void fillRes() { this.status
= "full"; resEvent(); } public void emptyRes() { this.status =
"empty"; resEvent(); } public synchronized void
addResListener(ReservoirListener rl) { allListeners.add(rl); }
public synchronized void removeResListener(ReservoirListener rl) {
allListeners.remove(rl); } private synchronized void resEvent() {
ReservoirEvent resEvent = new ReservoirEvent( this, status );
Iterator listeners = allListeners.iterator();
while(listeners.hasNext() ) { ((ReservoirListener)
listeners.next()).statusChanged(resEvent); } } }
1.3.4. Manipularea evenimentelor
Cnd exist clasa evenimentului, interfaa pentru eveniment i clasa
care activeaz evenimentul, ne rmne doar s plasm asculttorii i s
introducem n ei logica dorit. Aceast procedur se numete manipularea
evenimentelor, respectiv Event Handling.
Asculttorii plasai sunt clase care trebuie s implementeze
interfaa pe care am creat-o anterior pentru asculttorul
evenimentului (n exemplul nostru, ReservoirListener).
Putem crea aceste clase drept clase desemnate sau le putem
activa n timpul activrii evenimentului, drept instane anonime ale
clasei. Dac am crea o instan desemnat a asculttorului
evenimentului, ar arta astfel:
public class ResEventListener implements ReservoirListener {
public void statusChanged(ReservoirEvent event) {
-
System.out.println("Reservoir status : " + event.msg); } }
Prin implementarea interfeei ReservoirListener ne-am luat
obligaia s implementm metoda statusChange. Aceast metod accept
obiectul ReservoirEvent, sau obiectul care conine obiectul ce
activeaz evenimentul i argumentele evenimentului. Cnd "facem rost"
de aceste date, le putem manipula n mod standard. n exemplu, difuzm
mesajul despre statusul rezultatului.
Urmtorul cod arat instanarea clasei Reservoir, prin adugarea
asculttorului acestei instane i apelarea metodelor ei care cauzeaz
evenimentele nregistrate:
public class Main { public static void main(String[] args) {
Reservoir c = new Reservoir(); ResEventListener ri = new
ResEventListener(); c.addResListener(ri); c.fillRes();
c.emptyRes(); } }
Drept rezultat, la ieire vor fi difuzate urmtoarele mesaje:
Reservoir status : full Reservoir status : empty
n loc de instana desemnat (rl), am fi putut asocia asculttorul
la list cu ajutorul instanei anonime (Anonymous):
c.addResListener(new ReservoirListener());
sau chiar crea logica complet in line:
c.addResListener(new ReservoirListener() { public void
statusChanged(ReservoirEvent event) { System.out.println("From
inline class"); } });
Ambele moduri sunt valide, dar devin problematice atunci cnd
dorim s eliminm asculttorul de pe list (asculttorul trebuie atunci
gsit dup index).
Urmeaz codul complet al exemplului: ReservoirEvent.java
import java.util.EventObject; public class ReservoirEvent
extends EventObject { String msg; public ReservoirEvent(Object
source, String msg) { super(source); this.msg = msg; } }
ReservoirListener.java public interface ReservoirListener {
public void statusChanged(ReservoirEvent event); }
ResEventListener.java public class ResEventListener implements
ReservoirListener {
-
public void statusChanged(ReservoirEvent event) {
System.out.println("Reservoir status : " + event.msg); } }
Reservoir.Java (codul mai sus 1.3.3)
Main.java
public class Main { public static void main(String[] args) {
Reservoir c = new Reservoir(); c.addResListener(new
ReservoirListener() { public void statusChanged(ReservoirEvent
event) { System.out.println("Stare Rezervor -"); //from inline
anonimous class } }); c.fillRes(); c.emptyRes(); } }
Tema:
1. De sunt doua mesaje? 2. Simulati nivelul din rezervor
(creste/scade)
Bibliografie
1. Herbert Schildt, Java J2SE 5: Referine complete, Mikroknjiga
2. Rogers Cadenhead, Laura Lema, Java 6, Mikroknjiga
3. Bruce Eckel, A gndi n Java, Mikroknjiga
4. http://download.oracle.com/javase/tutorial/
5. http://www.javabeginner.com/
6. http://www.java2s.com/Tutorial/Java/CatalogJava.htm
7. http://freewarejava.com/tutorials/index.shtml