The Artima Developer Community
Sponsored Link

Objects and Java by Bill Venners
Chapter 4:
Initialization and Cleanup

Advertisement

Variables have lifetimes. The lifetime of an instance variable matches that of the object to which it belongs. The lifetime of a class variable matches that of the class to which it belongs. The lifetime of a local variable is from the point it is created to the point where it goes out of scope. The Java language and virtual machine have mechanisms to ensure each of these kinds of variables are initialized before they are used.

As discussed in Chapter 3, the Java compiler and Java virtual machine make sure local variables are explicitly initialized before they are used.

[bv: talk briefly about the lifetime of a class here]

At the beginning of an object's life, the Java virtual machine (JVM) allocates enough memory on the heap to accommodate the object's instance variables. When that memory is first allocated, however, the data it contains is unpredictable. If the memory were used as is, the behavior of the object would also be unpredictable. To guard against such a scenario, Java makes certain that memory is initialized, at least to predictable default values, before it is used by any code.

Initialization is important because, historically, uninitialized data has been a common source of bugs. Bugs caused by uninitialized data occur regularly in C, for example, because it doesn't have built-in mechanisms to enforce proper initialization of data. C programmers must always remember to initialize data after they allocate it and before they use it. The Java language, by contrast, has built-in mechanisms that help you ensure proper initialization of the memory occupied by a newly-created object. With proper use of these mechanisms, you can prevent an object of your design from ever being created with an invalid initial state.

Object Initialization Mechanisms

The Java language has three mechanisms dedicated to ensuring proper initialization of objects:

Instance variable initializers and instance initializers may together be called simply "initializers." All three mechanisms result in Java code that is executed automatically when an object is created. When you allocate memory for a new object with the new operator, the Java virtual machine will insure that initialization code is run before you can use the newly-allocated memory. If you design your classes such that initializers and constructors always produce a valid state for newly-created objects, there will be no way for anyone to create and use an object that isn't properly initialized.

Default initial values

Up to this point, the examples used in this book have done no explicit initialization of instance variables. This is perfectly legal in Java, and results in predictable default initial values, which are based only upon the type of the variable. Table 4-1 shows the default initial values for each of the variable types. (These are the default initial values for both instance and class variables. Initialization of class variables will be discussed in depth in Chapter 6.)

Type Default Value
boolean false
byte (byte) 0
short (short) 0
int 0
long 0L
char \u0000
float 0.0f
double 0.0d
object reference null

Table 4-1. Default values for fields

If you don't explicitly initialize an instance variable, then that variable will still have its default initial value when new returns its reference. For example, in all the versions of class CoffeeCup prior to this chapter, the innerCoffee field was not explicitly initialized (The class contains no constructors or initializers.):

// This class has no constructors or initializers
public class CoffeeCup {

    private int innerCoffee;

    // The rest of the class...
}
As a result, when the reference to a new CoffeeCup object is first returned by new, the innerCoffee field would be its default initial value. Because innerCoffee is an int, its default initial value is zero.

Note that this means that if you explicitly initialize innerCoffee, say to a value of 100, then when each CoffeeCup object is created, innerCoffee will, in effect, be initialized twice. First, innerCoffee will be given its default initial value of zero. Later, the zero will be overwritten with the proper initial value of 100. All of this takes place while the Java virtual machine is creating the new object -- before it returns the reference to the new object. By the time the reference to a new CoffeeCup object is returned from the new operator, the innerCoffee field will be set to 100.

As mentioned in Chapter 2, local variables do not participate in the default initial values that instance variables (and, as you will see in Chapter 6, class variables) are guaranteed to receive. The value of a local variable is undefined until you explicitly initialize it.

Constructors

The central player in object initialization is the constructor. In Java, constructors are similar to methods, but they are not methods. Like a method, a constructor has a set of parameters and a body of code. Unlike methods, however, constructors have no return type. Like methods, you can give access specifiers to constructors, but unlike methods, constructors with public, protected, or package access are not inherited by subclasses. (Also, instead of determining the ability to invoke a method, the access level of a constructor determines the ability to instantiate an object.)

Constructor Basics

In the source file, a constructor looks like a method declaration in which the method has the same name as the class but has no return type. For example, here is a constructor declaration for class CoffeeCup:

// In source packet in file init/ex2/CoffeeCup.java
class CoffeeCup {
    // Constructor looks like a method declaration
    // minus the return type
    public CoffeeCup() {
        // Body of constructor
    }
    // ...
}

As with methods, you can overload constructors by varying the number, types, and order of parameters. Here is a class CoffeeCup with two constructors:

// In source packet in file init/ex3/CoffeeCup.java
class CoffeeCup {

    private int innerCoffee;

    public CoffeeCup() {
        innerCoffee = 237;
    }

    public CoffeeCup(int amount) {
        innerCoffee = amount;
    }
    // ...
}

When you instantiate an object with new, you must specify a constructor. For example, given the CoffeeCup class above that has two constructors, you could instantiate it in either of these two ways:

// In source packet in file init/ex3/Example3.java
class Example3 {
    public static void main(String[] args) {

        // Create an empty cup
        CoffeeCup cup1 = new CoffeeCup();

        // Create a cup with 355 ml of coffee in it
        CoffeeCup cup2 = new CoffeeCup(355);
    }
}

The No-Arg Constructor

In Java jargon, constructors that take no parameters (or no arguments) are called "no-arg constructors." In the code shown above, the first instantiation of a CoffeeCup object specifies the no-arg constructor. The second instantiation specifies the constructor that requires an int as its only parameter.

The this() invocation

From within a constructor, you can explicitly invoke another constructor from the same class by using the this() statement. You may want to do this if you have several overloaded constructors in a class, all of which must execute much of the same code. Here's an example:

// In source packet in file init/ex4/CoffeeCup.java
class CoffeeCup {

    private int innerCoffee;

    public CoffeeCup() {
        this(237); // Calls other constructor
        // Could have done more construction here
    }

    public CoffeeCup(int amount) {
        innerCoffee = amount;
    }
    // ...
}

In this example, the no-arg constructor invokes the constructor that takes an int as its only parameter. It passes 237 to the other constructor, which assigns that value to innerCoffee.

You cannot call this() from methods, only from constructors. If you do call this()in a constructor, you must call it first, before any other code in the constructor, and you can only call it once. Any code you include after the call to this() will be executed after the invoked constructor completes.

Constructors Are Not Methods

To further illustrate the difference between methods and constructors, consider this fact: The name of a class is a valid name for its methods. In other words, class CoffeeCup could have methods named CoffeeCup:

// In source packet in file init/ex5/CoffeeCup.java
// THIS WORKS, BUT IT IS AN EXAMPLE OF POOR METHOD NAMING
class CoffeeCup {

    private int innerCoffee;

    public CoffeeCup() {	// The constructor
        innerCoffee = 237;
    }

    public void CoffeeCup() {	// The method
        innerCoffee = 99;
    }
    // ...
}

Given the above definition of class CoffeeCup, you could legally do the following:

// In source packet in file init/ex5/Example5.java
class Example5 {
    public static void main(String[] args) {

        CoffeeCup cup = new CoffeeCup(); // invoke the constructor
        cup.CoffeeCup(); // invoke the method
    }
}

