Java Tip 90: Accelerate your GUIs

Improve GUI performance using 'lazy' evaluation

Slow graphical user interfaces (GUIs) are a common complaint directed at Java. While constructing snappy GUIs can take time and effort, I'll present a class that can speed up GUIs with little extra work on your part. For serious GUI improvements, this is only the first step; however, it's an excellent one because it's simple. Sometimes this class improves performance enough without any additional work.

The approach

The approach is easily stated: delay building GUI components until necessary. On a macro scale, all Java programs already do this by default. Who, after all, builds all the possible frames and windows of a program while initializing? However, the class I present here allows you more fine-grained, lazy construction than that already offered by Java.

Three observations led to the utility class I built to support lazy construction of GUI components. First, most GUI panels are simple and initialize fast enough without any extra work.

Panels that cram a huge number of GUI components onto the screen are responsible for most slow initializations. These panels tend to be of two types, either tabbed panes or scroll panels.

Second, until a GUI component needs to be viewed, it usually doesn't need to be constructed. Third, GUI components cannot be seen until one of the following methods is called:

  • public void paint (Graphics)
  • public void paintComponents(Graphics)
  • public void paintAll (Graphics)
  • public void repaint ()
  • public void repaint (long)
  • public void repaint (int, int, int, int)
  • public void repaint (long, int, int, int, int)
  • public void update (Graphics)

The idea is to create a utility panel class that moves most of the GUI construction code out of the initializers and constructors. Instead, this code is placed in a function called lazyConstructor(). This function does most of the work ordinarily done by the GUI constructor, but is not itself a constructor. This utility panel class ensures that the lazyConstructor method is called once before any paint or update methods are called.

Using this panel class inside tab panels lets the tabbed panes construct faster because only one visible tab panel needs to be constructed initially.

The code

The code for this class looks like this:

import java.awt.*;

