3. Dynamiczna Java i programowanie komponentowe (JavaBeans)


Java jest językiem interpretowanym. Kompilator tworzy kod bajtowy, ten zaś podlega interpretacji przez wirtualną maszynę Javy.
Daje to podstawy do dynamicznego, w fazie wykonania, kształtowaniu mechanizmów działania programu. Osiągamy to dzięki tzw. refleksji.

Refleksja jest też wewnętrznym mechanizmem technologii JavaBeans
Termin JavaBeans wiąże się  z koncepcją budowania programów z gotowych, łatwo dostosowywanych do konkretnych potrzeb, komponentów programowych. Idea to niezwykle kusząca, ale jednocześnie – z niewiadomych powodów – owiana nimbem "wyższego wtajemniczenia".  W bardzo krótkim wprowadzeniu do JavaBeans zobaczymy, że nie ma tu nic tajemniczego ani trudnego. Myślę, że będzieono całkiem wystarczające, by zrozumieć podstawowe koncepcje JavaBeans, a dodatkowe szczegóły "techniczne" łatwo można uzupelnić na podstawie dokumentacji SDK Javy..


3.1. Dynamiczne ładowanie klas.

W klasie Object jest metoda getClass. Zastosowana wobec dowolnego obiektu zwraca odnośnik do jego klasy, do obiektu klasy java.lang.Class.
Obiekty klasy Class reprezentują klasy (to ważne: tu same klasy traktujemy jak obiekty)

Możemy wobec nich stosować różne metody klasy Class z pakietu java.lang, np.

getSuperClass(), zwracającą obiekt klasy Class oznaczający klasę bazową danej klasy,
getInterfaces(), zawracającą tablicę obiektów, zawierającą interfejsy danej klasy,
newInstance(), tworzącą nowy obiekt danej klasy.

Obiektów-klas nie możemy tworzyć za pomocą konstrukcji new.
Jedynie poprzez użycie odpowiednich metod uzyskujemy odnośniki do tych obiektów.

Jedną z takich metod jest statyczna metoda klasy Class:

forName(String NazwaKlasy);

Np. pisząc:

Class c = Class.forName("javax.swing.JButton");

lub

Class c = java.awt.Button.class;

uzyskujemy deskryptor klasy przycisków i możemy się nim posłużyć przy tworzeniu obiektu:

JButton b = c.newInstance();
  
W statycznym przypadku, gdy wszystko jest ustalone "w źródle" programu, sens takich konstrukcji jest niewielki. Ale bardzo często warto odłożyć pewne ustalenia do fazy wykonania programu, zwiększając jego elastyczność i uniwersalność. Wtedy dynamiczna reprezentacja obiektów-klas bardzo się przydaje.

Najprostszy przykład: odroczenie ustalenia sposobu obsługi akcji na przycisku do fazy wykonania programu pokazano na poniższym wydruku.  Jako argument podajemy nazwę klasy, której obiekt obsługuje akcję. Możemy mieć wiele takich (wariantowych) klas i bez żadnej rekompilacji zmieniać sposoby obsługi kliknięcia w przycisk.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

class Main extends JFrame {

static void exit(String s) { System.out.println(s); System.exit(1); }

public static void main(String args[]) {
   new Main(args[0]);
}

Main(String actionClassName) {
 Class actionClass = null;
 Action act = null;
 try {
       actionClass = Class.forName(actionClassName);
       act = (Action) actionClass.newInstance();
  } catch (Exception exc) {exit("Obiekt klasy akcji nie może być utworzony");}

 JButton b = new JButton();
 b.setAction(act);
 getContentPane().add(b);
 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 pack();
 show();
 }
}

Uwaga:newInstance zwraca referencję do obiektu klasy Object - stąd potrzeba konwersji do typu Action.

Jeśli teraz przygotujemy dwie klasy, opisujące różne akcje np.
import javax.swing.*;
import java.awt.event.*;

public class DialogAction extends AbstractAction {

  final static String ACTION_NAME = "Show msg";

   public DialogAction() {
     super(ACTION_NAME);
   }

   public void actionPerformed(ActionEvent e) {
     JOptionPane.showMessageDialog(null, ACTION_NAME);
   }

}
i
import javax.swing.*;
import java.awt.event.*;

public class PrintAction extends AbstractAction {

  final static String ACTION_NAME = "Print";

   public PrintAction() {
     super(ACTION_NAME);
   }

   public void actionPerformed(ActionEvent e) {
     System.out.println("Wykonan akcja: " + ACTION_NAME);
   }

}
to uruchomienie głównego programu z argumentem: DialogAction:

java Main DialogAction

spowoduje, że przycisk w oknie uzysaka nazwę Show Msg, a jego klikniecie otworzy okienko komunikaty.

Natomiast po uruchomieniu programu z argmentem PrintAction

java Main PrintAction

nada przyciskowi nazwę Print, a jego kliknięcie wyprowadzi komunikat na konsolę.


Takie możliwości istniały w Javie od zawsze. Ale samo dynamiczne ładowanie klas to zdecydowanie za mało, by można było tworzyć naprawdę elastyczne programy. Dopiero w wersji 1.1  Java zyskała prawdziwą elastyczność dzięki wprowadzeniu mechanizmów refleksji

3.2. Refleksja

Podstawowy programistyczny interfejs refleksji (Core Reflection API) realizowany jest przez klasy pakietu java.lang.reflect  oraz rozbudowaną klasę Class z pakietu java.lang.

Refleksja oznacza możliwoć wykonywania W TRAKCIE WYKONANIA PROGRAMU następujących działań: 


Użycie refleksji pozwala m.in. na:
 
Uzywając mechanizmów refleksji do metod i pól odwołujemy się poprzez ich nazwy, a nie identyfikatory.
Na czym polega różnica wobec statycznego przypadku?

W statyce odwołania są skonkretyzowane na etapie kompilacji.
Piszemy np. b.geText(). I tak już zostanie na zawsze.
W dynamice konstrukcja jest całkiem inna - właśnie posługująca się nazwą metody. Piszemy raczej tak: b.invokeMethod("getText").
Tu invokeMethod jest naszą własną metodą. Jako argument podajemy nazwę metody klasy, a ponieważ jest to String, możemy go zmieniać w każdym momencie wykonania programu. Np. możemy napisać: b.invokeMethod(s), a kolejne podstawienia pod s różnych nazw metod będzie zmieniać znaczenie tej linii programu. Właśnie w invokeMethod używamy środków refleksji.

 Klasy pakietu java.lang.reflect

