Lazy initialization

Lazy initialization is a performance optimization. It's used when data is deemed to be 'expensive' for some reason. For example: Lazy initialization has two objectives:

As usual, the size of any performance gain, if any, is highly dependent on the problem, and in many cases may not be significant. As with any optimization, this technique should be used only if there is a clear and significant benefit.

To avoid a NullPointerException, a class must self-encapsulate fields that have lazy initialization. That is, a class cannot refer directly to such fields, but must access them through a method.

The hashCode method of an immutable Model Object is a common candidate for lazy initialization.

Example 1

In this example, there are two fields with lazy initialization - fHashCode and fAwards.

import java.util.*;

public final class Athlete {

  public Athlete(Integer id){
    //a toy implementation:
    this.id = id;
    this.name = "Roger Bannister";
    //this.awards is not set here!
  }

  //..elided

  /**
   Lazy initialization is used here; this assumes that awards
   may not always be of interest to the caller,
   and that for some reason it is particularly expensive to
   fetch the List of Awards.
  */
  public List<String> getAwards(){
    if (this.awards == null) {
      //the awards field has not yet been populated
      //Here is a toy implementation
      List<String> awards = new ArrayList<>();
      awards.add("Gold Medal 2006");
      awards.add("Bronze Medal 1998");
      this.awards = awards;
    }
    return Collections.unmodifiableList(this.awards);
  }

  /**
   This style applies only if the object is immutable.
   
   Another alternative is to calculate the hashCode once, when the 
   object is initially constructed (again, applies only when object is 
   immutable).
  */
  @Override public int hashCode(){
    if (hashCode == 0) {
      hashCode = Objects.hash(getSigFields());
    }
    return hashCode;
  }
  
  @Override public boolean equals(Object aThat) {
    if (this == aThat) return true;
    if (!(aThat instanceof Athlete)) return false;
    Athlete that = (Athlete)aThat;
    for(int i = 0; i < this.getSigFields().length; ++i){
      if (!Objects.equals(this.getSigFields()[i], that.getSigFields()[i])){
        return false;
      }
    }
    return true;    
  }

  // PRIVATE
  private Integer id;
  private String name;
  private List<String> awards;
  private int hashCode;
  
  private Object[] getSigFields() {
    //self-encapusulated: awards is not referenced directly, 
    //since it may be null:
    return new Object[] {id, name, getAwards()};
  }
} 

Example 2

Here, the look up of the printers available to a desktop PC is treated as an expensive operation.

import java.util.Arrays;
import java.util.List;

import javax.print.DocFlavor;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.Sides;

/** Printing services available to a desktop client. */
public final class Printers {

  /** Print some plain text (perhaps internally converted to PDF). */
  void printSomething(String text, PrintService printer) {
    //...elided
  }

  /** Return the list of printers that can print PDFs (double-sided, portrait).*/
  List<PrintService> listAvailablePrinters(){
    if(availablePrinters == null){
      //double-sided, portrait, for PDF files.
      PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
      attrs.add(Sides.DUPLEX);
      attrs.add(OrientationRequested.PORTRAIT);
      //Expensive operation! This can take several seconds in some environments:
      availablePrinters = Arrays.asList(
        PrintServiceLookup.lookupPrintServices(DocFlavor.INPUT_STREAM.PDF, attrs)
      );
    }
    return availablePrinters;
  }
  
  // PRIVATE
  
  /**
   Looked up once, the first time it's needed, and then stored using a 
   static reference. If it was a non-static reference, then 
   the list of available printers would not be looked up just once, 
   but perhaps many times (once per 'Printers' object, and not once per 
   loaded 'Printers' class).
   
   Self-encapsulate:
   If this class's implementation needs to reference this item, it must do 
   so indirectly, by calling listAvailablePrinters().  
  */
  private static List<PrintService> availablePrinters;
  
}  

Example 3

Lazy initialization is particularly useful for GUIs which take a long time to construct.

There are several policies for GUI construction which a design may follow:

Here's an example of the first-request style, in which the fEditor field has lazy initialization (see the actionPerformed method).
package hirondelle.stocks.preferences;

import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.util.logging.*;