Although it is legal to give a method the same name as a class, in practice you should never do so, in part because other programmers might confuse it with a constructor, but also because it breaks many of the rules for good method naming. First, a class name is not a verb; it's a noun (at least it should be a noun). Method names should be verbs. You should name methods after the action they perform, and "CoffeeCup" is not an action. Also, "CoffeeCup" doesn't follow the naming convention for methods, in which the first letter is lowercase. The purpose of this example is merely to highlight the fact that constructors aren't methods by showing that a constructor does not conflict with a method that has the same signature. Java's recommended naming conventions are described later in this chapter, in the Design Corner section.

Default constructors

If you declare a class with no constructors, the compiler will generate a constructor for you. Such automatically-generated constructors, which are called default constructors, take no parameters (they are no-arg constructors) and have empty bodies. Because the compiler will automatically generate a default constructor if you don't declare any constructors explicitly, all classes are guaranteed to have at least one constructor.

For example, if you declare a CoffeeCup class without declaring a constructor explicitly:

// In source packet in file init/ex6/CoffeeCup.java
class CoffeeCup {

    private int innerCoffee;

    public void add(int amount) {
        innerCoffee += amount;
    }
    //...
}

The compiler will generate the same class file as if you had explicitly declared a no-arg constructor with an empty body:

// In source packet in file init/ex7/CoffeeCup.java
class CoffeeCup {

    private int innerCoffee;

    public CoffeeCup() {
    }

    public void add(int amount) {
        innerCoffee += amount;
    }
    //...
}

Instance Initialization Methods

When you compile a class, the Java compiler creates an instance initialization method for each constructor you declare in the source code of the class. Although the constructor is not a method, the instance initialization method is. It has a name, <init>, a return type, void, and a set of parameters that match the parameters of the constructor from which it was generated. For example, given the following two constructors in the source file for class CoffeeCup:

// In source packet in file init/ex8/CoffeeCup.java
class CoffeeCup {
    public CoffeeCup() {
        //...
    }
    public CoffeeCup(int amount) {
        //...
    }
    // ...
}

the compiler would generate the following two instance initialization methods in the class file for class CoffeeCup, one for each constructor in the source file:

// In binary form in file init/ex8/CoffeeCup.class:
public void <init>(CoffeeCup this) {...}
public void <init>(CoffeeCup this, int amount) {...}

Note that <init> is not a valid Java method name, so you could not define a method in your source file that accidentally conflicted with an instance initialization method. (To be precise, <init> is not a method in the Java language sense of the term, because it has an illegal name. In the compiled, binary Java class file, however, it is a valid method.)

Also, the this reference passed as the first parameter to <init> is inserted by the Java compiler into the parameter list of every instance method. For example, the method void add(int amount) in the source file for class CoffeeCup would become the void add(CoffeeCup this, int amount) method in the class file. The hidden this reference is the way in which instance methods, including instance initialization methods, are able to access instance data.

If you don't explicitly declare a constructor in a class, the Java compiler will create a default constructor on the fly, then translate that default constructor into a corresponding instance initialization method. Thus, every class will have at least one instance initialization method.

When the compiler generates an instance initialization method, it bases it on a constructor. It gives the method the same parameter list as the constructor, and it puts the code contained in the constructor's body into the method's body. But the instance initialization method does not necessarily represent a mere compilation of the constructor with the name changed to <init> and a return value of void added. Often, the code of an instance initialization method does more than the code defined in the body of its corresponding constructor. The compiler also potentially adds code for any initializers and an invocation of the superclass's constructor.

Initializers

Besides constructors, Java offers one other way for you to assign an initial value to instance variables: initializers. As mentioned previously, the two kinds of initializers in Java are instance variable initializers and instance initializers.

Instance variable initializers

In a constructor, you have the freedom to write as much code as needed to calculate an initial value. In an instance variable initializer, by contrast, you have only an equals sign and an expression. The left-hand side of the equals sign is the instance variable being initialized. The right-hand side of the equals sign can be any expression that evaluates to the type of the instance variable.

For example, if you wanted to always start coffee cups out with 355 milliliters of fresh brewed coffee in them, you could initialize innerCoffee with a constructor:

// In source packet in file init/ex9/CoffeeCup.java
class CoffeeCup {

    private int innerCoffee;

    public CoffeeCup() {
        innerCoffee = 355;
    }
    // ...
}

Alternatively, you could initialize innerCoffee with an instance variable initializer:

// In source packet in file init/ex10/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee = 355; // "= 355" is an initializer
    // no constructor here
    // ...
}

Instance Initializers

Java 1.1 introduced instance initializers. Instance initializers, which may also be called instance initialization blocks, are blocks of code (marked by open and close curly braces) that sit in the body of a class, but outside the body of any method declared in that class.

For example, here is the same CoffeeCup class with its innerCoffee variable initialized by an instance initializer:

// In source packet in file init/ex19/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee;

    // The following block is an instance initializer
    {
        innerCoffee = 355;
    }
    // no constructor here
    // ...
}

This manner of initializing innerCoffee yields the same result as the previous two examples: innerCoffee is initialized to 355.

Instance initializers are a useful alternative to instance variable initializers whenever: (1) initializer code must catch exceptions (described in Chapter 13), or (2) perform fancy calculations that can't be expressed with an instance variable initializer. You could, of course, always write such code in constructors. But in a class that had multiple constructors, you would have to repeat the code in each constructor. With an instance initializer, you can just write the code once, and it will be executed no matter what constructor is used to create the object. Instance initializers are also useful in anonymous inner classes (described in Chapter 11), which can't declare any constructors at all.

Note that the code inside an instance initializer may not return. In addition, instance initializers have special rules regarding exceptions. Information about these special rules is given in Chapter 13.

Initializers Can't Make Forward References

When you write an initializer (either an instance variable initializer or instance initializer), you must be sure not to refer to any instance variables declared textually after the variable being initialized. In other words, you can't make a forward reference from an initializer. (A forward reference is simply a use of a variable declared textually after the current statement in the source file.) If you disobey this rule, the compiler will give you an error message and refuse to generate a class file.

When an object is created, initializers are executed in textual order -- their order of appearance in the source code. The forward-referencing rule helps prevent initializers from using instance variables that have yet to be properly initialized.

For example, here is a virtual cafe class that has four chairs for every table:

// In source packet in file init/ex11/VirtualCafe.java
class VirtualCafe {
    private int tablesCount = 20;
    private int chairsCount = 4 * tablesCount;
    //...
}

These initializers work fine. The chairsCount initializer, = 4 * tablesCount, refers to an instance variable declared textually before it, so the compiler is happy. Because initializers are executed in textual order, tablesCount is already initialized to 20 by the time chairsCount's initializer multiplies it by four. Thus, chairsCount is initialized to 80.

If you were able to use instance variables declared textually later, you could end up with unexpected behavior:

// In source packet in file init/ex12/VirtualCafe.java
// THIS WON'T COMPILE, BUT AS A THOUGHT EXPERIMENT,
// IMAGINE IT WERE POSSIBLE
class VirtualCafe {
    private int chairsCount = 4 * tablesCount;
    private int tablesCount = 20;
    //...
}

If the above declaration were possible, chairsCount's initializer would use tablesCount before tablesCount were assigned a value of 20. At that point, the tablesCount variable would have its default initial value of zero. Hence, this code would initialize chairsCount to four times zero. If you do the math, you will discover that, in this case, chairsCount does not get initialized to 80.

Getting around the forward reference rule

Although this kind of forward referencing is disallowed by the compiler in an attempt to help programmers avoid just the above kind of mistake, you can't let down your guard completely. There is still a way you could inadvertently (or purposefully) circumvent the compiler's preventative restrictions:

// In source packet in file init/ex13/VirtualCafe.java
class VirtualCafe {

    private int chairsCount = initChairsCount();
    private int tablesCount = 20;

    private int initChairsCount() {
        return tablesCount * 4;
    }
    //...
}

The above code compiles fine, and has the same result as the previous thought experiment. Here chairsCount's initializer sneakily invokes a method that uses tablesCount before its initializer has been executed. When initChairsCount() calculates tablesCount * 4, tablesCount is still at its default initial value of zero. As a result, initChairsCount() returns zero, and chairsCount is initialized to zero.

Other Ways to Instantiate Objects

Besides the new operator, Java provides three other ways to instantiate objects: These other ways of creating objects are described in later chapters. Cloning is described in Chapter 14; deserialization in Chapter 19; and newInstance() in Chapter 23.

More Object Initialization to Come

Although the information here may seem like a lot, there is still more to object initialization, in particular, the way in which initialization interacts with inheritence. Information about initialization and inheritance will be given in Chapter 7.

Design Corner

Behind the Scenes

[bv: perhaps show that diagram of relationship between language, class file, and virtual machine.] The <init> method is not actually part of the Java language. Rather, it is something the Java virtual machine expects to see in a Java class file. This distinction is significant because the Java language does not depend on the class file. Java source can be compiled into other binary formats, including native executables. A Java compiler that translates Java language source into some other binary format need not generate a method named <init>, so long as objects are initialized in the proper way at the proper time. The Java Language Specification (JLS) details the order of initialization and when it occurs, but doesn't say how it is actually accomplished. Still, understanding how initialization works inside class files can help you understand the order of initialization in the language.

Class Initialization

Variables have lifetimes. The lifetime of an instance variable matches that of the object to which it belongs. The lifetime of a class variable matches that of the class to which it belongs. The lifetime of a local variable is from the point it is created to the point where it goes out of scope. The Java language and virtual machine have mechanisms to ensure each of these kinds of variables are initialized before they are used.

[bv: talk briefly about the lifetime of a class here] [bv:Clean up this class init stuff. It is too redundant with object init stuff and a bit too cutseypie with suger packets.]

Introduce class variable initializers and static initialization blocks.

Default Initial Values

Class variables get initialized to the same default initial values as instance variables. These default initial values are shown in Table 4-1.

Class Variable Initializers

public class SugarHolder {
      private int sugarPacketCount; // instance variable
      private static int sugarPacketsInCafeCount; // class variable
}

Class SugarHolder contains one instance variable and one class variable. In this example, the SugarHolder class maintains a class variable, sugarPacketsInCafeCount, to track the total number of packets of sugar sitting in sugar holders throughout the cafe. The instance variable, sugarPacketCount, just tracks the number of packets of sugar in an individual sugar holder.

In the example above, sugarPacketsInCafeCount will have an initial value of zero, because zero is the default initial value for ints. If you wanted a different initial value for sugarPacketsInCafeCount, you would have to explicitly initialize it to the other value. For example, imagine you are simulating a cafe that always starts out with five sugar holders, each of which contains five sugar packets. You could easily initialize the sugarPacketsInCafeCount to 25 using a class variable initializer. You write a class variable initializer with an equals sign and an expression, as shown below:

class SugarHolder {
      private int sugarPacketCount;
      private static int sugarPacketsInCafeCount = 25;
}

In the above example, sugarPacketsInCafeCount will be set to 25 before any use of the class. The class variable initializer is " = 25".

The Java compiler enforces the same forward referencing rule on class variable initializers. Class variable initializers may refer to any class variable that appears textually before it in the class, but not after. This rule aims to prevent situations in which a class variable is used before it is initialized.

Some examples of static initialization blocks are shown below. In these examples the SugarHolder class from an earlier example has been enhanced to include class variables that track the total number of particular types of sugar packets in the cafe. This cafe offers four type of sugar packets, each of which comes in a different color package. The white packet contains plain old sugar. The brown packet contains unbleached brown granules of sugar. The pink and blue packets contain two different brands of sugar substitutes. The first example shows an unruly class initializer that refers to class variables that appear textually after it. Because of this one class variable initializer's disregard for the rules, the entire class will be rejected by the compiler.

class SugarHolder {
      private int sugarPacketCount;

      // This won't compile because the following class
      // variable initializer refers to class variables
      // that appear textually after it.
      private static int sugarPacketsInCafeCount =
            whitePacketsInCafeCount + brownPacketsInCafeCount
            + pinkPacketsInCafeCount + bluePacketsInCafeCount;
      private static int whitePacketsInCafeCount = 10;
      private static int brownPacketsInCafeCount = 5;
      private static int pinkPacketsInCafeCount = 5;
      private static int bluePacketsInCafeCount = 5;
}

The following example rectifies the problem by simply moving the declaration of sugarPacketsInCafeCount such that it appears textually after the declarations of the class variables it uses.

class SugarHolder {
      private int sugarPacketCount;

      private static int whitePacketsInCafeCount = 10;
      private static int brownPacketsInCafeCount = 5;
      private static int pinkPacketsInCafeCount = 5;
      private static int bluePacketsInCafeCount = 5;
      // This class will compile because the following class
      // variable initializer only refers to other class
      // variables that appear textually before it.
      private static int sugarPacketsInCafeCount =
            whitePacketsInCafeCount + brownPacketsInCafeCount
            + pinkPacketsInCafeCount + bluePacketsInCafeCount;
}

Static Initialization Blocks

If you need to initialize a class variable in a manner too complex to express with an equals sign and expression, you can write a static initialization block. A static initialization block is a code block preceded by the word static, as in:

class SugarHolder {
	// ...
	static {
		// code for the static initialization
		// goes here
	}
	// ...

For a more concrete example, suppose instead of starting over with a fresh cafe each time you run your simulation program, you want to continue the state of the previous cafe. In this case you could write a static initialization block such as the one shown below:

// Defined in file SugarHolder.java
public class SugarHolder {
      private int sugarPacketCount;
      private static int sugarPacketsInCafeCount;
      static {
            // Initialize the total number of sugar packets with
            // state data left over from previous execution of
            // the simulation program.
            int sugarHolderCount =
                  PersistentStorage.getSugarHolderCount();
            for (int i = 0; i < sugarHolderCount; ++i) {
                 sugarPacketsInCafeCount +=
                       PersistentStorage.getSugarPacketCount(i);
            }
      }
}
// Defined in file PersistentStorage.java:
public class PersistentStorage {
	public int getSugarPacketCount(int sugarHolderIndex) {
		// For now return something that looks good. Later,
		// actually set up persistent storage
		return 23;
	}
	// ...
}

In the above example, the code in the static initialization block will be executed before the class is used, resulting in a sugarPacketsInCafeCount that is initialized to a value retrieved from persistent storage.

You can include in a class as many class variable initializers as there are class variables, and as many static initialization blocks as you wish. You can place static initialization blocks anywhere in your class. You could, for example, write one static initialization block that initializes several class variables. Alternatively, you could write a separate static initialization block for each class variable. You could even write several static initialization blocks for a single class variable, although in this case your peers might think you're a bit odd. The point here is that there is no one-to-one correspondence between class variables and static initialization blocks. You can, in fact, create static initialization blocks in classes that don't have any class variables.

Static initialization blocks have full privileges of regular static methods, with one exception. A static initialization block may refer to any class variable that appears textually before it in the class, but not after. The Java compiler will balk if one of your static initialization blocks uses or assigns a class variable the appears textually after it, even though such variables are in scope.

The next example includes a static initialization block with a healthy disrespect for authority. Like the irreverent class variable initializer from a previous example, this static initialization block accesses class variables that appear textually after it. In this case, authority will win again, because the class will not compile.

// Defined in file SugarHolder.java:
// This class won't compile, because the static initialization block
// refers to class variables that appear textually after it.
class SugarHolder {
      private int sugarPacketCount;
      private static int sugarPacketsInCafeCount;
      static {
            // Initialize the total number of sugar packets with
            // state data left over from previous execution of
            // the simulation program.
            int sugarHolderCount =
                  PersistentStorage.getSugarHolderCount();
            for (int i = 0; i < sugarHolderCount; ++i) {
                 sugarPacketsInCafeCount +=
                       PersistentStorage.getSugarPacketCount(i);
                 whitePacketsInCafeCount +=
                       PersistentStorage.getWhitePacketCount(i);
                 brownPacketsInCafeCount +=
                       PersistentStorage.getBrownPacketCount(i);
                 pinkPacketsInCafeCount +=
                       PersistentStorage.getPinkPacketCount(i);
                 bluePacketsInCafeCount +=
                       PersistentStorage.getBluePacketCount(i);
            }
      }
      private static int whitePacketsInCafeCount;
      private static int brownPacketsInCafeCount;
      private static int pinkPacketsInCafeCount;
      private static int bluePacketsInCafeCount;
}
// Defined in file PersistentStorage.java:
public class PersistentStorage {
	public int getSugarPacketCount(int sugarHolderIndex) {
		return 23;
	}
	public int getWhitePacketCount(int sugarHolderIndex) {
		return 6;
	}
	public int getBrownPacketCount(int sugarHolderIndex) {
		return 7;
	}
	public int getPinkPacketCount(int sugarHolderIndex) {
		return 5;
	}
	public int getBluePacketCount(int sugarHolderIndex) {
		return 5;
	}
	// ...
}

One solution to the problem in the previous example is to simply move the colored packet class variables above the static initialization block, as shown below:

// This class compiles happily.
class SugarHolder {
      private int sugarPacketCount;
      private static int sugarPacketsInCafeCount;
      private static int whitePacketsInCafeCount;
      private static int brownPacketsInCafeCount;
      private static int pinkPacketsInCafeCount;
      private static int bluePacketsInCafeCount;
      static {
            // Initialize the total number of sugar packets with
            // state data left over from previous execution of
            // the simulation program.
            int sugarHolderCount =
                  PersistentStorage.getSugarHolderCount();
            for (int i = 0; i < sugarHolderCount; ++i) {
                 sugarPacketsInCafeCount +=
                       PersistentStorage.getSugarPacketCount(i);
                 whitePacketsInCafeCount +=
                       PersistentStorage.getWhitePacketCount(i);
                 brownPacketsInCafeCount +=
                       PersistentStorage.getBrownPacketCount(i);
                 pinkPacketsInCafeCount +=
                       PersistentStorage.getPinkPacketCount(i);
                 bluePacketsInCafeCount +=
                       PersistentStorage.getBluePacketCount(i);
            }
      }
}

Another solution is to move the problem-causing portion of the static initialization block code to another static initialization block that appears textually after the colored packet class variables, as in:

// This class also compiles happily.
class SugarHolder {
      private int sugarPacketCount;
      private static int sugarPacketsInCafeCount;
      static {
            // Initialize the total number of sugar packets with
            // state data left over from previous execution of
            // the simulation program.
            int sugarHolderCount =
                  PersistentStorage.getSugarHolderCount();
            for (int i = 0; i < sugarHolderCount; ++i) {
                 sugarPacketsInCafeCount +=
                       PersistentStorage.getSugarPacketCount(i);
            }
      }
      private static int whitePacketsInCafeCount;
      private static int brownPacketsInCafeCount;
      private static int pinkPacketsInCafeCount;
      private static int bluePacketsInCafeCount;
      static {
            // Initialize the total number of colored packets with
            // state data left over from previous execution of
            // the simulation program.
            int sugarHolderCount =
                  PersistentStorage.getSugarHolderCount();
            for (int i = 0; i < sugarHolderCount; ++i) {
                 whitePacketsInCafeCount +=
                       PersistentStorage.getWhitePacketCount(i);
                 brownPacketsInCafeCount +=
                       PersistentStorage.getBrownPacketCount(i);
                 pinkPacketsInCafeCount +=
                       PersistentStorage.getPinkPacketCount(i);
                 bluePacketsInCafeCount +=
                       PersistentStorage.getBluePacketCount(i);
            }
      }
}

[bv: mention things get initialized in textual order?]

As an example of a static initialization block in a class that contains no class variables, consider the StaticGreeting class below. From outward appearances, this class behaves like a typical "Hello World" program with a coffee-oriented message. This program, however, manages to print its greeting before the main method is ever invoked. Even though the class does not contain any class variables, the static intialization block will be rolled into a <clinit> method. The Java Virtual Machine will execute the <clinit> method when the class is initialized, before main is invoked, and the <clinit> method will print out the greeting.

class StaticGreeting {
      public static void main(String args[]) {
      }
      static {
            System.out.println("Wake up and smell the coffee!");
      }
}

When executed, the above program prints out:

Wake up and smell the coffee!

The example above points out that code within a static initialization block is not limited to just initializing class variables, but can do anything a regular static method can do, so long as it doesn't access class variables that appear textually after it. Yet as a design guideline, you should try to keep initializers just focused on initialization.

Be Careful

Code within static initalizers can call other static methods in the class, can instantiate an object of its own type and call instance methods on the object, can call methods in other classes and pass the object to those methods, all before the class has been completely initialized. This means that you have the power to write static initialization blocks such that a class is used before it is fully initialized. As a result some class variables could be used while they still have their default initial values, rather than their proper initial values. For example, the following program demonstrates one way a class can be used before it is initialized:

class StaticGreeting2 {
      public static void main(String args[]) {
      }
      static {
            printGreeting();
      }
	String s = "Wake up and smell the coffee!";
      static void printGreeting() {
            System.out.println(s);
      }
}

When executed, the above program prints out the following decidedly feeble greeting:

null

In the previous example, the static initialization block invoked static method printGreeting before String s was initialized with a cheery greeting. In effect, the static initialization block used a loophole in the law against referring to class variables that appear textually later. It invoked a method that refers to the class variable. In so doing, the class variable is used while it still had its default initial value of null. When designing static initialization blocks, you should attempt to ensure that class variables are not used before they are fully initialized.

Static init blocks can call static methods. So methods can run even when things haven't had their initializers run. This is true for instance fields too. BUT, because they always have their default value, at least things are predictable.

[bv: refer to exceptions chapter, as in obj init chapter.] Static init blocks can't result in checked exceptions being thrown. They can invoke methods that may throw checked exceptions, but they must catch them. Similar is true for class var initializers.

The Class Initialization Method

All the class variable initializers and static initialization blocks of a class are collected by the Java compiler and placed into one special method, the class initialization method. This method is named "<clinit>". Static initialization blocks and class variable initializers are executed in textual order, because the code of the <clinit> method implements the class variable initializers and static initialization blocks in the order in which they appear in the class declaration. If a class has no class variable initializers or static initialization blocks, it will have no <clinit> method. The Java Virtual Machine invokes the <clinit> method, if it exists, once for each class. Regular methods cannot invoke a <clinit> method, because <clinit> is not a valid method name in Java. During a class's preparation phase, the class variables are set to default values. During the initialization phase, the Java Virtual Machine executes the <clinit> method, which sets class variables to their proper initial values. [bv: mention name spaces]

When Do Class Variables Get Initialized?

The Java Virtual Machine initializes classes and interfaces on their first active use. An active use is:

[bv: perhaps give a nice example of this.]

Special Treatment for Primitive Constants

The Java compiler resolves references to fields that are both static and final, and initialized by a compile-time constant expression, to a copy of the constant value. An example of two constant fields are shown below as part of class CoffeeCup. The maxCapacity field is initialized by a compile- time constant, but the currentCoffeeAmount field is not. References to the maxCapacity field are compiled to refer directly to a constant pool entry with the constant value 250. The maxCapacity field is not initialized by the <clinit> method. However, because the constant value of currentCoffeeAmount is not known at compile time, it must be initialized by <clinit>. The Java Virtual Machine will execute the <clinit> method during the initialization phase of the class, and the <clinit> method will invoke Math.random, multiply the result by 250, and assign currentCoffeeAmount its initial value, which will thereafter remain constant.

class CoffeeCup {
	static final int maxCapacity = 250; // milliliters
	static final int currentCoffeeAmount =
            (int) (Math.random() * 250d);
}

The use or assignment of a static, final field is not an active use of a class, because such fields are not initialized via the <clinit> method. A constant field is a class variable that is declared final as well as static, and is initialized with a compile-time constant expression. Such fields are resolved to constant expressions To initialize a class, the Java Virtual Machine executes the class's <clinit> method, if it has one. A class's superclass must be initialized before the class is initialized, so the Java Virtual Machine will invoke the <clinit> methods of the class's superclass if it hasn't yet been invoked. The code of a <clinit> method does not explicitly invoke a superclass's <clinit> method, therefore the Java Virtual Machine must make certain the <clinit> methods of superclasses have been executed before it invokes the <clinit> method of a class.

To initialize an interface, the Java Virtual Machine executes the interface's <clinit> method, if it has one, just as it does for classes. An interface may have a <clinit> method because interfaces can contain constants. In interfaces, constants are public, final, static variables. The variables must have an initialization expression, which is the interface version of a class variable initializer. Interfaces cannot have static initialization blocks. As with classes, only initializers that require evaluation of expressions that are not compile-time constants will end up as code in the <clinit> method. Therefore, if an interface has at least one constant that is initialized by an expression that is not a compile time constant, that interface will have a <clinit> method. Initialization of an interface does not require prior initialization of its superinterfaces.

[SHOW THAT THESE ARE PUT INTO SEPARATE CLASS FILES AND THAT THEY ARE USED BY THE CASE STATEMENTS OF SWITCHES, AND TO DO CONDITIONAL COMPILATION.]

The End of an Object's Life

This chapter shows you how to ensure proper cleanup of objects at the end of their lives. It discusses garbage collection, finalizers, and cleanup routines to release non-memory resources.

Here, "end of an object's lifetime" does not mean when an object is garbage collected, but when your program stops using it. If your object's validity depends upon its exclusive hold on a non-memory resource, such as a file handle, your object should release that resource not upon garbage collection, but upon

Class FileOutputStream is the perfect example of a cleanup method (close()) and the double checking done by a finalizer.

[MENTION freeMemory(), and totalMemory() in Runtime and runFinalization(), gc() in both System and Runtime.]

[A] Cleanup

Cleanup is about releasing resources at the end of an object's life.

I. Cleanup is important because objects use finite resources which must be released back to the system at the end of an object's life. Certainly memory, possibly other resources. Java has a garbage collector to clean up memory. You must write Java code to clean up any non-memory resource, including possibly a special method called a finalizer which is automatically run by the garbage collector when the object is reclaimed.

[B] The Garbage Collector

I. Paint a general picture of the garbage collector

II. Explain when finalize() is called by the garbage collector. Perhaps paint a picture of the garbage collection session.

III. Objects may never get garbage collected. A lot of times garbage collection happens only when the system runs out of memory. If it doesn't ever run out of memory, it doesn't ever garbage collect.

IV. You can give the gc a hint that this would be a good time to run.

V. Finalizers may or may not be run on exit, depending upon the runFinalizersOnExit() parameter of System? Did this get into Java 1.1?

VI. Describe the process of first pass, second pass.

[B] Finalizers

I. Finalizers can help if you allocate memory in native methods.

II. Could use finalizers to release memory created in some other way than by calling new. (For example, in native methods.) "Finalizers are about memory." That's all they should be worrying about.

III. Finalizers can use try/catch to handle any exceptions thrown during their execution. Any uncaught exceptions thrown by the finalizer itself are ignored by the JVM.

IV. Don't resurrect objects. Clone() an existing one if you have to.

V. Finalizer() is called only once by the garbage collector. So if you resurrect it, and then it gets garbage collected again, finalizer() won't get called the second time.

VI. The possibility of object resurrection shows that the gc must do another marking process after the finalizers have been run.

VII. Always invoke super.finalize() at the end of your finalizer().

[B] Why use a finalizer?

1) Not very many compelling reasons. Most of the time, don't.