Klasa

Przeznaczenie

Array 

Tworzenie tablic, uzyskiwanie i ustalanie wartości elementów

Constructor 

Informacja i dostęp do danego konstru ktora danej klasy. W szczególności wykorzystanie dla tworzenia obiektu.

Field 

Informacja i dostęp do pola obiektu. Pobranie i zmiana wartości pola.

Method 

Informacja o danej metodzie danej klasy. Dynamiczne wywołanie metody na rzecz danego obiektu.

Modifier 

Uzyskiwanie informacji o modyfikatorach składowej obiektu lub klasy.


 
Użyteczne metody klasy java.lang.Class

 

Metoda

Przeznaczenie

getClasses() 

getDeclaredClasses() 

Zwraca tablicę obiektów klasy Class, które są składowymi danej klasy.

getConstructors() 

getDeclaredConstructors()

Zwraca tablicę obiektów klasy Constructor; są to konstruktory danej klasy

getConstructor(Class[]) getDeclaredConstructor(Class[]) 

Zwraca obiekt konstruktor (obiekt klasy konstruktor), który ma podane typy argumentów 

getMethods() 

getDeclaredMethods() 

Zwraca tablicę, zawierającą odnośniki do metod klasy. Metody są obiektami klasy Method.

getMethod(String, Class[])

getDeclaredMethod(String, Class[]) 

Zwraca metodę o podanej nazwie i podanych argumentach jako obiekt klsy Method.

Uwaga : Rozróżnienie pomiędzy metodami mającymi i nie mającymi w nazwie tekstu "Declared" jest następujące:

3.3.  Przykład wykorzystania refleksji

Napiszmy program, który daje użytkownikowi wybór co do następstw przyciśnięcia jakiego przycisku. Co więcej, wyborów takich użytkownik może dokonywać w fazie wykonania programu.
Zestaw możliwych akcji (na przyciskach) będzie zawarty w klasie ActionsSet np.

class ActionsSet {
  public void Action1() { System.out.println("Action1"); }
  public void Action2() { System.out.println("Action2"); }
  public void Action3() { System.out.println("Action3"); }
  public void Action4() { System.out.println("Action4"); }
  public void Action5() { System.out.println("Action5"); }
}
Opcje dla użytkownika będą przedstawione w menu kontekstowym, otwieranym na przycisku. Z tego menu może on wybrać (wielokrotnie i różnie w trakcie działania programu) co konkretnie ma się stać, jeśli przyciśnie ten przycisk.

Poniższy wydruk pokazuje konstrukcję programu.

import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class Main extends JFrame implements ActionListener, MouseListener {

  static void exit(String s) { System.out.println(s); System.exit(1); }

  Method currAction = null;   // bieżąca metoda obsługi
  Class actionClass = null;   // klasa obsługi
  Object actionObject = null; // obiekt obsługi
  JPopupMenu popUp = null;    // menu kontekstowe z wyborem obsługi


Main() {
  super("Test refleksji");
  try {
    actionClass = Class.forName("ActionsSet");
    actionObject = actionClass.newInstance();
  }  catch(Exception exc) {
         exit("Wadliwa klasa obsługi");
  }

  JButton b = new JButton("Akcja");
  b.setFont(new Font("Dialog", Font.BOLD, 24));
  b.addActionListener(this);
  b.addMouseListener(this);
  getContentPane().setLayout(new FlowLayout());
  getContentPane().add(b);
  popUp = new JPopupMenu();
  createMenuItems();
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  pack();
  show();
}

void createMenuItems() {
  Method m[] = null;
   try {
       m = actionClass.getDeclaredMethods();
   } catch(Exception exc) {
        exit("Niedostępna info o metodach klasy obsługi");
   }

  for (int i = 0; i < m.length; i++) {
     String name = m[i].getName();
     JMenuItem mi = new JMenuItem(name);
     mi.addActionListener(this);
     popUp.add(mi);
  }
}


void setCurrentAction(String action) {
  Class args[] = {};
  try {
     currAction = actionClass.getMethod(action, args);
    } catch(Exception exc) { exit("Nieznana metoda obsługi"); }
}

public void actionPerformed(ActionEvent e) {
   Object src = e.getSource();
   if (src instanceof JMenuItem)
      setCurrentAction(((JMenuItem) src).getText());
   else {
     try {
       Object args[] = { };
       currAction.invoke(actionObject, args);  // wywołanie metody obsługi
     }  catch(Exception exc) {
          JOptionPane.showMessageDialog(null, "Akcja na przycisku nieustalona!");
     }
   }
}

public void mousePressed(MouseEvent e) {
  showPopup(e);
}
public void mouseReleased(MouseEvent e) {
  showPopup(e);
}

public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}

private void showPopup(MouseEvent e)  {
  if (e.isPopupTrigger()) popUp.show(e.getComponent(), e.getX(), e.getY());
}

public static void main(String args[]) { new Main(); }

}

W programie korzystamy z klasy Method. Uzyskując odnośnik do konkretnej metody, możemu ustalić ją jako obsługującą zdarzenie (metoda setCurrentAction). Przy zajściu zdarzenia możemy "pośrednio" (dynamicznie) wywołać jego obsługę. Służy temu metoda invoke z klasy Method, użyta w actionPerformed.

Działanie programu ilustruje rysunek.

r

Po wyborze opcji Action3 - kliknięcia w przycisk będą wyprowadzać na konsolę napisy "Akcja3".


Dwa  komentarze

Kwestia argumentów: gdy określają sygnaturę metody (w getMethod)  występują jako typy danych, a więc obiekty klasy Class.
Jako konkretne argumenty wywołania metody  są konkretnymi obiektami, a więc egzemplarzami klasy Object (obiekty klasy Object - w metodzie invoke).
W tym programie metoda wywoływana pośrednio nie miała argumentów.

Sposób tworzenia menu kontekstowego. prezentuje ono dostępne akcje, a te - całkowicie - są opisywane poprzez klasę ActionsSet. Stąd ułatwienie : dynamiczne pobranie akcji za pomocą metody getDeclaredMethods z klasy Class. Nawet wtedy, gdy wszystko jest umieszczone w jednym pliku i nie przewiduje się zmian w alternatywnych sposobach obsługi - to podejcie do programowania zasługuje na uwagę.

3.4. Pojęcie JavaBean


