Concepts / Creating Actions with AbstractAction

Creating Actions with AbstractAction

An Action can be passed to the constructor of a JButton or a JMenuItem, so one Action can drive several components.

  • Programming

A Familiar Button and a New Route

Up to now, a push button in a Java GUI has probably been built in a familiar way. You create it with a text label, then attach an ActionListener that runs when the user presses it. That approach works well for a single button. This article introduces a second route: you package the behavior into an Action object first, and then hand that same object to a button. As you will see, the same object can also be handed to a menu item, and one change made to the Action will reach every component built from it.

Before reading any explanation, look at the declaration below and ask yourself one question: when a button built from clearAction is pressed, which part of this declaration does the work? Keep your answer in mind. The rest of the article unpacks it.

java

What an Action Bundles Together

Definition

An Action is an object that represents an action together with properties that describe it, such as a name, an icon, and an enabled or disabled status. The Action interface defines what an action is.

Think of an Action as more than a single method. It bundles descriptive information with behavior. The name is the text a user sees for the action. The icon is a picture that represents it. The enabled property records whether the action is currently available or unavailable. These properties describe the action as something a user can see and control, separate from the code that runs when the action fires.

This bundling is why the Action idea is useful. The same object carries a label, an icon, and an enabled state, so any component that uses the action can present itself consistently. Notice that the properties belong to the action, not to one particular button. That distinction matters once you see that a button is only one of the things that can use an action. A menu item can use it too.

Reading the Declaration Piece by Piece

The declaration from the introduction does three things at once. It declares a variable of type Action named clearAction. It calls the AbstractAction constructor with the text Clear. And it opens a braced body that defines a subclass, which is what makes this an anonymous inner class. The source describes this pattern as the usual way to define actionPerformed, because it keeps the behavior right next to the place where the action is created.

Code walkthrough
Action clearAction = new AbstractAction("Clear") { ... };
  1. 1
    Line one creates a new object and stores a reference to it in clearAction.
  2. 2
    The text Clear is the action's name. It describes the action and is not code that runs when the button is pressed.
  3. 3
    The body between the braces defines actionPerformed. This is the only part of the declaration that does work when the action is performed.
  • The declared type is Action, so clearAction can be given to any component that accepts an Action.
  • The string Clear is passed to the AbstractAction constructor. It is a property of the action, not a behavior.
  • The braces open an anonymous subclass of AbstractAction. Everything inside the braces is part of that subclass.

Where a Click's Behavior Is Defined

AbstractAction is an abstract class that implements the Action interface. Because it implements Action, every AbstractAction object is also an Action, and that is the type a button needs in order to accept it. The class includes one method that matters most here: public void actionPerformed(ActionEvent evt). AbstractAction declares this method, but a concrete subclass must define it. In other words, AbstractAction supplies the structure, and the programmer supplies the behavior that runs when the action is performed.

Inside the braces, actionPerformed calls display.clear(). The source assumes display is an object with a clear() method. The behavior is therefore an ordinary method call, written inside the anonymous subclass. The button does not contain that call. A JButton can be created with a constructor that takes an Action object, so the button uses the behavior defined in that action rather than a separate listener attached afterward.

UserClear buttonJButtonclearActionAction objectdisplayhas clear()1. presses button2. performs action, runs actionPerformed3. calls clear()
When the user clicks the button, which object's method is called next? The button hands the event to the Action, and the Action's actionPerformed method does the work.

One Action Shared by a Button and a Menu Item

The source states that a JButton and a JMenuItem can both be constructed from an Action. The program below creates one Action object and hands that same object to a button and to a menu item. Read it focusing on which object is passed where. The two components are two presentations of the same Action.

java
Notes on the code
  • The same clearAction object is passed to both constructors.
  • The last line changes the enabled state through the Action alone. Neither component is named in that line.
clearActionAction object with nameClearclearButtonJButtonclearItemJMenuItempassed to constructorpassed to constructor
How do a JButton and a JMenuItem both end up pointing at the same Action object? Each component receives the same clearAction object through its constructor.

Disabling and Renaming One Action

Enabling, disabling, and renaming an Action update every component built from it. The enabled state belongs to the Action, so setting it to false changes every linked component. The name works the same way. The NAME property belongs to the Action, so changing it updates the text of every component built from that Action. The before and after versions below show only the rename, and the surrounding code is unchanged.

Before
clearAction.putValue(Action.NAME, "Clear");

Both the clearButton and the clearItem show the text Clear.

After
clearAction.putValue(Action.NAME, "Erase");