2) Give an example of using the finalizer to double check on a sloppy programmer who didn't call close() or cleanup(). (Talk about just opening the file when you need it, using it, then closing it all in one method. In other words, the file handle is not part of the object's long term state. In this case, you don't have to worry about close(), cleanup(), or finalize(). But if it takes up too much time to do all that openning and closing...)

3) One of the few things finalizers are good for is to keep track of data about the garbage collector's performance. Another is for freeing memory that was allocated via native methods. Even this, however, should be able to fit into a cleanUp() method.

4) Even though Object() has a protected finalize(), that doesn't mean the the Java Virtual Machine will always call it. Java Virtual Machine implementations should be smart about figuring out whether you actually override it or even do something useful in the overridden guy.

[B] Strategies for ensuring the release of non-memory resources Clean up: Another area in which the garbage collector will influence your designs and implementations is in object clean up. In C++ there is a destructor that does the opposite job of the constructor. The destructor's primary use is to explicitly free any memory pointed to by data members of the object. There is, of course, no need to do this in Java, because the garbage collector takes care of freeing memory. However, C++ destructors were also used to release any non-memory resources, such as file handles or graphic display contexts, that may have been used by the object. It is important to release resources such as these, because there is a finite number of them available to the program. If you do not release these kinds of resources when you are done with them, your program may run out of them. In Java, such resources are not released by the garbage collector, so the programmer must release them explicitly. Because Java objects have no destructors, the design challenge becomes one of making sure non-memory resources will be released when an object doesn't need them anymore.

A straightforward solution is to design objects that don't hang onto a resource for their entire lifetimes. Instead, aim for objects that grab a resource only when it is needed during a method invocation, and then release the resource before returning from the method. Objects designed in this way don't need cleanup. However, this won't work in all cases. So, when you have an object that requires a release of non- memory resources upon the object's "death," you should define a method that performs this service, and invoke it on your object when your program no longer needs the object. This method will end up being invoked where you would have explicitly deallocated an object in a non-garbage-collected language such as C++. So it resurrects that old problem of figuring out when an object is no longer needed. You'll probably want to write this function to recognize that it has been accidentally called twice and either throw an exception or ignore the second call. Also, you could even put a check in the finalizer of the class to make sure the cleanup routine has been called. If it hasn't been called, the finalizer could call it.

