Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Designing object initialization

Ensure proper initialization of your objects at all times

  • Print
  • Feedback

Page 4 of 5

  • Default values are the values given to instance (and class) variables when they are first allocated on the heap -- before any initialization code is executed
  • A no-arg constructor is a constructor that takes no arguments
  • A default constructor is a no-arg constructor generated implicitly by the compiler for classes that don't have any constructors explicitly declared in the source file
  • An instance variable initializer is an equals sign (=) and expression sitting between an instance variable declaration and its terminating semicolon
  • An instance initializer is a block of code executed during object initialization in textual order along with instance variable initializers


For more information on these terms, see the companion article to this month's column, "Object initialization in Java."

Strategies for object initialization

When you design a class, you should attempt to ensure that all fields declared in the class are initialized to "proper" values, no matter how that object is created. Although Java's mechanisms for object initialization can help you achieve this goal, in the end, it's up to you to be sure to use the mechanisms correctly. By themselves, these mechanisms do not guarantee that classes you design will always be properly initialized.

Fortunately, as the designer of a class, you get to decide what initial values are deemed proper. If you decide that the default values for each instance variable declared in a class are proper, you needn't have any initializers or constructors at all. The important point is that the object begin its life with an internal state that yields proper behavior from then on. As mentioned previously, this is not only a goal of initialization but also of each state transformation the object can go through during its lifetime. If all your instance variables are private, only the methods of your class can transform the state of the object. By proper design of initializers, constructors, and methods of your class, you can ensure that an object of that class will always have a proper internal state -- from the beginning of its lifetime to the end.

Four approaches to initializing an instance variable

In the case of an instance variable for which the default value is not a proper initial state, you can take one of four approaches to initialization:

  1. Always assign it the same proper initial state via an initializer or constructor
  2. Calculate a proper initial state from data passed to a constructor
  3. Do either of the above, depending on which constructor is used to create the object
  4. Don't assign it a proper initial state and declare the object "invalid"


The first approach
By example:

// In Source Packet in ex2/CoffeeCup.java
// Approach 1
class CoffeeCup {
    private int innerCoffee = 355;
    // no constructors defined in this version of CoffeeCup
    //...
}


In this example, innerCoffee is always initialized to 355, so CoffeeCup objects will always begin life with 355 milliliters of coffee in them.

The second approach
Alternatively, you could require that client programmers using the CoffeeCup class pass in an initial starting value for innerCoffee:

// In Source Packet in ex3/CoffeeCup.java
// Approach 2
class CoffeeCup {
    private int innerCoffee;
    // Only one constructor defined in this
    // version of CoffeeCup
    public CoffeeCup(int startingAmount) {
        if (startingAmount < 0) {
            String s = "Can't have negative coffee.";
            throw new IllegalArgumentException(s);
        }
        innerCoffee = startingAmount;
    }
    //...
}


In this example, class CoffeeCup declares only one constructor, which takes an int parameter. Because a constructor is explicitly declared in CoffeeCup, the compiler won't generate a default constructor. Therefore, CoffeeCup does not have a no-arg constructor. As a result, client programmers who want to create a CoffeeCup object are forced to use the constructor that requires an int. They must supply an initial value for innerCoffee.

Checking for invalid data passed to constructors
As soon as you allow client programmers to pass data to constructors -- data which you use to calculate initial starting values for instance variables -- you have to deal with the possibility that a client programmer will pass an invalid parameter value. You should generally check for invalid parameter values passed to constructors. If an invalid parameter is passed, you should most likely throw an exception. In the above example, CoffeeCup's constructor throws an IllegalArgumentException, which is an exception defined in the java.lang package. Another alternative upon detecting invalid parameter data is not to use the invalid parameter data in establishing the initial state of the object:

// In Source Packet in ex4/CoffeeCup.java
// Also Approach 2, but a less desirable way to
// handle invalid parameters
class CoffeeCup {
    private int innerCoffee;
    // Only one constructor defined in this
    // version of CoffeeCup
    public CoffeeCup(int startingAmount) {
        if (startingAmount < 0) {
            innerCoffee = 0;
        }
        else {
            innerCoffee = startingAmount;
        }
    }
    //...
}


As in the previous example, the startingAmount parameter to this constructor is only used if it is greater than zero; however, in this example the constructor doesn't throw an exception when it discovers that an invalid startingAmount has been passed to it. Instead, it just sets innerCoffee to zero. In general, this way of dealing with invalid parameter data passed to a constructor is less desirable than throwing an exception, because the behavior of the constructor is more mysterious to client programmers. A constructor that either uses passed data or throws an exception is easier to understand than one that only uses passed data some of the time. The less a client programmer has to know about the internal implementation of a constructor, the easier it is for the client programmer to understand how to use that constructor.

The third approach
A third approach, and one that probably makes the most sense for class CoffeeCup, is to give client programmers a choice between specifying an initial amount of coffee or using a default:

// In Source Packet in ex5/CoffeeCup.java
// Approach 3
class CoffeeCup {
    private int innerCoffee;
    // Only two constructors defined in this
    // version of CoffeeCup
    public CoffeeCup() {
    }
    public CoffeeCup(int startingAmount) {
        if (startingAmount < 0) {
            String s = "Can't have negative coffee.";
            throw new IllegalArgumentException(s);
        }
        innerCoffee = startingAmount;
    }
    //...
}


In this version of CoffeeCup, the no-arg constructor has no statements because the default value of innerCoffee, zero, is a natural default amount of coffee in a cup. With this CoffeeCup class, a client programmer could either create an empty CoffeeCup using the no-arg constructor or a CoffeeCup filled with a specified amount of coffee using the constructor that takes an int parameter:

// In Source Packet in ex5/Example1.java
class Example1 {
    public static void main(String[] args) {
        // Create an empty coffee cup.
        CoffeeCup cup1 = new CoffeeCup();
        // Create a coffee cup filled with 355 ml coffee.
        CoffeeCup cup2 = new CoffeeCup(355);
    }
}


The third approach seems like the best way to design class CoffeeCup, because there is a "natural" value for innerCoffee, zero, which represents an empty cup. For some instance variables, however, there may not be any natural initial value. Given that kind of instance variable, the best approach is usually the second.

Instance variables with no natural initial value
An example of an instance variable with no natural initial value is one representing the size of a cup. If you decide to sell distinct sizes of coffee product in your virtual café -- short (8 ounce), tall (12 ounce), and grande (16 ounce) -- you will need to model this in your solution. Rather than modeling this with three different types, such as ShortCoffeeCup, TallCoffeeCup, and GrandeCoffeeCup, you may decide to model it as an attribute of a generic CoffeeCup class. To do so, you could add a private instance variable, size, to class CoffeeCup and three public constants that define the range of values for the size field:

// In Source Packet in ex6/CoffeeCup.java
class CoffeeCup {
    public static final int SHORT = 0;
    public static final int TALL = 1;
    public static final int GRANDE = 2;
    private int size;
    //...
}


When someone walks into your virtual café and orders a coffee drink without specifying one of the three sizes, the likely scenario is that you'll ask them what size they want. (You ask customers explicitly what they want because there is no default size for a coffee cup. If you guess, giving a "default-size" cup to those who don't voluntarily reveal a preferred size, you'll probably have many unhappy customers, who will say, "Oh, but I wanted a smaller size. Is it okay if I just pay for the smaller size?")

Analogously, in your solution domain, you might require that client programmers tell you what size CoffeeCup they want each time they create a CoffeeCup object. To do so, you would initialize size using the second approach from the list. Here's how you would enhance the CoffeeCup class to include a size:

// In Source Packet in ex7/CoffeeCup.java
// This class uses approach 2 for size
// and approach 3 for innerCoffee.
class CoffeeCup {
    public static final int SHORT = 0;
    public static final int TALL = 1;
    public static final int GRANDE = 2;
    public static final int MAX_SHORT_ML = 237;
    public static final int MAX_TALL_ML = 355;
    public static final int MAX_GRANDE_ML = 473;
    private int size;
    private int innerCoffee;
    // Only two constructors defined in this
    // version of CoffeeCup
    public CoffeeCup(int size) {
        this(size, 0);
    }
    public CoffeeCup(int size, int startingAmount) {
        if ((size != SHORT) && (size != TALL)
            && (size != GRANDE)) {
            String s = "Invalid cup size.";
            throw new IllegalArgumentException(s);
        }
        if (startingAmount < 0) {
            String s = "Can't have negative coffee.";
            throw new IllegalArgumentException(s);
        }
        if (startingAmount > getMaxAmount(size)) {
            String s = "Too much coffee.";
            throw new IllegalArgumentException(s);
        }
        this.size = size;
        innerCoffee = startingAmount;
    }
    public int add(int amount) {
        innerCoffee += amount;
        int max = getMaxAmount(size);
        int spillAmount = 0;
        if (innerCoffee > max) {
            spillAmount = innerCoffee - max;
            innerCoffee = max;
        }
        return spillAmount;
    }
    private static int getMaxAmount(int size) {
        int retVal = 0;
        switch (size) {
        case SHORT:
            retVal = MAX_SHORT_ML;
            break;
        case TALL:
            retVal = MAX_TALL_ML;
            break;
        case GRANDE:
            retVal = MAX_GRANDE_ML;
            break;
        default:
            String s = "Invalid cup size.";
            throw new IllegalArgumentException();
        }
        return retVal;
    }
    //...
}


Given this version of CoffeeCup, client programmers could create a new CoffeeCup object using either of two constructors, but in both cases, they would have to indicate a desired cup size:

  • Print
  • Feedback

Resources
  • For the source code listings that appear in this article, see http://www.javaworld.com/jw-03-1998/techniques/techniques.zip
  • Recommended Books on Java Design http://www.artima.com/designtechniques/booklist.html
  • Object Orientation FAQ http://www.cyberdyne-object-sys.com/oofaq/
  • 7237 Links on Object-Orientation http://www.rhein-neckar.de/~cetus/software.html
  • The Object-Oriented Page http://www.well.com/user/ritchie/oo.html
  • Collection of Information on OO Approach http://arkhp1.kek.jp:80/managers/computing/activities/OO_CollectInfor/OO_CollectInfo.html
  • Design Patterns Home Page http://hillside.net/patterns/patterns.html
  • A Comparison of OOA and OOD Methods http://www.iconcomp.com/papers/comp/comp_1.html
  • Object-Oriented Analysis and Design Methodsa Comparative Review http://wwwis.cs.utwente.nl:8080/dmrg/OODOC/oodoc/oo.html
  • Patterns-Discussion FAQ http://gee.cs.oswego.edu/dl/pd-FAQ/pd-FAQ.html
  • Implementing Basic Design Patterns in Java (Doug Lea) http://g.oswego.edu/dl/pats/ifc.html
  • Patterns in Java AWT http://mordor.cs.hut.fi/tik-76.278/group6/awtpat.html
  • Software Technology's Design Patterns Page http://www.sw-technologies.com/dpattern/
  • Synchronization of Java Threads Using Rendezvous http://www-cad.eecs.berkeley.edu/~jimy/classes/rendezvous/
  • Design PatternsElements of Reusable Object-Oriented Software, In Java http://www.zeh.com/local/jfd/dp/design_patterns.html :END_RESOURCES