JavaBean - (bean – ziarno) – to programowy komponent "wielokretnego użytku", którego właściwości i funkcjonalność mogą być odczytywane i/lub zmieniane uniwersalnymi środkami programistycznymi.


Uwaga: JavaBean oznacza zarówno obiekt, jak i klasę tego obiektu. Rozróżnienie zawsze jasno wynika z kontekstu. O JavaBean będziemy mówić krócej "ziarno".

Co oznacza powyższa definicja?
Gdy wbudowujemy zewnętrzny komponent-ziarno do naszego programu, mamy do dyspozycji środki programistyczne, które pozwalają uzyskać o nim informacje:

Uniwersalność sposobów odczytywania i/lub zmieniana charakterystyk obiektu-ziarna opiera się na:

3.5.  Właściwości i akcesory

Ziarna mają właściwości (atrybuty).

Dostęp do własciwości zapewniają metody klasy-ziarna nazywane akcesorami..
Akcesor pobierający właściwości nazywa się getter, a ustalający – setter.


Wyróżniamy własciwości proste (w tym binarne) oraz właściwości indeksowane.
Właściwości proste mają jedną wartość, właściwości indeksowane – wiele wartości, przedstawianych jako tablica.

Standardowe wzorce deklaracji akcesorów są następujące

Dla  prostej (niebinarnej) własciwości o nazwie NNN i typie Typ

getter: Typ getNNN()
setter: void  setNNN(Typ)

np. dla ziarna javax.swing.JButton i własciwości background mamy
getter: Color getBackground()
 i setter: void setBackground(Color).

Dla właściwości binarnej o nazwie NNN:

getter: boolean isNNN()
setter: void setNNN(boolean)

np. boolean isVisible(), setVisible(boolean)

Dla właściwości indeksowanej o nazwie NNN, której wartości reprezentowane są jako tablice elementów typu Typ:
 
getter elementu:  Typ getNNN(int) // zwraca wartość  podanego indeksu właściwości
setter elementu: vois setNNN(int, Typ) // ustala wartość  podanego indeksu właściwości

getter tablicy: Typ[] getNNN()
setter tablicy: void setNNN(Typ[])


Właściwości ziarna mogą być związane (bounded).

O zmianie związanej właściwości ziarna mogą być zawiadamiane inne komponenty i reagować na tę zmianę.

Właściwości ziarna mogą być ograniczane (constrained).
Ograniczana właściwość – to taka, o której zmianie powiadamiane są zainteresowane inne komponenty i są pytane o zgodę na tę zmianę. Jeśli którykolwiek z komponentów nie da takiej zgody (zawetuje zmianę)  – zmiana nie dochodzi do skutku.

Setter związanej i/lub ograniczanej właściwości ma obowiązek wygenerować zdarzenie klasy PropertyChangeEvent..

Klasy-ziarna , mające związane właściwości musżą dostarczyć metody przyłączania słuchaczy zmian właściwości: addPropertyChangeListener(PropertyChangeListener)

Klasy-ziarna, mające ograniczane właściwości, muszą dostarczyć  metody addVetoableChangeListener(VetoableChangeListener).

Ziarna możemy wykorzystywać, możemy też je tworzyć (w znaczeniu: definiować klasę ziarna)

Tworzenie ziarna (jako klasy) wymaga zdefiniowania klasy,  która:

3.6.  Nasłuch i wetowanie zmian właściwosci

Zmiana właściwości związanej lub ograniczanej powinna generować zdarzenie typu PropertyChangeEvent.

Komponenty (obiekty) zainteresowane w śledzeniu zmian  tej własciwości  muszą implementować interfejs PropertyChangeListener.  W ten sposób stają się słuchaczami zmian właściwości..
Komponenty, które mogą wetować zmiany właściwości muszą implementować interfejs VetoableChangeListener (będą więc słuchaczami zmian właściwości ograniczonych i będę miały możliwość wetowania tych zmian).

Zdarzenie typu PropertChangeEvent możemy zapytać o:
Interfejs PropertyChangeListener ma jedną metodę:

    public void propertyChange(PropertyChangeEvent)

W implementacji tej metody, dowiadując się o zmianach właściwości, możemy na nie odpowiednio reagować.

Również interfejs VetoableChangeListener ma jedną metodę: vetoableChange(...) z argumentem–zdarzeniem typu PropertyChange. W jej implementacji , gdy dowiemy się już wszystkich niezbędnych szczegółow o zmianie – możemy ją zawetować.

Wetowanie zmiany  odbywa się na zasadzie zgłoszenia wyjątku PropertyVetoException, zatem deklaracja metody vetoableChange wygląda następująco:

    public void vetoableChange(PropertyChangeEvent e) 
                                                throws PropertyVetoException

a w jej implementacji – gdy po sprawdzeniu warości właściwości chcemy zgłosić veto – sygnalizujemy wyjątek: throw new PropertyVetoException(...).

Słuchacze zmian  właściwości  związanych (jak zawsze w Javie) musżą być przyłączeni do źródła zdarzenia., którym jest  w tym przypadku ziarno. 
Przyłączenie staje się możliwe, jesli w klasie-ziarnie zdefiniowano metodę addPropertyChangeListener(...)
Musi też być zdefiniowana metoda removePropertyChangeListener, odłaczająca słuchacza.

To samo dotyczy nasłuchu zmian właściwości ograniczanych: klasa zaiarno musi dostarczyć  metody przyłączenia słuchacza:  addVetoableChangeListener oraz metody odłączania słuchacza:  removeVetoableChangeListener.

Obowiązkiem klasy ziarna, która implementuje związane i/lub ograniczane właściwości jest również dostarczenie odpowiednich definicji setterów dla tych właściwości.
W setterach należey  generować zdarzenie PropertyChangeEvent  i propagować go pośród przyłączonych słuchaczy.

W pakiecie java.beans znajdują się dwie klasy narzędziowe,  znacznie ułatwiające wykonanie tych zadań: PropertyChangeSupport i VetoableChangeSupport.
Klasy dostarczają metod  generowania zdarzeń i propagacji zdarzeń zmian:
Konstruktory tych klas mają jako argument  referencję do obiektu-ziarna.

Schemat postępowania przy implementacji właściwości związanej jest następujący:

class Ziarno .... {

  //wsparcie
  private PropertyChangeSupport chg = new PropertyChangeSupport(this);

