In
the previous example, property changes can take place when one of the
setStocks()
methods are invoked. This results in
the state of the WatchList
being changed. What
would happen if two objects were using the
WatchList
and one of them changed the
Stocks property? It’s possible that users
of the WatchList
will want to know if its
properties are changed.
A Bean component can provide change notifications for its properties. These notifications take the form of events, and they conform to the event model described in the earlier chapters. Properties that support change notifications are known as bound properties, because other objects can bind themselves to changes in their value.
An object can bind itself to non-specific property changes. In this
case the source object sends notifications to the bound object
whenever the value of one of its bound properties has changed. The
notification takes the form of a
java.beans.PropertyChangeEvent
. The public methods
for this class are shown below, without their implementations:
public class java.beans.PropertyChangeEvent extends java.util.EventObject { // contructor PropertyChangeEvent(Object source, // the source of the event String propertyName, // the property name Object oldValue, // the old value Object newValue); // the new value // get the new value of the property public Object getNewValue(); // get the old value of the property public Object getOldValue(); // get the event propagation id public Object getPropagationId(); // get the name of the property that changed public String getPropertyName(); // set the event propagation id public void setPropagationId(); }
The
java.beans.PropertyChangeEvent
class treats old and new values as
instances of class Object
. If the property that
changed uses one of the primitive data types like
int
or float
, you’ll have
to use the object version of the type here, such as
java.lang.Integer
or
java.lang.Float
. It is possible that when the
event is fired the old or new values are not known, or that multiple
values have changed. In this case null
can be
returned from the getOldValue()
or
getNewValue()
method. This is also true of the
property name. If multiple properties have changed, the
getPropertyName()
method may return
null
. The PropertyChangeEvent
also contains something called a propagation ID.
This is reserved for future use. If you receive a
PropertyChangeEvent
and then in turn generate and
fire another PropertyChangeEvent
, you must
propagate the propagation ID as well.
A PropertyChangeEvent
for a bound property should only be fired after its internal state
has been updated. This means that the event signifies a change that
has already taken place. If an object supports bound properties, it
will provide the following methods for registering and unregistering
the associated event listeners:
public void addPropertyChangeListener(PropertyChangeListener p); public void removePropertyChangeListener(PropertyChangelistener p);
The
java.beans.PropertyChangeListener
interface extends the base Java class
java.util.EventListener
. This interface supports a
single method called
propertyChange()
,
which takes only one parameter as an argument, of type
java.beans.PropertyChangeEvent
. If an object wants
to receive notifications of bound property changes, it would
implement the java.beans.PropertyChangeListener
interface and register itself with the source object by calling its
addPropertyChangeListener()
method. A diagram of this interaction is
shown in Figure 4.1.
Let’s go back to our Temperature
class.
Earlier we added a read-only property to this class named
CurrentTemperature. If we wanted to implement
this as a bound property, we would implement the
addPropertyChangeListener()
and
removePropertyChangeListener()
methods. Then
whenever CurrentTemperature changed, we would
fire a PropertyChangeEvent
to all listeners.
Originally we designed the Temperature
class to
fire a TempChangedEvent
to listeners that
implement the TempChangeListener
interface. For
now we’ll remove the code related to the firing of
TempChangeEvent
events. Now that we understand
properties, let’s modify the Temperature
class to fire the PropertyChangeEvent
instead.
Because the object does not keep track of the previous temperature,
the oldValue
parameter of the
PropertyChangeEvent
constructor is set to
null
. The code would now look like this:
package BeansBook.Simulator; import java.beans.*; import java.util.Vector; public class Temperature { // the current temperature in Celsius protected double currentTemp = 22.2; // the collection of objects listening for property changes protected Vector propChangeListeners = new Vector(); // the constructors public Temperature(double startingTemp) { this(); currentTemp = startingTemp; } public Temperature() { } // the get method for property CurrentTemperature public double getCurrentTemperature() { return currentTemp; } // add a property change listener public synchronized void addPropertyChangeListener(PropertyChangeListener l) { // add a listener if it is not already registered if (!propChangeListeners.contains(l)) { propChangeListeners.addElement(l); } } // remove a property change listener public synchronized void removePropertyChangeListener(PropertyChangeListener l) { // remove it if it is registered if (propChangeListeners.contains(l)) { propChangeListeners.removeElement(l); } } // notify listening objects of CurrentTemperature property changes protected void notifyTemperatureChange() { // create the event object PropertyChangeEvent evt = new PropertyChangeEvent(this, "CurrentTemperature", null, new Double(currentTemp)); // make a copy of the listener object vector so that it cannot // be changed while we are firing events Vector v; synchronized(this) { v = (Vector) propChangeListeners.clone(); } // fire the event to all listeners int cnt = v.size(); for (int i = 0; i < cnt; i++) { PropertyChangeListener client = (PropertyChangeListener)v.elementAt(i); client.propertyChange(evt); } } }
With this change in place, we have to change the implementation of
the Thermometer
class as well. Instead of
implementing the TempChangeListener
interface it
will implement the PropertyChangeListener
interface. We remove the
tempChanged()
method and replace it with
a propertyChange()
method. For the sake of
simplicity, let’s revert to the case where the
Thermometer
works with a single
Temperature
object named
theTemperature
instead of the two that we worked
with previously. The code for the changed constructor and
propertyChange()
method is shown below:
// constructor Thermometer(Temperature temperature) { theTemperature = temperature; // register for property change events theTemperature.addPropertyChangeListener(this); } // handle the property change events public void propertyChange(PropertyChangeEvent evt) { // determine if the CurrentTemperature property of the temperature // object is the one that changed if (evt.getSource() == theTemperature && evt.getPropertyName() == "CurrentTemperature") { Temperature t = (Temperature)evt.getSource(); // get the new value object Object o = evt.getNewValue(); double newTemperature; if (o == null) { // go back to the object to get the temperature newTemperature = t.getCurrentTemperature(); } else { // get the new temperature value newTemperature = ((Double)o).doubleValue(); } } }
The propertyChange()
method first determines if
the source of the event was its instance of the
Temperature
class, and if the property that
changed is called CurrentTemperature. If these
two things are true, the source is cast to type
Temperature
and the new value object is retrieved
from the event object. Since the new value object can be
null
, there is a check for that condition. If it
is null
, it goes back to the source to get the
value of the CurrentTemperature property. If it
isn’t null
, the value is retrieved directly
from the new value object.
A support class called
java.beans.PropertyChangeSupport
can be used to fire property change
events to the registered listeners. You can either inherit from this
class, or directly use an instance of it.
PropertyChangeSupport
implements the
java.io.Serializable
interface, which we discuss in Chapter 5. The method signatures of this class are as
follows:
public class java.beans.PropertyChangeSupport implements java.io.Serializable { // construct the object public PropertyChangeSupport(Object source); // add a property change listener public synchronized void addPropertyChangeListener(PropertyChangeListener l); // fire a property change event to any listeners public void firePropertyChange(String propertyName, Object oldValue, Object newValue); // remove a property change listener public synchronized void removePropertyChangeListener(PropertyChangeListener l); }
We could reimplement the Temperature
class by
inheriting from the PropertyChangeSupport
class.
This would eliminate the code that deals with the property change
events. Note that the version of the constructor that takes no
parameters calls the superclass constructor to make itself the event
source. Although we aren’t going to keep the
Temperature
class like this, here’s what the
code would look like if we did:
package BeansBook.Simulator; import java.beans.*; import java.util.Vector; public class Temperature extends PropertyChangeSupport { // the current temperature in Celsius protected double currentTemp = 22.2; // the constructors public Temperature(double startingTemp) { this(); currentTemp = startingTemp; } public Temperature() { super(this); } // the get method for property CurrentTemperature public double getCurrentTemperature() { return currentTemp; } // notify listening objects of CurrentTemperature property changes protected void notifyTemperatureChange() { // fire the event firePropertyChange("CurrentTemperature", null, new Double(currentTemp)); } }
Get Developing Java Beans now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.