Transcript
  • 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


Top Related