  String text;    // to będzie właściwośc związana o nazwie "text"
  ...
// setter
synchronized void setText(String newTxt) { // pamiętamy o wielowątkowości!

    String oldTxt =  text;   // stara wartość
    text = newTxt;           // ustalenie nowej wartości
     // powiadomienie
    chg.firePropertyChange("text", oldTxt, newTxt);
}
....

// metody dodawania i usuwania słuchaczy
public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
 chg.addPropertyChangeListener(l);
 }

public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
 chg.removePropertyChangeListener(l);
 }
...
}


Implementacja właściwości ograniczanych musi uzwględniać możliwość zawetowania zmiany przez któregoś ze słuchaczy.

Schemat postępowania przy implementacji właściwości ograniczanej jest następujący:

class Ziarno .... {

  //wsparcie
  private VetoableChangeSupport veto = new VetoableChangeSupport(this);

  String text;    // to będzie właściwośc ograniczana o nazwie "tekst"
  ...
// setter

synchronized void setText(String newTxt)  throws PropertyVetoException
 {
    String oldTxt =  text;  // stara wartość

     // wywołujemy metodę fireVotoableChange, która z kolei
     // wywołuje metody vetoableChange zarejestrowanych słuchaczy
     // jeśli któraś z nich zgłasza veto, setter kończy działanie
     // a wyjątek PropertyVetoException jest przekazywany do obsługi
     //  przez metodę wywołującą setText

     veto.fireVetoableChange("tekst", oldTxt, newTxt);

     // Tylko jeśli nikt nie zawetował zmiany:

     text = newTxt;   // ustalenie nowej wartości
}
....
// metody dodawania i usuwania słuchaczy

public synchronized void addVetoableChangeListener(PropertyChangeListener l) {
 veto.addVetoableChangeListener(l);
 }

public synchronized void removeVetoableChangeListener(PropertyChangeListener l) {
 veto.removeVetoableChangeListener(l);
 }
...
}

// Uwaga: wsystkie klasy  zdarzeniowe i interfejsy nasłuchu
// dla właściwości znajdują się w pakiecie java.beans.


3.7.  JavaBean - przykład praktyczny

Będziemy budować klasę-licznik jako JavaBean. Klasę nazwiemy Counter.
Licznik będzie miał jedną właściwość o nazwie count (stan licznika).
W wersji pierwszej uczynimy tę właściwość związaną (bounded), w  wersji drugiej – związaną i ograniczoną (constrained).

Przy budowie aplikacji zastosujemy koncepcję  "Model-View-Controller". Będzie to dalsze praktyczne ćwiczenie tej ważnej tematyki, którą na razie poznaliśmy tylko na przykładzie kompoenntów Swingu.

Sama klasa Counter odzwierciedla logikę działania licznika ("model "). Obiekty tej klasy "są niewidzialne", a zatem  żeby zobaczyć licznik musimy stworzyć dodatkową klasę, która zdefiniuje widok licznika  (view).
Nazwiemy ją CounterView. Warto zwrócić uwagę: separacja kodu jest korzystna – widok uniezależniamy od modelu, a model od widoku, w ten sposób możemy mieć np. wiele widoków licznika, lub zmieniać model nie zmieniając widoku.

Komunikacja między modelem i widokiem będzie się odbywać na zasadzie nasłuchu zmian właściwości (zmian właściwości count) czyli obiekt klasy CounterView będzie też słuchaczem zmian właściwości (PropertyChangeListener).

Musimy też mieć jakieś środki zmiany stanu licznika. Interakcję użytkownika z modelem/widokiem zapewnia tzw. kontroler.
Widzieliśmy, że w komponentach Swingu (w naturalny dla nich sposób) kontroler połączony jest z widokiem. Tu jednak odseparujemy jego kod od widoku, tworząc klasę CounterControlGui, zapewniającą interfejs interakcji z licznikiem.. Widok zostanie dodany do tego GUI (ale kody obu klas będą odseparowane).

W wersji drugiej – kiedy właściwość count będzie związana i ograniczana musimy dostarczyć obiektu-nadzorcy, który będzie sprawdzał czy zmiana właściwości jest dopuszczalna i jeśli stwierdzi, że nie – będzie wetował tę zmianę. Odpowiednią klasę nazwiemy CounterLimitator.

W metodzie main(...) klasy Main, w której nasza aplikacja zacznie życie stworzymy wszystkie odpowiednie obiekty w/w klas i ustanowimy niezbędne połączenia między nimi.

W sumie logika działania aplikacji będzie wyglądać tak:


r

Zaczynamy od wersji pierwszej, uboższej, w której klasa Counter daje przykład typowego programowania JavaBean z wlaściwością związaną:

// Klasa Counter

import java.awt.event.*;
import java.beans.*;
import java.io.*;

public class Counter implements Serializable {

  private int count = 0;    // właściwość count

  // Pomocniczy obiekt do prowadzenia listy słuchaczy zmian właściwości oraz
  // propagowania zmian  wśród zarejestrowanych złuchaczy
   private PropertyChangeSupport propertyChange = new PropertyChangeSupport(this);


  // Konstruktory

  public Counter() {
    this(0);
  }

  public Counter(int aCount) {
    setCount( aCount );
  }


  // Metody przyłączania i odłączania słuchaczy zmian właściwości

  public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
    propertyChange.addPropertyChangeListener(listener);
  }

  public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
    propertyChange.removePropertyChangeListener(l);
  }

  // Proste metody zwiększania i zmniejszania licznika

  public void increment() {
    setCount(getCount()+1);
  }

  public void decrement() {
    setCount(getCount()-1);
  }


  // Getter właściwości "count"
  public int getCount() {
    return count;
  }

  // Setter właściowści "count"
  public synchronized void setCount(int aCount) {
    int oldValue = count;
    count = aCount;

    // wywołanie metody firePropertChange z klasy PropertyChangeSupport
    // powoduje wygenerowanie zdarzenia PropertyChangeEvent i rozpropagowanie
    // go wśród wszystkich przyłączonych słuchaczy, ale tylko wtedy, gdy nowa
    // wartość właściwości różni się od starej wartości

    propertyChange.firePropertyChange("count", new Integer(oldValue),
                                            new Integer(aCount));
  }

}
Widok licznika przedstawimy jako etykietę - klasa ta jednocześnie będzie nasłuchiować zmian właściwości count i odpowiednio do tego zmieniać tekst na etykiecie (a także wyprowadzać informacje o zmianach właściwości count na konsolę).

