Skip to main content

WeakReferences and Actions

Posted by zixle on November 9, 2005 at 1:25 AM PST

In my last blog I delved into why one might use
Actions. In this article I'm going to cover how Swing's components
support Actions. Eventually I'll wind up in why you should know about
WeakReferences.

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 href="http://mustang.dev.java.net">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!

Related Topics >>