tags:

views:

599

answers:

7

Hello, I'm new to Java Programming but an experienced C++ programmer. I was learning how to program GUIs using swing. I was wondering how resource intensive (runtime as well as memory) are ActionListeners? Is there a general guideline to the total number of listeners that one should create in a particular program? How many until performance is affected?

I am currently learning Java through the Deitel Developer Series Java for Programmers book. In a particular example they have an array of JRadioButtonItems as a private variable for the class. They also had created an ItemHandler class that extended from the ActionListener class that conducted a linear search on the entire array of radio buttons in order to determine the one that was selected and changes the state of the program accordingly. All of the radio buttons in the array shared the same Action Listener. This seemed rather inefficient to conduct a linear search of information so I had rewritten the ActionListener class to take in the proposed value to modify in the constructor and gave each radio button its own ActionListener with the proposed value passed in by the constructor in order to avoid doing a linear search. Which method would be better performance-wise? I apologize for sounding like a noob, I am just trying to develop a good set of habits for programming in Java. Attached is a small example of the code. Thanks.

    /************************************************************************
    Original code in Deitel book with linear search of selected Radio button in Actionlistener
    ****************************************************************************/
    import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;


public class MenuTest extends JFrame{
    private final Color colorValues[] = {Color.BLACK, Color.WHITE, Color.GREEN};
    private JRadioButtonMenuItem colorItems[];  
    private ButtonGroup colorButtonGroup;


    public MenuTest(){
     super("Menu Test");
     JMenu fileMenu = new JMenu("File");

     JMenuBar bar = new JMenuBar();
     setJMenuBar(bar);
     bar.add(fileMenu);

     String colors[] = {"Black", "White", "Green"};
     JMenu colorMenu = new JMenu("Color");
     colorItems = new JRadioButtonMenuItem[colors.length];
     colorButtonGroup = new ButtonGroup();

     ItemHandler itemHandler = new ItemHandler();

     for(int count = 0; count < colors.length; count++){
      colorItems[count] = new JRadioButtonMenuItem(colors[count]);
      colorMenu.add(colorItems[count]);
      colorButtonGroup.add(colorItems[count]);
      colorItems[count].addActionListener(itemHandler);
     }

     colorItems[0].setSelected(true);
     fileMenu.add(colorMenu);
     fileMenu.addSeparator();

    }

    private class ItemHandler implements ActionListener{
     public void actionPerformed(ActionEvent event){
      for(int count = 0; count < colorItems.length; count++){
       if(colorItems[count].isSelected()){
        getContentPane().setBackground(colorValues[count]);
       }
      }
     }
    }


    public static void main(String args[]){
     MenuTest menuFrame = new MenuTest();
     menuFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     menuFrame.setSize(600,400);
     menuFrame.setVisible(true);
     menuFrame.getContentPane().setBackground(menuFrame.colorValues[0]);
    }
}
    /************************************************************************
    My Code redefined version of Deitel's w/o linear search in ActionListener
    ************************************************************************/

        import java.awt.Color;
        import java.awt.event.ActionEvent;
        import java.awt.event.ActionListener;

        import javax.swing.ButtonGroup;
        import javax.swing.JFrame;
        import javax.swing.JLabel;
        import javax.swing.JMenu;
        import javax.swing.JMenuBar;
        import javax.swing.JMenuItem;
        import javax.swing.JRadioButtonMenuItem;

        public class MenuTest extends JFrame{
        private final Color colorValues[] = {Color.BLACK, Color.WHITE, Color.GREEN};
        private JRadioButtonMenuItem colorItems[];  
        private ButtonGroup colorButtonGroup;


        public MenuTest(){
         super("Menu Test");
         JMenu fileMenu = new JMenu("File");

         JMenuBar bar = new JMenuBar();
         setJMenuBar(bar);
         bar.add(fileMenu);

         String colors[] = {"Black", "White", "Green"};
         JMenu colorMenu = new JMenu("Color");
         colorItems = new JRadioButtonMenuItem[colors.length];
         colorButtonGroup = new ButtonGroup();

         ItemHandler itemHandler = new ItemHandler();

         for(int count = 0; count < colors.length; count++){
          colorItems[count] = new JRadioButtonMenuItem(colors[count]);
          colorMenu.add(colorItems[count]);
          colorButtonGroup.add(colorItems[count]);
          colorItems[count].addActionListener(new ItemHandler(colorValues[count]));
         }

         colorItems[0].setSelected(true);
         fileMenu.add(colorMenu);
         fileMenu.addSeparator();

        }

        private class ItemHandler implements ActionListener{
         private Color setColor;
         public ItemHandler(Color inColor){
          super();
          setColor = inColor;
         }
         public void actionPerformed(ActionEvent event){
          getContentPane().setBackground(setColor);
          repaint();
         }
        }
        public static void main(String args[]){
         MenuTest menuFrame = new MenuTest();
         menuFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         menuFrame.setSize(600,400);
         menuFrame.setVisible(true);
         menuFrame.getContentPane().setBackground(menuFrame.colorValues[0]);
        }
    }
+11  A: 

CPU usage: close to none. Listeners are only called when the state of the object they listen to is changed. They don't really "listen". The object to which they are listening calls them when needed. (Note: this is a simplification)

Memory usage: Mostly an ActionListener has no state. So the total enduring memory usage is the minimum required for any object. In your example, there is state, as you have a setColor field. But memory usage is low.

IMO, listeners are efficient and you can use as many as you want.

Steve McLeod
+3  A: 

Listeners are very low cost compared to the Components they are attached to. Just having hundreds of listeners is unlikely to make much difference to CPU or memory. However, the total work listeners do is more important. If you see a performance issue, you can use a CPU/memory profiler to identify and optimise the cause. Its not something you should need to worry about too much upfront.