//Klasa CounterView
//Widok licznika przedstawiamy w postaci etykiety

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;


public class CounterView extends JLabel implements PropertyChangeListener {


  // Konstruktor domyślny: inicjalizuje etykietę tekstem "0"
  CounterView()  {
     this("0");
  }

  // Konstruktor inicjalizujący etykietę podanym tekstem
  CounterView(String lab) {
     super(lab);
     setOpaque(true);   // etykieta nie przezroczysta
       // ramka
     setBorder(BorderFactory.createLineBorder(Color.black));
       // rozmiary i wyrównanie tekstu
     setPreferredSize(new Dimension(75, 40));
     setHorizontalAlignment(CENTER);
  }

  // obługa zdarzenia PropertyChange
  public void propertyChange(PropertyChangeEvent e)  {
    Integer oldVal = (Integer) e.getOldValue(),
           newVal = (Integer) e.getNewValue();
    System.out.println("Value changed from " + oldVal + " to " + newVal);
    setText("" + newVal + "");  // pokazanie na etykiecie nowego stanu licznika
   }


}
Klasa kontrolera - CounterControlGui dostarcza dwóch przycisków (zwiększ, zmniejsz licznik) oraz pole tekstowe, w którym można wpisać wartość licznika (ENTER)
Zarówno kliknięcie w przyciski jak i ENETR na polu tekstowym powoduje powstanie zdarzenia Action, które tu (w tej klasie) obsługujemy ustalając nowe wartości licznika za pomocą metod incremet() decrement() i setCount(...) z klasy  Counter.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;

public class CounterControlGui extends JFrame implements ActionListener {

  Counter counter;
  JButton binc = new JButton("Increment");
  JButton bdec = new JButton("Decrement");
  JTextField txt = new JTextField(10);

  // Konstruktor otrzymuje jako argumenty obiekty typu Counter i CounterView
  // Pierwszy jest nam potrzebny do komunikacji z licznikiem, drugi - widok
  // wbudujemy w to GUI.

  CounterControlGui(Counter c, CounterView clab)  {
    counter = c;
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    binc.addActionListener(this);
    cp.add(binc);
    cp.add(clab);
    bdec.addActionListener(this);
    cp.add(bdec);
    txt.addActionListener(this);
    cp.add(txt);
    setDefaultCloseOperation(3);
    pack();
    show();
  }


  // Obsługa akcji
  public void actionPerformed(ActionEvent e)  {
      if (e.getSource() == txt)  {
         int n = 0;
         try  {
            n = Integer.parseInt(txt.getText());
         } catch (NumberFormatException exc)  { return; }
         counter.setCount(n);
         return;
      }
      String cmd = e.getActionCommand();
      if (cmd.equals("Increment")) counter.increment();
      else if (cmd.equals("Decrement")) counter.decrement();
      else System.out.println("Unrecognized command");
  }

}
I wreszcie lacząca wszystko klasa Main, która inicjuje działanie aplikacji.

public class Main {

 public static void main(String[] args)  {

   // Tworzymy obiekty: licznik i jego widok
   Counter counter = new Counter();
   CounterView counterView = new CounterView(""+counter.getCount());

   // Rejestrujemy widok jako słuchacza zmian licznika
   counter.addPropertyChangeListener(counterView);

   // Tworzymu GUI kontrolera i pokazujemy go
   CounterControlGui gui = new CounterControlGui(counter, counterView);
   gui.pack();
   gui.show();
  }

}

Dizałanie aplikacji ilustruje rysunek oraz komunikaty na konsoli, powstałe po kolejnych klilnięciach w przyciski Incremenet i Decrement oraz wprowadzeniu liczby 23 w polu tekstowym i naciśnieciu ENTER.

r


Value changed from 0 to 1
Value changed from 1 to 0
Value changed from 0 to 23


Wersja 2

W wersji drugiej chcemy dodać obiekt – nadzorcę, który będzie sprawdzał czy zmiana licznika jest dopuszczalna, a jeśli nie to będzie wetował tę zmianę.
Mechanizm wetowania polega na sygnalizowaniu wyjątku PropertyVetoException.
Wyjątek ten będzie sygnalziowany przez obiekt-nadzorcę, który jest jednoczesnie słuchaczem zmian wartości właściwości ograniczonych (VetoableChangeListener).

Klasa definiująca obiekty nadzorujące nasz licznik wygląda tak.

import java.beans.*;

public class CounterLimitator implements VetoableChangeListener {

// minimalne i makszymalne dopuszczalne wartości licznika
private int min, max;

CounterLimitator(int minLim, int maxLim)  {
  min = minLim;
  max = maxLim;
}

// Obsługa zdarzenia vetoableChange
// metoda może sygnalizować PropertyVetoException
public void vetoableChange(PropertyChangeEvent e)
            throws PropertyVetoException {
   Integer newVal = (Integer) e.getNewValue();
   int val = newVal.intValue();
   // Sprawdzamy, czy zmiana  licznika jest dopuszczalna,
   // jeśli nie – sygnalizujemy wyjatek  PropertyVetoException
   if (val < min || val > max)
      throw new PropertyVetoException("Niedopuszczalna zmiana wartości", e);
   }

}

W klasie Counter musimy poczynić zmiany, po to by właściwość count była zarazem związana i ograniczana.
Wykorzystamy podobną do PropertyChangeSupport klasę pomocniczą VetoableChangeSupport
Obiekt tej klasy nazwiemy vetos, dostarczymy też metod przyłączania i odłączania słuchaczy zmian ograniczanych.

public class Counter {

//...

private VetoableChangeSupport vetos = new VetoableChangeSupport(this);

public synchronized void addVetoableChangeListener(VetoableChangeListener l) {
	vetos.addVetoableChangeListener(l);
}

public synchronized void removeVetoableChangeListener(VetoableChangeListener l) {
	vetos.removeVetoableChangeListener(l);
}

// ...
}

Zmianie ulegnie też metoda setCount.