/** * LazyPanel is an abstract base class that provides functionality * to defer populating a Panel object until it is actually viewed. * This is extremely useful when using CardLayout and tab panel * views because it allows the construction of the subviews to * be done on a pay-as-you-go basis instead of absorbing all the cost * of construction up front. * * If subclasses choose to override any of the following methods, * it is their responsibility to ensure their overridden methods * call the parent's method

first

. The methods are: * * public void paint (Graphics) * public void paintComponents(Graphics) * public void paintAll (Graphics) * public void repaint () * public void repaint (long) * public void repaint (int, int, int, int) * public void repaint (long, int, int, int, int) * public void update (Graphics) * * Each of these methods ensures the panel is constructed * and then simply forwards the call to the parent class. * * You use this class by extending it and moving as much of the * constructor code as possible from the child class into the method * lazyConstructor. An example of using LazyPanel is: *

* * <PRE> * import java.awt.*; * * class BusyPanel extends LazyPanel * { * public BusyPanel (int rows, int cols) * { * this.rows = rows; * this.cols = cols; * } * * protected void lazyConstructor() * { * setLayout (new GridLayout (rows, cols)); * for (int i = 0; i < rows * cols; ++i) * { * add (new Button (Integer.toString (i + startValue))); * ++startValue; * } * }

* * static private int startValue = 0; * * private int rows; * private int cols; * } * </PRE> * You use it like this: * <PRE> * import java.awt.*; * import java.awt.event.*; * * class TestFrame extends Frame * { * public TestFrame () * {

* setLayout (new BorderLayout ()); * add (nextButton, "South"); * * framePanel.setLayout (layout); * for (int i = 0; i < 30; ++i) * framePanel.add (new BusyPanel (8, 8), ""); * add (framePanel, "Center"); * * nextButton.addActionListener ( * new ActionListener() * { * public void actionPerformed (ActionEvent event) * { * layout.next (framePanel); * } * } * ); * * setSize (400, 300); * } * * private CardLayout layout = new CardLayout(); * private Button nextButton = new Button ("Next Panel"); * private Panel framePanel = new Panel(); * * static public void main (String args[]) * { * (new TestFrame()).show(); * } * } * </PRE> * To see the advantage of using the LazyPanel, try moving the code * in the lazyConstructor() method into the constructor of BusyPanel, * recompile and rerun the example. The extra lag in startup time * is what the LazyPanel class is intended to remove. * * This works with swing, too. Just modify the LazyPanel to * extend JPanel instead of Panel. */ public abstract class LazyPanel extends Panel { // We want to call the lazyConstructor only once. private boolean lazyConstructorCalled = false;

// Some versions of Swing called paint() before // the components were added to their containers.

// We don't want to call lazyConstructor until // the components are actually visible. private boolean isConstructorFinished = false;

/** * Make a LazyPanel. */ protected LazyPanel () { isConstructorFinished = true; }

public void paint (Graphics g) { callLazyConstructor();

super.paint (g); }

public void paintAll(Graphics g) { callLazyConstructor();

super.paintAll (g); }

public void paintComponents (Graphics g) { callLazyConstructor();

super.paintComponents (g); }

public void repaint () { callLazyConstructor();

super.repaint(); }

public void repaint (long l) { callLazyConstructor();

super.repaint (l); }

public void repaint (int i1, int i2, int i3, int i4) { callLazyConstructor();

super.repaint (i1, i2, i3, i4); }

public void repaint (long l, int i1, int i2, int i3, int i4) { callLazyConstructor();

super.repaint (l, i1, i2, i3, i4); }

public void update (Graphics g) { callLazyConstructor();

super.update (g); }

/** * Force the lazyConstructor() method implemented in the child class * to be called. If this method is called more than once on * a given object, all calls but the first do nothing. */ public synchronized final void callLazyConstructor() { // The general idea below is as follows: // 1) See if this method has already been successfully called. // If so, return without doing anything. // // 2) Otherwise ... call the lazy constructor. // 3) Call validate so that any components added are visible. // 4) Note that we have run. if ((lazyConstructorCalled == false) && (getParent() != null)) { lazyConstructor(); lazyConstructorCalled = true; validate(); } }

/** * This method must be implemented by any child class. Most of * the component creation code that would have gone in the constructor * of the child goes here instead. See the

example

* at the top. */ abstract protected void lazyConstructor (); }

Using the LazyPanel

Using the LazyPanel class is straightforward, but there are a few things of which you should be aware.

Don't throw exceptions from lazyConstructor

For panels, it is easy enough to write constructors that throw exceptions if something goes wrong. Unfortunately, lazyConstructor is an ordinary method and not a constructor. Because lazyConstructor is called from paint(), it cannot throw anything except RuntimeExceptions and Errors. Since there isn't any caller other than the AWT thread to catch these exceptions and errors, it is best not to throw any exceptions at all. If necessary, place the body of your lazyConstructor method in a try/catch block and do something sensible in the catch block.

GUI builders will not cooperate

The code generated by the GUI builders in Inprise's JBuilder and Symantec's Cafe does not work well with the LazyPanel class. The GUI builders' generated code uses initializers to construct most of the GUI components for the panel. This eliminates the advantage of the LazyPanel, since none of the work is moved to the lazyConstructor() method.

One approach is to manually move the component construction from the initializers to the lazyConstructor method. Unfortunately, the GUI builder then can no longer modify the panel for further enhancements. A better approach is to keep the GUI-builder-generated panel unmodified and put it inside a LazyPanel. The LazyPanel then contains only one component -- the component built by the GUI builder.

Your code will not perform any faster in the long run

Finally, this approach doesn't actually speed up your application. If your GUI originally needed 1 MB of memory and took 30 seconds to create all the components, it will still need 1 MB of memory and will still take 30 seconds to fully construct. The LazyPanel spreads the cost of constructing the needed GUI components over time, so you pay the price in small chunks instead of requiring all the memory and time up front. LazyPanel does not, however, do anything to reduce that cost. Often this class will be good enough to accelerate your GUI. Other times, however, simply dropping in a LazyPanel or two will only begin your serious performance tuning.

Conclusion

Java GUIs with many components initialize more quickly by completing only the absolute minimum initialization necessary. The LazyPanel class lets you speed up the initialization of more complex GUIs with little extra programming. It is a good start for improving the speed of complex GUIs and often is sufficient for less complicated ones.

Mark Roulo is the Java Tip technical coordinator for JavaWorld. He has been programming professionally since 1989 and has been using Java since the alpha-3 release. He works full time at KLA-Tencor and is part of a team building a 500KLOC, distributed, parallel, multicomputer application for image processing (among other things) written almost entirely in Java.

Learn more about this topic