Finalizers: This brings us to finalizers. You may be wondering, why can't a finalizer be used for clean up? Isn't it kind of like a destructor? It turns out that often the best finalizer is no finalizer.

Finalizers are guaranteed to be called before the memory used by an object is reclaimed. However, there is no guarantee that any object's memory will ever be reclaimed. Hence, there is no guarantee that a finalizer will ever be called. (There is a promise that, barring catastrophic error conditions, all finalizers will be run on any left-over objects when the Java Virtual Machine exits; However, this likely too late if your program is waiting for a file handle to be released. (Besides, I'm not convinced it happens anyway.)) Therefore, it is vital that you never rely on a finalizer to free finite resources, such as file handles, that may be needed later by your program.

You might be tempted to use the finalizer as your clean up routine, given that it is perfectly legal in Java to invoke a finalizer explicitly just like any other method. You could write the finalizer such that it does the clean up the first time it is invoked, and does nothing any subsequent time it is invoked. In this way it would serve as a fail safe in case you ever forget to invoke it explicitly. However, this is not the best approach because finalizers require CPU time to run. (One finalizer that just checks an instance variable, discovers the method has already been finalized, and returns won't take too long. But if your program generates 1,000,000 of those objects, the small amount of time for each could add up to something significant.) Also, objects without finalizers are "easier" for the garbage collector to dispose of. The heap space for an object might get reclaimed sooner if that object doesn't have a finalizer. (Explain the process?) Therefore, it is usually better that clean up be done by a method that is not the finalizer, so that clean up is done in a program thread, not a garbage collector thread. In this manner you can retain control of precisely when the clean up happens, and ease the garbage collector's demands on CPU time.

[A] Memory Management

Stress: One last thing you need to keep in mind in your designs and implementations is the garbage collector's stress level. If your program is generating millions of little objects and discarding them shortly after their creation, you could be placing a heavy burden on the garbage collector, which could degrade the performance of your program. Because each Java Virtual Machine can implement its garbage collector differently, giving lots of work to the garbage collector can affect your program's performance differently on different platforms. One way to combat this is to redesign your program so it doesn't create millions of little objects with short lifetimes. If this is not possible, you may want to create a container class that allows you to reuse little objects that have come to the end of their short lifetimes. By reusing an object, rather than discarding it and allocating a new one, you reduce the garbage collector's work load. Garbage collection doesn't solve all memory management problems. You could still run out of memory, and you could still have a memory leak. Don't leave unused references to objects lying around. Null out the reference. It's OK to return something you allocated. A garbage-collected heap solves certain design dilemmas common in environments where programmers must do their own deallocation, such as returning objects created by a method. Returning objects: Having a garbage-collected heap allows you to treat objects in your Java programs a bit more like you treat them in the real world. In a language such as C or C++, where the programmer must explicitly deallocate memory, there is a pesky design issue whenever a function needs to return a pointer to some memory that it allocated. It is common among programmers to consider such a function unfriendly, because the function requires some other function to free the memory it allocated. Idealists pontificate about the virtuous program in which all functions, before returning, free any memory that they allocate. Indeed this is a worthy goal, but unfortunately not always practical. In Java, the issue goes away. If you find yourself in a similar situation in a Java program, just create the new object and return it. When the recipient of the object is done with it, the garbage collector will take care of freeing the memory. A good example is a method from which you want to return several ints. In Java, you can guiltlessly allocate an array of ints and return the array. When the program is done with the array object, the garbage collector will reclaim the memory. Can say: A Java object does not have a destructor, like C++ objects. Given the architecture of the JVM, Java object lives to not have clean, well-defined ends. Where a C++ object experiences a traumatic and sudden death, a Java object kind of fades away.

Why clean up?
Every object in a Java program uses computing resources that are finite. Most obviously, all objects use some memory to store their images on the heap. (This is true even for objects that declare no instance variables. Each object image must include some kind of pointer to class data, and can include other implementation-dependent information as well.) But objects may also use other finite resources besides memory. For example, some objects may use resources such as file handles, graphics contexts, sockets, and so on. When you design an object, you must make sure it eventually releases any finite resources it uses so the system won't run out of those resources.

Because Java is a garbage-collected language, releasing the memory associated with an object is easy. All you need to do is let go of all references to the object. Because you don't have to worry about explicitly freeing an object, as you must in languages such as C or C++, you needn't worry about corrupting memory by accidentally freeing the same object twice. You do, however, need to make sure you actually release all references to the object. If you don't, you can end up with a memory leak, just like the memory leaks you get in a C++ program when you forget to explicitly free objects. Nevertheless, so long as you release all references to an object, you needn't worry about explicitly "freeing" that memory.

Similarly, you needn't worry about explicitly freeing any constituent objects referenced by the instance variables of an object you no longer need. Releasing all references to the unneeded object will in effect invalidate any constituent object references contained in that object's instance variables. If the now-invalidated references were the only remaining references to those constituent objects, the constituent objects will also be available for garbage collection. Piece of cake, right?

The rules of garbage collection
Although garbage collection does indeed make memory management in Java a lot easier than it is in C or C++, you aren't able to completely forget about memory when you program in Java. To know when you may need to think about memory management in Java, you need to know a bit about the way garbage collection is treated in the Java specifications.

Garbage collection is not mandated
The first thing to know is that no matter how diligently you search through the Java Virtual Machine Specification (JVM Spec), you won't be able to find any sentence that commands, Every JVM must have a garbage collector. The Java Virtual Machine Specification gives VM designers a great deal of leeway in deciding how their implementations will manage memory, including deciding whether or not to even use garbage collection at all. Thus, it is possible that some JVMs (such as a bare-bones smart card JVM) may require that programs executed in each session "fit" in the available memory.

Of course, you can always run out of memory, even on a virtual memory system. The JVM Spec does not state how much memory will be available to a JVM. It just states that whenever a JVM does run out of memory, it should throw an OutOfMemoryError.

Nevertheless, to give Java applications the best chance of executing without running out of memory, most JVMs will use a garbage collector. The garbage collector reclaims the memory occupied by unreferenced objects on the heap, so that memory can be used again by new objects, and usually de-fragments the heap as the program runs.

Garbage collection algorithm is not defined
Another command you won't find in the JVM specification is All JVMs that use garbage collection must use the XXX algorithm. The designers of each JVM get to decide how garbage collection will work in their implementations. Garbage collection algorithm is one area in which JVM vendors can strive to make their implementation better than the competition's. This is significant for you as a Java programmer for the following reason:

Because you don't generally know how garbage collection will be performed inside a JVM, you don't know when any particular object will be garbage collected.

So what? you might ask. The reason you might care when an object is garbage collected has to do with finalizers. (A finalizer is defined as a regular Java instance method named finalize() that returns void and takes no arguments.) The Java specifications make the following promise about finalizers:

Before reclaiming the memory occupied by an object that has a finalizer, the garbage collector will invoke that object's finalizer.

Given that you don't know when objects will be garbage collected, but you do know that finalizable objects will be finalized as they are garbage collected, you can make the following grand deduction:

You don't know when objects will be finalized.

You should imprint this important fact on your brain and forever allow it to inform your Java object designs.

Finalizers to avoid
The central rule of thumb concerning finalizers is this:

Don't design your Java programs such that correctness depends upon "timely" finalization.

In other words, don't write programs that will break if certain objects aren't finalized by certain points in the life of the program's execution. If you write such a program, it may work on some implementations of the JVM but fail on others.

Don't rely on finalizers to release non-memory resources
An example of an object that breaks this rule is one that opens a file in its constructor and closes the file in its finalize() method. Although this design seems neat, tidy, and symmetrical, it potentially creates an insidious bug. A Java program generally will have only a finite number of file handles at its disposal. When all those handles are in use, the program won't be able to open any more files.

A Java program that makes use of such an object (one that opens a file in its constructor and closes it in its finalizer) may work fine on some JVM implementations. On such implementations, finalization would occur often enough to keep a sufficient number of file handles available at all times. But the same program may fail on a different JVM whose garbage collector doesn't finalize often enough to keep the program from running out of file handles. Or, what's even more insidious, the program may work on all JVM implementations now but fail in a mission-critical situation a few years (and release cycles) down the road.

Other finalizer rules of thumb
Two other decisions left to JVM designers are selecting the thread (or threads) that will execute the finalizers and the order in which finalizers will be run. Finalizers may be run in any order -- sequentially by a single thread or concurrently by multiple threads. If your program somehow depends for correctness on finalizers being run in a particular order, or by a particular thread, it may work on some JVM implementations but fail on others.

You should also keep in mind that Java considers an object to be finalized whether the finalize() method returns normally or completes abruptly by throwing an exception. Garbage collectors ignore any exceptions thrown by finalizers and in no way notify the rest of the application that an exception was thrown. If you need to ensure that a particular finalizer fully accomplishes a certain mission, you must write that finalizer so that it handles any exceptions that may arise before the finalizer completes its mission.

One more rule of thumb about finalizers concerns objects left on the heap at the end of the application's lifetime. By default, the garbage collector will not execute the finalizers of any objects left on the heap when the application exits. To change this default, you must invoke the runFinalizersOnExit() method of class Runtime or System, passing true as the single parameter. If your program contains objects whose finalizers must absolutely be invoked before the program exits, be sure to invoke runFinalizersOnExit() somewhere in your program.

So what are finalizers good for?
By now you may be getting the feeling that you don't have much use for finalizers. While it is likely that most of the classes you design won't include a finalizer, there are some reasons to use finalizers.

One reasonable, though rare, application for a finalizer is to free memory allocated by native methods. If an object invokes a native method that allocates memory (perhaps a C function that calls malloc()), that object's finalizer could invoke a native method that frees that memory (calls free()). In this situation, you would be using the finalizer to free up memory allocated on behalf of an object -- memory that will not be automatically reclaimed by the garbage collector.

Another, more common, use of finalizers is to provide a fallback mechanism for releasing non-memory finite resources such as file handles or sockets. As mentioned previously, you shouldn't rely on finalizers for releasing finite non-memory resources. Instead, you should provide a method that will release the resource. But you may also wish to include a finalizer that checks to make sure the resource has already been released, and if it hasn't, that goes ahead and releases it. Such a finalizer guards against (and hopefully will not encourage) sloppy use of your class. If a client programmer forgets to invoke the method you provided to release the resource, the finalizer will release the resource if the object is ever garbage collected. The finalize() method of the LogFileManager class, shown later in this article, is an example of this kind of finalizer.

Avoid finalizer abuse
The existence of finalization produces some interesting complications for JVMs and some interesting possibilities for Java programmers. For a discussion of the impact of finalizers on JVMs, see the sidebar, a short excerpt from chapter 9, "Garbage Collection," of my book, Inside the Java Virtual Machine.

What finalization grants to programmers is power over the life and death of objects. In short, it is possible and completely legal in Java to resurrect objects in finalizers -- to bring them back to life by making them referenced again. (One way a finalizer could accomplish this is by adding a reference to the object being finalized to a static linked list that is still "live.") Although such power may be tempting to exercise because it makes you feel important, the rule of thumb is to resist the temptation to use this power. In general, resurrecting objects in finalizers constitutes finalizer abuse.

The main justification for this rule is that any program that uses resurrection can be redesigned into an easier-to-understand program that doesn't use resurrection. A formal proof of this theorem is left as an exercise to the reader (I've always wanted to say that), but in an informal spirit, consider that object resurrection will be as random and unpredictable as object finalization. As such, a design that uses resurrection will be difficult to figure out by the next maintenance programmer who happens along -- who may not fully understand the idiosyncrasies of garbage collection in Java.