Peter Lawrey
+1  A: 

Keep in mind that listening is not the same as a busy wait. The listener is registered on a list of interested parties via proxy methods of some sort. There is very little wasted CPU time in most Listener Patterns.

ojblass
+1  A: 

There's a tradition in Java ME to use fewer classes, as the overhead of downloading another class file is significant, so you sometimes get Java code that tries and reduce the number of listener types, and use conditional logic in the listener. For desktop Java application size isn't really an issue, but you sometimes see it leaking into that space.

Not really in answer to the question, but once you've gone that far, you'll find you can reduce the scope of most the fields of MenuTest to be method variables in the constructor.

I tend to use anonymous listeners, as most of the time they are too simple to bother naming.

The code shown uses action listeners to detect whether the menu items are 'actioned'.

In the constructor it sets the selected menu item, then in the main method it accesses the colorValues field of the object, and sets the value of the background. Obviously this fails at both information hiding or encapsulating behaviour.

If instead you use ChangeListeners to check whether or not a menu item is set as selected, then you don't have two separate pieces of logic to maintain the same state as the color is set in response to the call to setSelected, and you don't have to leak the colorValues field to the caller.

for ( int count = 0; count < colors.length; count++ ){
    colorItems[count] = new JRadioButtonMenuItem(colors[count]);
    colorMenu.add(colorItems[count]);
    colorButtonGroup.add(colorItems[count]);

    final Color color = colorValues[count];

    colorItems[count].addChangeListener ( new ChangeListener() {
        public void stateChanged ( ChangeEvent event ){
            if ( ( ( AbstractButton ) event.getSource() ).isSelected () ) 
                getContentPane().setBackground ( color );
        }
    });
}

colorItems[0].setSelected(true);

// no call to setBackgroundColour() in main()

You can also have a second final variable to hold the colorItem and avoid the cast on event.getSource().

It would also be possible to implement the change listener to also listen for changes setting the background colour on the frame, and select the appropriate menu item if the colour is changed using setBackground().

Calling setBackground() automatically marks the panel as damaged, so there's no need to call repaint afterwards.

Pete Kirkham
A: 

Well, consider the most basic listener implementation: all you are doing is basically adding an object to a list of Listeners, nothing more. Registering a callback function, if you will. So the implementation of this line:

someObject.addListener(new someAnonymousListener{ ... });

essentially would be:

someListenerList.add(new someAnonymousListener{ ... });

Up until now, no harm no foul. The object is just sitting there in a list with all the other registered listeners.

However, notice what happens when an event is triggered on the object:

for (SomeListener l : someListenerList) {
    l.doSomeAction();
}

Yikes. This is where you should be careful. Make sure your listener is not doing anything too complex or you will be seeing a bottleneck, which will show up during profiling.

Bottom line: simply holding a listener on an object is almost free. Just make sure you are not registering too many listeners, and that each one of them is keeping it's actions simple and to the point.

Yuval A
You raise a good issue: listeners should generally do very little. If they need to do something complex, they should spawn a new thread, ideally using the SwingWorker class, then return immediately.
Steve McLeod
+3  A: 

The much bigger issue here is set up time, and to a lesser extent permanent generation memory.

For the actual object sizes you can do a back of an envelope impact analysis. About 20 bytes an object. Say you have 10,000 of them, that's 200K. On a 1GB machine that is 0.02% of your machines memory going on listeners - I wouldn't worry about that in a cell phone.

Te bigger picture is how many classes you load. My experiment (on Usenet somewhere) gave around 2K overhead for each relatively small anonymous inner class loaded. 10,000 of those is 20MB. 2% of our 1GB machine. I'd worry about that for widely distributed software, but not for a couple dozen machines in a medium size company or department. Even so, it's vacuously more important to get software that actually works and is maintainable (apparently, 85% of software development costs are in maintenance (for some definition) and most projects never get that far).

Getting the code loaded is a relatively expensive operation. Although fortunately we now have jars rather than opening a new socket to do an HTTP 1.0 request for each class file (applets were slow to start - how did that happen?). Still, strings have to be looked up, method resolved, bytecode verified, etc. And it's still being interpreted for 1,500 iterations before we incur the expense of the compiler.

Once you have working (it's not working until it's in production), maintainable software, then you might want to consider fine tuning performance (although, to be contradictory, it's good to have heads-up). Tune the slowest part. With processors in the GHz, responding to each little mouse click tends to go more than fast enough. Slow tends to be operations such as opening a new dialog box.

So, for us, we want to reduce set up time, but we can be quite inefficient at event fire time. 50ms is the response time we should aim at, and that on a 1GHz machine that is 20,000,000 cycles (lots!). So an appropriately performant strategy is to use few types of listener. Use a single listener type (possibly instance) to listen to a fair amount of model. Ignore the event arguments (usually a good idea for a state change listener) and run through checking what needs to be done (remember we have plenty of time).

I feel the need to repeat myself here. Make your code nice. Go for many input listeners (and commands). State change listeners should ignore their arguments.

Tom Hawtin - tackline
10,000 objects @ 20 bytes != 20K (should be 200K).
Kevin Brock
@Kevin Brock Erm, yes. D'oh. 0.02%.
Tom Hawtin - tackline
+1  A: 

I am a Swing Programmer since 5 years. As per my experience, it is never an issue how may listeners are there. It is important to deregister listeners when they are not longer interested in events.

just rememeber that in GUI applications, the response time is more importantant than memory. You may be interested in my articles:

Use Weak Listeners to avoid Memory Leaks
Know when your Object gets Garbage Collected
Long-Lived Models may cause Memory Leaks

Santhosh Kumar T