public class Counter {

//...
public synchronized void setCount(int aCount)
                         throws PropertyVetoException {

     int oldValue = count;

     // wywołujemy metodę fireVotoableChange, która z kolei
     // wywołuje metody vetoableChange zarejestrowanych słuchaczy
     // jeśli któraś z nich zgłasza veto, setter kończy działanie
     // a wyjątek PropertyVetoException jest przekazywany do obsługi
     // przez metodę wywołującą setCount
     // (co zaznaczyliśmy w nagłówku metody przez
     // throws PropertyVetoException)

    vetos.fireVetoableChange("count", new Integer(oldValue), new Integer(aCount));

    // tylko jeśli nikt nie zawetował

    count = aCount;  // ustalamy nową wartość licznika

    // ... powiadamiamy nasłuchujących zmiany właściwości count

    propertyChange.firePropertyChange("count", new Integer(oldValue),
                                       new Integer(aCount));
}

//...
}
Ponieważ metody increment, decrement i konstruktory wywołują metodę setCount, to w sygnaturach tych metod i konstruktorów musimy dodać informację, że wyjątek PropertyVetoException ma być obsługiwany przez  wołającego te metody.

A wołamy je z GUI kontrolera.
Tam jest miejsce do obsługi tego wyjątku:
public class CounterControlGui .... {

....
public void actionPerformed(ActionEvent e)  {
  try  {
    if (e.getSource() == txt)  {
       int n = 0;
       try  {
          n = Integer.parseInt(txt.getText());
       } catch (NumberFormatException exc)  { return; }
       counter.setCount(n);
       return;
    }
    String cmd = e.getActionCommand();
    if (cmd.equals("Increment")) counter.increment();
    else if (cmd.equals("Decrement")) counter.decrement();
    else System.out.println("Unrecognized command");
  } catch (PropertyVetoException exc)  { // obługa wyjatku:
      System.out.println(""+ exc);       // podanie informacji
                                         //o niedopuszczalnej zmianie wartości
  }
}

//...
}
W klasie Main dodajemy fragment dotyczący tworzenia obiektu klasy CounterLimitator (nadzorcy) i rejestrujemy go jako słuchacza zmian właściwości ograniczanej:

import java.beans.*;
public class Main {

 public static void main(String[] args) throws PropertyVetoException {
   Counter counter = new Counter();
   CounterView counterView = new CounterView(""+counter.getCount());
   counter.addPropertyChangeListener(counterView);

   // licznik może się zmieniać od –5 do 10
   // bo Limitator zawetuje każdą inną zmianę
   CounterLimitator clim = new CounterLimitator(-5, 10);
   counter.addVetoableChangeListener(clim);

   CounterControlGui gui = new CounterControlGui(counter, counterView);
   gui.pack();
   gui.show();
  }

}
Dzialanie programu  (przy tym samym GUI kontrolera) zilustrujemy komunikatami na konsoli po kolejneych kliknięciach w przycisk "Decrement".



Value changed from 0 to -1
Value changed from -1 to -2
Value changed from -2 to -3
Value changed from -3 to -4
Value changed from -4 to -5
java.beans.PropertyVetoException: Niedopuszczalna zmiana wartości


Jak widać, CounterLimitator nie dopuścił do zmiany wartości z -5 na -6.


3.8. Introspekcja


Srodowisko programistyczne, które ma umożliwiać dynamiczne odczytywanie własności i funkcjonalności dowolnych "podrzucanych" mu ziaren analizuje ziarna za pomocą uniwerslanych metod introspekcji.

Introspekcja kojarzy dwa mechanizmy:
Oba mechanizmy mogą być stosowane łącznie. W prostych przypadkach (prostych ziaren) nie ma potrzeby żmudnego tworzenia klasy BeanInfo, metody refleksji są całkiem wystarczające.
Introspekcja za pomocą metod refleksji jest możliwa dzięki kontraktowi dotyczącemu wzorców nazewnictwa.

Dla właściwości introspekcja określa pary metod get... (is...)  -  set... z tymi samymi nazwami właściwości i z odpowiednimi sygnaturami. Może się okazać, że niektóre właściwości są tylko do odczytu lub tylko do zapisu. Uwzględnia się też indeksowane własciwości.

Dla  określenia, czy istnieją możliwość obsługi zdarzeń i jakich używane są wzorce: addXXXListener i removeXXXListener.

Samą introspekcje realizuje klasa Introspector.

Introspector analizuje klasę-zarna (i nadrzędne wobec niej klasy oraz implementowane interfejsy) zbierając informacje o właściowościach, metodach, zdarzeniach.
Informacja ta jest umieszczana w obiekcie typu BeanInfo (BeanInfo jest nazwą interfejsu). Wobec tego obiektu możemy następnie zastosować metody zwracające iinformacje o ziarnie.

Np. analizę klasy-ziarna javax.swing.JButton uzyskujemy przez następujące odwołanie:

BeanInfo info = Introspector.getBeanInfo(Class.forName("javax.swing.JButton"));
lub
BeanInfo info = Introspector.getBeanInfo(javax.swing.JButton.class));


Następnie wobec obiektu info możemu zastosować metody interfejsu BeanInfo:

Zwracane tablice są tablicami obiektów odpowiednich klas. Wobec tych obiektów stosujemy metody tych klas pozwalające na uzyskiwanie różnej konkretnej informacji.
Np. program na poiniższym wydruku analizuje klasę podaną jako argument i wypisuje niektóre informacje o niej:

import java.lang.reflect.*;
import java.beans.*;

public class BeanAnalyze {

  static void say(String s) { System.out.println(s); }

  public static void main(String[] arg) throws Exception {

    BeanInfo beanInfo = Introspector.getBeanInfo(Class.forName(arg[0]));

    PropertyDescriptor[] pd = beanInfo.getPropertyDescriptors();
    MethodDescriptor[] md = beanInfo.getMethodDescriptors();
    EventSetDescriptor[] evd = beanInfo.getEventSetDescriptors();

    say("Właściwości:");
    for (int i = 0; i < pd.length; i++) {
      say(pd[i].getShortDescription());
      // getReadMethod i getWriteMethod zwracają obiekty typu Method
      say(" getter: "+ pd[i].getReadMethod());
      say(" setter: "+ pd[i].getWriteMethod());
    }

    say("\nMetody:");
    for (int i=0; i<md.length; i++) {
      say(" " + md[i].getMethod());
    }

    say("\nZdarzenia:");
    for (int i = 0; i < evd.length; i++) {
      say("Zdarzenie : " + evd[i].getShortDescription());
      Method[] met = evd[i].getListenerMethods();
      say("Metody obsługi:");
      for (int j=0; j < met.length; j++)  say(" " + met[j]);
    }
  }

}

Fragmenty (dużego!) wydruku z programu uruchomionego z argumentem javax.swing.JButton:


Właściwości:
UI
 getter: public javax.swing.plaf.ButtonUI javax.swing.AbstractButton.getUI()
 setter: public void javax.swing.AbstractButton.setUI(javax.swing.plaf.ButtonUI)
UIClassID
 getter: public java.lang.String javax.swing.JButton.getUIClassID()
 setter: null
accessibleContext
 getter: public javax.accessibility.AccessibleContext javax.swing.JButton.getAccessibleContext()
 setter: null
action
 getter: public javax.swing.Action javax.swing.AbstractButton.getAction()
 setter: public void javax.swing.AbstractButton.setAction(javax.swing.Action)
actionCommand
 getter: public java.lang.String javax.swing.AbstractButton.getActionCommand()
 setter: public void javax.swing.AbstractButton.setActionCommand(java.lang.String)
actionListeners
 getter: public java.awt.event.ActionListener[] javax.swing.AbstractButton.getActionListeners()
 setter: null
actionMap
 getter: public final javax.swing.ActionMap javax.swing.JComponent.getActionMap()
 setter: public final void javax.swing.JComponent.setActionMap(javax.swing.ActionMap)
...

Metody:
 public boolean java.awt.Component.action(java.awt.Event,java.lang.Object)
 public synchronized void java.awt.Component.add(java.awt.PopupMenu)
 public java.awt.Component java.awt.Container.add(java.awt.Component)
 public java.awt.Component java.awt.Container.add(java.awt.Component,int)
 public void java.awt.Container.add(java.awt.Component,java.lang.Object)
 public void java.awt.Container.add(java.awt.Component,java.lang.Object,int)
 public java.awt.Component java.awt.Container.add(java.lang.String,java.awt.Component)
 public void javax.swing.AbstractButton.addActionListener(java.awt.event.ActionListener)
...

Zdarzenia:
Zdarzenie : action
Metody obsługi:
 public abstract void java.awt.event.ActionListener.actionPerformed(java.awt.event.ActionEvent)
Zdarzenie : ancestor
Metody obsługi:
 public abstract void javax.swing.event.AncestorListener.ancestorMoved(javax.swing.event.AncestorEvent)
 public abstract void javax.swing.event.AncestorListener.ancestorAdded(javax.swing.event.AncestorEvent)
 public abstract void javax.swing.event.AncestorListener.ancestorRemoved(javax.swing.event.AncestorEvent)
Zdarzenie : change
Metody obsługi:
 public abstract void javax.swing.event.ChangeListener.stateChanged(javax.swing.event.ChangeEvent)
...


Szczerze mowiąc, za pomocą metod instrospekcji sporo można się dowiedziec o dostępnych właściwościach i metodach danej klasy.

Oczywiście, mając obiekty-metody (uzyskane z deskryptorów metod) mozemy je dynamicznie wyowływac środkami refleksji. Ale istnieje też nieco prostsza, specjalnie dla JavaBean przygotowana możliwość dynamicznego pobierania i ustalania wlaściwości oraz wołania innych metod.

3.9. Dynamiczne pobieranie i ustalanie właściwości

W pakiecie java.beans znajdziemy dwie ciekawe klasy  Statement i Expression

Obiekt klasy Statement tworzymy za pomocą konstuktora:

    Statement s =  new Statement( Object target,
                                           String methodName,
                                           Object[] arguments );

a wywołanie na jego rzecz metody void execute() spowoduje wywołanie na rzecz obiektu target metody o nazwie methodName z argumentami podanymi w tablicy arguments

Zwykle stosujemy tę procedure do ustalania właściowości JavaBean, ale może ona być rownież stosowana do wyołania dowolnych metod


Klasa Expression dziedziczy klasę Statement. Czyli tu też możemy wołać metodę execute(). Jednak jej główne zastosowanie polega na dynamicznym pobieraniu właściwości, lub - inaczej - dynamicznym wołaniu metod, które zwracają wyniki i uzyskiwaniu dostępu do tych wyników.

Obiekt klasy Expression tworzymy za pomocą konstuktora:

    Expression e =  new Expression( Object target,
                                                String methodName,
                                                Object[] arguments );



Szczegółowo komentowany program ilustruje zastosowanie tych klas.
W poniższym przykładzie metody klas Statement i Expression bedziemy stosowac wobec obiektów klasy JButton oraz naszej własnej klasy TestBean, która wygląda tak:
public class TestBean {

  private String[] headers;
  private int count;


  public TestBean() {
  }

  public TestBean(int n) {
    count = n;
  }

  public String[] getHeaders() {
    return headers;
  }

  public void setHeaders(String[] value) {
    headers = value;
  }

  public int getCount() {
    return count;
  }

  public void setCount(int value) {
    count = value;
  }

}

Szczegółowo komentowany program ilustruje zastosowanie klas Statement i Expression, dając też pewne dodatkowe na ich temat informacje.
import java.beans.*;
import java.awt.*;
import javax.swing.*;

public class DynamicExec {

  public static void main(String[] args) throws Exception {

    Statement stmt;
    Expression expr;

    JButton b = new JButton();

    // Na rzecz przycisku wołamy dynamicznie metodę setText
    // z argumentem "Przycisk"
    stmt = new Statement(b, "setText", new Object[] { "Przycisk" });
    stmt.execute();

    // Jaki wynik? Najpierw statyczne odwołanie
    System.out.println("Tekst na przycisku 1: " + b.getText());

    // Teraz dynamicznie: stwórzmy wyrażenie, którego wynikiem
    // jest wynik podanej metody z podanymi argumentami wywolanej
    // na rzecz b
    // Uwaga: brak argumentów - czyli tablica Object o rozmiarze 0
    expr = new Expression(b, "getText", new Object[0]);

    // Jeżeli wyrażenie expr nie ma jeszcze wyniku
    // metoda getValue() wywołuje podaną w wyrażeniu metodę
    // i zwraca jej wynik; w przeciwnym razie zwraca
    // ustalony wczesniej wynik

    String txt = (String) expr.getValue();
    System.out.println("Tekst na przycisku 2: " + txt);

    // Możemy też stosować klasy Statement i Expression
    // wobec naszych własnych klas JavaBeans

    TestBean tbean = new TestBean();

    // Uwaga: przy przekazywaniu argumentów i zwrocie wynikow
    // następują automatyczne przeksztalcenia pomiedzy
    // typami prostymi i odpowiadającymi im klasami opakowującymi
    // np. int - Integer  - setCount wymaga argumentu int,
    // my podajemy Integer

    stmt = new Statement(tbean, "setCount",
                               new Object[] { new Integer(22) });
    stmt.execute();

    // Jaka jest teraz wartość właściwości count
    // I znowu: getCount() zwraca int, my odbieramy Integer

    expr = new Expression( tbean, "getCount", new Object[0] );
    Integer val = (Integer) expr.getValue();

    System.out.println("Wartość count: " + val);

    // Czy możemy działać na tabliach? Ależ tak!

    stmt = new Statement(tbean, "setHeaders",
                         new Object[] { new String[] { "a", "b" } }
                        );
    stmt.execute();

    expr = new Expression(tbean, "getHeaders", new Object[0]);
    String[] hdr = (String[]) expr.getValue();

    System.out.println("Ustalone nagłówki");
    for (int i=0; i<hdr.length; i++)
       System.out.println(hdr[i]);

    // Możemy nawet stworzyć nowy obiekt
    // używając specjalnej nazwy metody - new  (oczywiście)

    expr = new Expression(TestBean.class, "new",
                          new Object[] { new Integer(111) }
                         );
    TestBean tb2 = (TestBean) expr.getValue();

    expr = new Expression (tb2, "getCount", new Object[0]);
    val = (Integer) expr.getValue();

    System.out.println("W nowym obiecie count = " + val);

  }
}