If you feel you simply must bring an object back to life, consider cloning a new copy of the object instead of resurrecting the same old object. The reasoning behind this piece of advice is that garbage collectors in the JVM invoke the finalize() method of an object only once. If that object is resurrected and becomes available for garbage collection a second time, the object's finalize() method will not be invoked again.

Managing non-memory resources
Because heap memory is automatically reclaimed by the garbage collector, the main thing you need to worry about when you design an object's end-of-lifetime behavior is to ensure that finite non-memory resources, such as file handles or sockets, are released. You can take any of three basic approaches when you design an object that needs to use a finite non-memory resource:

  1. Obtain and release the resource within each method that needs the resource
  2. Provide a method that obtains the resource and another that releases it
  3. Obtain the resource at creation time and provide a method that releases it

Approach 1: Obtain and release within each relevant method
As a general rule, the releasing of non-memory finite resources should be done as soon as possible after their use because the resources are, by definition, finite. If possible, you should try to obtain a resource, use it, then release it all within the method that needs the resource.

A log file class: An example of Approach 1
An example of a class where Approach 1 might make sense is a log file class. Such a class takes care of formatting and writing log messages to a file. The name of the log file is passed to the object as it is instantiated. To write a message to the log file, a client invokes a method in the log file class, passing the message as a String. Here's an example:

import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.IOException;

class LogFile {

    private String fileName;

    LogFile(String fileName) {
        this.fileName = fileName;
    }

    // The writeToFile() method will catch any IOException
    // so that clients aren't forced to catch IOException
    // everywhere they write to the log file.  For now,
    // just fail silently. In the future, could put
    // up an informative non-modal dialog box that indicates
    // a logging error occurred. - bv 4/15/98
    void writeToFile(String message) {

        FileOutputStream fos = null;
        PrintWriter pw = null;

        try {
            fos = new FileOutputStream(fileName, true);
            try {
                pw = new PrintWriter(fos, false);

                pw.println("------------------");
                pw.println(message);
                pw.println();
            }
            finally {
                if (pw != null) {
                    pw.close();
                }
            }
        }
        catch (IOException e) {
        }
        finally {
            if (fos != null) {
                try {
                    fos.close();
                }
                catch (IOException e) {
                }
            }
        }
    }
}

Class LogFile is a simple example of Approach 1. A more production-ready LogFile class might do things such as:

The main feature of this simple version of class LogFile is that it surrounds each log message with a series of dashes and a blank line.

Using finally to ensure resource release
Note that in the writeToFile() method, the releasing of the resource is done in finally clauses. This is to make sure the finite resource (file handle) is actually released no matter how the code is exited. If an IOException is thrown, the file will be closed.

Pros and cons of Approach 1
The approach to resource management taken by class LogFile (Approach 1 from the above list) helps make your class easy to use, because client programmers don't have to worry about explicitly obtaining or releasing the resource. In both Approach 2 and 3 from the list above client programmers must remember to explicitly invoke a method to release the resource. In addition -- and what can be far more difficult -- client programmers must figure out when their programs no longer need a resource.

A problem with Approach 1 is that obtaining and releasing the resource each time you need it may be too inefficient. Another problem is that, in some situations, you may need to hold onto the resource between invocations of methods that use the resource (such as writeToFile()), so no other object can have access to it. In such cases, one of the other two approaches is preferable.

