WeakReferences and Actions
Actions can be dynamically changed. This is a key feature of Action. The most common use case is changing the Action's enabled property based on some criteria. For example, the back button in a browser is enabled based on whether or not you can navigate to the previous page. To get this to work requires toggling the enabled property.
Actions communicate changes through the ProperyChangeListener interface. That is, any time you change a property on an Action the Action will notify all its PropertyChangeListeners on the change. When you set an Action on a Swing component the Swing component installs a PropertyChangeListener on the Action. The PropertyChangeLisener allows for the Swing component to update it's state to match that of the Action.
An implementation of this might look like:
public class AbstractButton extends JComponent {
public void setAction(Action action) {
action.addPropertyChangeListener(new ActionPropertyChangeListener());
}
private class ActionPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
if ("enabled".equals(e.getPropertyName())) {
setEnabled(action.isEnabled());
}
...
}
}
}
This code looks fine, but it turns out this code is wrong!
One of Java's many claims to fame is garbage collection. With garbage collection developers no longer need to explicitly allocate or deallocate memory, it's taken care of for you. None-the-less developers still need to be cognizant of the lifetime of objects.
If we had implemented support for Actions as highlighted in the previous code the lifetime of the component would be tied to that of the Action. This results from the Action keeping a reference to the PropertyChangeListener, which in turn references the component, which references the Action... In other words we would have a cycle. When this happens the lifetime of each object is the same. As such it's not possible for one to be freed without the other being freed. Here's a picture showing what's going on:
Cycles are only problematic when you want the life cycle of one of the objects in the cycle to be longer than that of the others. For example, the components in a containment hierarchy form a cycle, but this is not problematic because you wouldn't want a button in a visible window to go away suddenly if you didn't explicitly reference it. With Actions this isn't the case, in particular the Action should be able to outlive the lifetime of the component. How do we do this while keeping the cycle?
WeakReferences are a way of indirectly referencing an object. The advantage of referencing an Object via a WeakReference is that the reference isn't considered by the gc. In particular if the only references to an object are via WeakReferences then that object is fair game for the gc to free.
This is exactly what we want for Actions. Rather than having the PropertyChangeListener directly reference the Component it'll indirectly reference the Component through a WeakReference. This way the life time of the component is not tied to that of the Action. For example, if you dispose a window containing a component with an Action the component can now be gc'd.
Here's an image illustrating what is going on now. Notice the extra reference. There is still a cycle, but because of the magic of WeakRefernces the Component can now be gc'd.
And here's the code:
public class AbstractButton extends JComponent {
public void setAction(Action action) {
action.addPropertyChangeListener(new ActionPropertyChangeListener(this, action));
}
private static class ActionPropertyChangeListener implements PropertyChangeListener {
private WeakReference<AbstractButton> buttonRef;
private Action action;
ActionPropertyChangeListener(AbstractButton button, Action action) {
buttonRef = new WeakReference<AbstractButton>(button);
this.action = action;
}
public void propertyChange(PropertyChangeEvent e) {
AbstractButton button = buttonRef.get();
if (button == null) {
// The button has gcd, remove listener
action.removePropertyChangeListener(this);
} else {
if ("enabled".equals(e.getPropertyName())) {
button.setEnabled(action.isEnabled());
}
...
}
}
}
}
There's a couple of interesting things to mention here. Because we're
using WeakReferences it's possible for the referenced object, the
button in this case, to be gc'd. If this happens, the get method of
WeakReferences will return null. If get returns null
ActionPropertyChangeListener removes itself from the set of
PropertyChangeListeners on the Action. The second key thing is that
this inner class must be static. If the class weren't static an
implicit reference would be kept to the button so that the lifetime of
the button and Action would again be the same, defeating all the work
we've done.
For brevitys sake I've isolated this code all in one class and taken a few shortcuts. Internally in Swing we've factored this out into a common class of the same name. If you're curious, download the latest mustang source and poke around.
WeakReferences aren't for everything, but they are necessary in cases like this. In particular if you have a cycle and want some objects to live shorten than others in that cycle, WeakReferences are for you!
- Login or register to post comments
- Printer-friendly version
- zixle's blog
- 2944 reads