3.10. Serializacja JavaBeans

Oprócz zapisywania do strumieni obiektowych (ObjectOutputStream), obiekty JavaBeans można serializowac w postaci tekstowej, w formacie XML (wersja 1.0, kodowanie UTF-8). Jest to nawet - w przypadku JavaBeans - bardziej "przenośny" sposób utrwalania obiektów.
Zapisywaniem obiektów zajmuje się klasa XMLEncoder, a ich odtwarzaniem - klasa XMLDccoder. Przy tym  stan obiektu zapisywany jest w postaci  zdatnej do wykorzystania przez klasy Statement i Expression przy odtwarzaniu tego obiektu

Najprostsze (przeznaczone wyłącznie dla JavaBeans) zastosowanie tych klas pokazuje poniższy program.
import java.beans.*;
import java.awt.*;
import javax.swing.*;
import java.io.*;

public class SerialBean {

  String fname = "test.xml";

  public SerialBean() {
    JButton b = new JButton("Kąśliwie wróbel ćwierkał");
    b.setBackground(Color.red);
    b.setForeground(Color.yellow);

    try {
      XMLEncoder enc = new XMLEncoder(
                         new BufferedOutputStream(
                            new FileOutputStream(fname)
                            )
                      );
      enc.writeObject(b);
      enc.close();
    } catch (FileNotFoundException exc) {
        exc.printStackTrace();
        System.exit(1);
    }
    nowReadAndReport();
  }

  private void nowReadAndReport() {
    try {
      XMLDecoder dec = new XMLDecoder(
                          new BufferedInputStream(
                              new FileInputStream(fname)));
      Object obj = dec.readObject();
      JButton b = (JButton) obj;
      dec.close();
      System.out.println("Napis na przycisku: " + b.getText());
      System.out.println("Kolor tła: " + b.getBackground());
      System.out.println("Kolor tekstu : " + b.getForeground());

    } catch (FileNotFoundException exc) {
        exc.printStackTrace();
        System.exit(1);
    }
  }



  public static void main(String[] args) {
    SerialBean serialbean = new SerialBean();
  }


}
Wyprowadzi on na konsolę właściwą  informację o stanie zapisanego a później odtworzonego obiektu:

Napis na przycisku: Kąśliwie wróbel ćwierkał
Kolor tła: java.awt.Color[r=255,g=0,b=0]
Kolor tekstu : java.awt.Color[r=255,g=255,b=0]


Z ciekawości można zajrzeć do pliku test.xml.
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.4.1-rc" class="java.beans.XMLDecoder">
 <object class="javax.swing.JButton">
  <string>KÄĹ>liwie wrĂłbel Ä+wierkaĹ'</string>
  <void property="background">
   <object class="java.awt.Color">
    <int>255</int>
    <int>0</int>
    <int>0</int>
    <int>255</int>
   </object>
  </void>
  <void property="foreground">
   <object class="java.awt.Color">
    <int>255</int>
    <int>255</int>
    <int>0</int>
    <int>255</int>
   </object>
  </void>
 </object>
</java>

Faktycznie, jest do dokładny szablon gotowy do zastosowania klas Expression (dla metody-kostsruktora new z argumentem String) i Statement (dla setBackground i setForeground). Wszystko opiera się na zachowaniu protokołu JavaBeans.

Oczywiście, możemy w ten sposób serializować obiekty prawie wszystkich klas Javy (bo prawie wszystkie są JavaBeans) oraz  własnych klas, spełniających protokół JavaBeans.
A nawet można uwzględnić pewne odstępstwa od tego protokołu (np. inicjację właściwości JavaBeans w konstruktorz, bez użycia setterów) poprzez dostosowanie delegata "persystencji", do którego odwołuje się XMLEncoder - klasy DefaultPersistanceDelegate.
O takim bardziej zaawansowanym zastosowaniu, jak również o użyciu nasłuchu wyjątków dekodowania za pomocą interfejsu ExceptionListener można przeczytać w dokumentacji API Javy.


3.11. Inne zagadnienia związane z JavaBeans


To skrótowe wprowadzenie do JavaBeans nie wyczerpuje tematu.
Warto więc na koniec zwrócić uwagę na nieporuszone tu ważne kwestie
Informacje na te teamaty można zanaleźć w dokumentacji.

3.12. Ćwiczenia i zadania

  1. Za pomocą refleksji wypisać nazwy i typy parematrów wszystkich dostępnych metod wybranej klasy ze standardowcyh pakietów Javy.
  2. Napisać program, który na podstawie podanych w pliku tgekstowym informacji tworzy okno z komponentami i ustala ich właściwości. Wykorzystać refleksję.
  3. Stworzyć klasę-ziarno, reprezentującą obiekty - konta bankowe. Oczywiście będziemy tu mieli wpłaty i wypłaty. Napisać program symulacyjny, w którym obniżenie stanu konta poniżej zera jest automatycznie raportowane, a wypłaty powodujące przekroczenie limitu przyznanego kredytu - automatycznie odrzucane.
  4. Za pomocą mechanizmów serializacji JavaBeans zapewnić utrwalanie i odtwarzanie informacji o kontach.