Approach 2: Offer methods for obtaining and releasing resources
In Approach 2 from the list above, you provide one method for obtaining the resource and another method for releasing it. This approach enables the same class instance to obtain and release a resource multiple times. Here's an example:

import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.IOException;

class LogFileManager {

    private FileOutputStream fos;
    private PrintWriter pw;
    private boolean logFileOpen = false;

    LogFileManager() {
    }

    LogFileManager(String fileName) throws IOException {
        openLogFile(fileName);
    }

    void openLogFile(String fileName) throws IOException {
        if (!logFileOpen) {
            try {
                fos = new FileOutputStream(fileName, true);
                pw = new PrintWriter(fos, false);
                logFileOpen = true;
            }
            catch (IOException e) {
                if (pw != null) {
                    pw.close();
                    pw = null;
                }
                if (fos != null) {
                    fos.close();
                    fos = null;
                }
                throw e;
            }
        }
    }

    void closeLogFile() throws IOException {
        if (logFileOpen) {
            pw.close();
            pw = null;
            fos.close();
            fos = null;
            logFileOpen = false;
        }
    }

    boolean isOpen() {
        return logFileOpen;
    }

    void writeToFile(String message) throws IOException {

        pw.println("------------------");
        pw.println(message);
        pw.println();
    }

    protected void finalize() throws Throwable {
        if (logFileOpen) {
            try {
                closeLogFile();
            }
            finally {
                super.finalize();
            }
        }
    }
}

In this example, class LogFileManager declares methods openLogFile() and closeLogFile(). Given this design, you could write to multiple log files with one instance of this class. This design also allows a client to monopolize the resource for as long as it wants. A client can write several consecutive messages to the log file without fear that another thread or process will slip in any intervening messages. Once a client successfully opens a log file with openLogFile(), that log file belongs exclusively to that client until the client invokes closeLogFile().

Note that LogFileManager uses a finalizer as a fallback in case a client forgets to invoke closeLogFile(). As mentioned earlier in this article, this is one of the more common uses of finalizers.

Note also that after invoking closeLogFile(), LogFileManager's finalizer invokes super.finalize(). Invoking superclass finalizers is good practice in any finalizer, even in cases (such as this) where no superclass exists other than Object. The JVM does not automatically invoke superclass finalizers, so you must do so explicitly. If someone ever inserts a class that declares a finalizer between LogFileManager and Object in the inheritance hierarchy, the new object's finalizer will already be invoked by LogFileManager's existing finalizer.

Making super.finalize() the last action of a finalizer ensures that subclasses will be finalized before superclasses. Although in most cases the placement of super.finalize() won't matter, in some rare cases, a subclass finalizer may require that its superclass be as yet unfinalized. So, as a general rule of thumb, place super.finalize() last.

Approach 3: Claim resource on creation, offer method for release
In the last approach, Approach 3 from the above list, the object obtains the resource upon creation and declares a method that releases the resource. Here's an example:

import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.IOException;

class LogFileTransaction {

    private FileOutputStream fos;
    private PrintWriter pw;
    private boolean logFileOpen = false;

    LogFileTransaction(String fileName) throws IOException {
        try {
            fos = new FileOutputStream(fileName, true);
            pw = new PrintWriter(fos, false);
            logFileOpen = true;
        }
        catch (IOException e) {
            if (pw != null) {
                pw.close();
                pw = null;
            }
            if (fos != null) {
                fos.close();
                fos = null;
            }
            throw e;
        }
    }

    void closeLogFile() throws IOException {
        if (logFileOpen) {
            pw.close();
            pw = null;
            fos.close();
            fos = null;
            logFileOpen = false;
        }
    }

    boolean isOpen() {
        return logFileOpen;
    }

    void writeToFile(String message) throws IOException {

        pw.println("------------------");
        pw.println(message);
        pw.println();
    }

    protected void finalize() throws Throwable {
        if (logFileOpen) {
            try {
                closeLogFile();
            }
            finally {
                super.finalize();
            }
        }
    }
}

This class is called LogFileTransaction because every time a client wants to write a chunk of messages to the log file (and then let others use that log file), it must create a new LogFileTransaction. Thus, this class models one transaction between the client and the log file.

One interesting thing to note about Approach 3 is that this is the approach used by the FileOutputStream and PrintWriter classes used by all three example log file classes. In fact, if you look through the java.io package, you'll find that almost all of the java.io classes that deal with file handles use Approach 3. (The two exceptions are PipedReader and PipedWriter, which use Approach 2.)

Behind the Scenes

The <init> method is not actually part of the Java language. Rather, it is something the Java virtual machine expects to see in a Java class file. This distinction is significant because the Java language does not depend on the class file. Java source can be compiled into other binary formats, including native executables. A Java compiler that translates Java language source into some other binary format need not generate a method named <init>, so long as objects are initialized in the proper way at the proper time. The Java Language Specification (JLS) details the order of initialization and when it occurs, but doesn't say how it is actually accomplished. Still, understanding how initialization works inside class files can help you understand the order of initialization in the language.

Special Treatment for Primitive Constants

The Java compiler resolves references to fields that are both static and final, and initialized by a compile-time constant expression, to a copy of the constant value. An example of two constant fields are shown below as part of class CoffeeCup. The maxCapacity field is initialized by a compile- time constant, but the currentCoffeeAmount field is not. References to the maxCapacity field are compiled to refer directly to a constant pool entry with the constant value 250. The maxCapacity field is not initialized by the <clinit> method. However, because the constant value of currentCoffeeAmount is not known at compile time, it must be initialized by <clinit>. The Java Virtual Machine will execute the <clinit> method during the initialization phase of the class, and the <clinit> method will invoke Math.random, multiply the result by 250, and assign currentCoffeeAmount its initial value, which will thereafter remain constant.

class CoffeeCup {
	static final int maxCapacity = 250; // milliliters
	static final int currentCoffeeAmount =
            (int) (Math.random() * 250d);
}

The use or assignment of a static, final field is not an active use of a class, because such fields are not initialized via the <clinit> method. A constant field is a class variable that is declared final as well as static, and is initialized with a compile-time constant expression. Such fields are resolved to constant expressions To initialize a class, the Java Virtual Machine executes the class's <clinit> method, if it has one. A class's superclass must be initialized before the class is initialized, so the Java Virtual Machine will invoke the <clinit> methods of the class's superclass if it hasn't yet been invoked. The code of a <clinit> method does not explicitly invoke a superclass's <clinit> method, therefore the Java Virtual Machine must make certain the <clinit> methods of superclasses have been executed before it invokes the <clinit> method of a class.

To initialize an interface, the Java Virtual Machine executes the interface's <clinit> method, if it has one, just as it does for classes. An interface may have a <clinit> method because interfaces can contain constants. In interfaces, constants are public, final, static variables. The variables must have an initialization expression, which is the interface version of a class variable initializer. Interfaces cannot have static initialization blocks. As with classes, only initializers that require evaluation of expressions that are not compile-time constants will end up as code in the <clinit> method. Therefore, if an interface has at least one constant that is initialized by an expression that is not a compile time constant, that interface will have a <clinit> method. Initialization of an interface does not require prior initialization of its superinterfaces.

[SHOW THAT THESE ARE PUT INTO SEPARATE CLASS FILES AND THAT THEY ARE USED BY THE CASE STATEMENTS OF SWITCHES, AND TO DO CONDITIONAL COMPILATION.]


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use