Both the clearButton and the clearItem show the text Erase, because neither component was changed directly.

clearActionenabled set to falseclearButtondisabledclearItemdisabledgoverns stategoverns state
When setEnabled(false) is called on the Action, what state does each linked button and menu item show afterward? Both show the disabled state, because both read it from the same Action.

What do you think happens?

After clearAction.setEnabled(false) runs, can the user still click clearButton?

  • Yes, the button stays clickable

  • No, the button is disabled because it reads the state from the Action

Reveal answer
Answer:

No, the button is disabled because it reads the state from the Action

The enabled state belongs to the Action. Disabling the Action updates every component built from it, including the button and the menu item, so you change the Action and not the components.

Why You Can Drop the Component Variables

Once the components are created, you may stop keeping variables that point to them. Any later change you want to make, such as enabling, disabling, or renaming, can be done on the Action object alone. The source states that you can do everything you need to do by manipulating the Action object. This means the references clearButton and clearItem are not needed for later changes.

Why a Lambda Will Not Work Here

Java programmers often reach for lambda expressions to shorten code, so it is natural to wonder whether the anonymous class above could become a lambda. The source addresses this directly: you cannot use a lambda expression here, because Action is not a functional interface. A lambda expression can only stand in for an interface that has a single abstract method, and Action does not qualify. The deeper reason is that the anonymous subclass does more than supply a method. It also passes the name Clear to the constructor, and a lambda has no way to supply the stored properties of an Action.

FeatureAnonymous AbstractAction subclassLambda expression
Stands in forAn Action object with stored propertiesA single abstract method of a functional interface
Can set the name ClearYes, through the constructorNo, nothing in a lambda supplies that property
Works as an Action hereYesNo, because Action is not a functional interface
Defines actionPerformedYes, as a method in the bracesOnly if the target interface is functional

Why the anonymous subclass is required for this Action

Mistakes When Creating Actions

  • Common mistake

    Looking for the button's behavior in the button's own code

    Why it fails

    The button does not contain the click behavior. It uses the Action it was given.

    Do this instead

    Find the actionPerformed method inside the Action's declaration.

  • Common mistake

    Changing the button and the menu item separately after they share an Action

    Why it fails

    Enabling, disabling, and renaming update every component built from the Action, so separate changes are unnecessary and can drift out of step.

    Do this instead

    Change the Action object once and let every linked component follow.

  • Common mistake

    Trying to replace the anonymous subclass with a lambda expression

    Why it fails

    Action is not a functional interface, and a lambda cannot supply the Action's name property.

    Do this instead

    Keep the anonymous subclass of AbstractAction with its actionPerformed method.

  • Common mistake

    Assuming the name text describes the behavior

    Why it fails

    The string passed to the constructor is a property such as the name. The code that runs on a press is inside actionPerformed.

    Do this instead

    Read the constructor argument as a label and the braces as the behavior.

Tracing a Shared Action in Practice

EASY

Suppose you have the Action clearAction from this article, and a JButton clearButton and a JMenuItem clearItem were both built from it. Then you call clearAction.setEnabled(false) and later call clearAction.putValue(Action.NAME, "Erase"). Predict the state of clearButton and clearItem after both calls. Then state which variables you needed in order to make both changes.

Hints
  • Ask which object holds the enabled state and the name.
  • Count how many component references you needed to touch in order to change the shared state.
  • The button and the menu item are two presentations of one Action.

What Changed About the Button

  1. 1
    An Action bundles properties such as a name, an icon, and an enabled status with the behavior that runs when the action is performed.
  2. 2
    AbstractAction implements Action and declares actionPerformed, and a concrete subclass must define it, usually as an anonymous inner class.
  3. 3
    A JButton or a JMenuItem can be constructed from the same Action, so one object drives several components.
  4. 4
    Enabling, disabling, and renaming the Action update every component built from it, so the component references are not needed for later changes.
  5. 5
    A lambda expression cannot replace the anonymous subclass here, because Action is not a functional interface.

Key Takeaways

  • An Action bundles descriptive properties, such as a name, an icon, and an enabled status, with the behavior that runs when it is performed.
  • AbstractAction implements Action and declares actionPerformed, and a concrete subclass must define that method, usually as an anonymous inner class.
  • One Action can be passed to the constructor of a JButton and a JMenuItem, so one object drives several components.
  • Disabling or renaming the Action updates every component built from it, so later changes can be made on the Action alone.
  • A lambda expression cannot replace the anonymous subclass, because Action is not a functional interface.