import hirondelle.stocks.util.Args;
import hirondelle.stocks.util.ui.StandardEditor;
import hirondelle.stocks.util.ui.UiUtil;
import hirondelle.stocks.preferences.PreferencesEditor;
import hirondelle.stocks.util.Util;

/**
* Present dialog to allow update of user preferences.
*
* <P>Related preferences are grouped together and placed in 
* a single pane of a <tt>JTabbedPane</tt>, which corresponds to an 
* implementation of {@link PreferencesEditor}. Values are pre-populated with 
* current values for preferences.
*
*<P>Most preferences have default values. If so, a  
* <tt>Restore Defaults</tt> button is provided for that set of related 
* preferences.
*
*<P>Preferences are not changed until the <tt>OK</tt> button is pressed. 
* Exception: the logging preferences take effect immediately, without the need 
* for hitting <tt>OK</tt>.
*/
public final class EditUserPreferencesAction extends AbstractAction {

  /**
  * Constructor.
  *  
  * @param aFrame parent window to which this dialog is attached.
  * @param aPrefEditors contains implementations of {@link PreferencesEditor}, 
  * each of which is placed in a pane of a <tt>JTabbedPane</tt>.
  */
  public EditUserPreferencesAction (JFrame aFrame, List<PreferencesEditor> aPrefEditors) {
    super("Preferences...", UiUtil.getEmptyIcon()); 
    Args.checkForNull(aFrame);
    Args.checkForNull(aPrefEditors);
    fFrame = aFrame;
    putValue(SHORT_DESCRIPTION, "Update user preferences");
    putValue(LONG_DESCRIPTION, "Allows user input of preferences.");
    putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_P));    
    fPrefEditors = aPrefEditors;
  }

  /** Display the user preferences dialog.  */
  @Override public void actionPerformed(ActionEvent event) {
    fLogger.info("Showing user preferences dialog.");
    //lazy construction: fEditor is created only once, when this action 
    //is explicitly invoked
    if (fEditor == null) {
      fEditor = new Editor("Edit Preferences", fFrame);
    }
    fEditor.showDialog();
  }
  
  // PRIVATE 
  private JFrame fFrame;
  private java.util.List<PreferencesEditor> fPrefEditors;
  private static final Logger fLogger = Util.getLogger(EditUserPreferencesAction.class);  
  
  /**
  * Specifying this as a field allows for "lazy" creation and use of the GUI, which is 
  * of particular importance for a preferences dialog, since they are usually heavyweight, 
  * and have a large number of components.
  */
  private Editor fEditor;
  
  /**
  * Return GUI for editing all preferences, pre-populated with current 
  * values.
  */
  private JComponent getPrefEditors(){
    JTabbedPane content = new JTabbedPane();
    content.setTabPlacement(JTabbedPane.LEFT);
    int idx = 0;
    for(PreferencesEditor prefEditor: fPrefEditors) {
      JComponent editorGui = prefEditor.getUI();
      editorGui.setBorder(UiUtil.getStandardBorder());
      content.addTab(prefEditor.getTitle() , editorGui);
      content.setMnemonicAt(idx, prefEditor.getMnemonic());
      ++idx;
    }
    return content;
  }
  
  /** Called only when the user hits the OK button.  */
  private void saveSettings(){
    fLogger.fine("User selected OK. Updating table preferences.");
    for(PreferencesEditor prefEditor: fPrefEditors) {
      prefEditor.savePreferences();
    }
  }
  
  /**
  * An example of a nested class which is nested because it is attached only 
  * to the enclosing class, and it cannot act as superclass since multiple 
  * inheritance of implementation is not possible. 
  * 
  * The implementation of this nested class is kept short by calling methods 
  * of the enclosing class.
  */
  private final class Editor extends StandardEditor { 
    Editor(String aTitle, JFrame aParent){
      super(aTitle, aParent, StandardEditor.CloseAction.HIDE);
    }
    @Override public JComponent getEditorUI () {
      JPanel content = new JPanel();
      content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
      content.add(getPrefEditors());
      //content.setMinimumSize(new Dimension(300,300));
      return content;
    }
    @Override public void okAction() {
      saveSettings();
      dispose();
    }
  }  
}
 

See Also :
Constructors in general
Implementing hashCode
Self encapsulate fields
Standardized dialogs