Section 4.4
Constructors, Object Initialization, and Garbage Collection


OBJECT TYPES IN JAVA ARE VERY DIFFERENT from the primitive types. Simply declaring a variable whose type is given as a class does not automatically create an object of that class. Objects must be explicitely constructed. For the computer, the process of constructing an object means, first, finding some unused memory in the heap that can be used to hold the object and, second, filling in the object's instance variables. As a programmer, you don't care where in memory the object is stored, but you will usually want to exercise some control over what initial values are stored in a new object's instance variables. In many cases, you will also want to do more complicated initialization or bookkeeping every time an object is created.

There are several ways for a Java programmer to control what happens when an object is constructed. The first is to provide initial values in the class definition, where the instance variables are declared. For example, consider the class:

         class Mosaic {

             // class to represent "mosaics" consisting of
             // colored squares arranged in rows and columns

             int rows = 10;    // number of rows of squares
             int columns = 20; // number of columns of squares
             .
             .   // (the rest of the class definition)
             .
          }

When an object of type Mosaic is created, it includes two instance variables named rows and columns, which are initialized with the values 10 and 20, respectively. This means that for every newly created object, msc, of type Mosaic, the value of msc.rows will be 10, and the value of msc.columns will be 20. (Of course, there is nothing to stop you from changing those values after the object has been created.) Note that the declarations "int rows = 10;" and "int columns = 20;" are executed each time an object of type Mosaic is created, and that rows and columns refer to the copies of the instance variables in that particular object. For static member variables, of course, the situation is quite different. There is only one copy of a static variable, and initialization of that variable is done just once, when the class is first loaded.

If you don't provide any initial value for an instance variable, default initial values are provided automatically. Instance variables of numerical type (int, double, etc.) are automatically initialized to zero if you provide no other values; boolean variables are initialized to false; and char variables, to the Unicode character with code number zero.

An instance variable can also be a variable of object type. For such variables, the default initial value is null. (In particular, since Strings are objects, the default initial value for String variables is null.) Of course, you can provide an alternative initial value if you like. For example, the class Mosaic might contain an instance variable of type MosaicFrame, where MosaicFrame is the name of another class. This instance variable could be initialized with a new object of type MosaicFrame:

         class Mosaic {
             int ROWS = 10; // number of rows of squares
             int COLS = 20; // number of columns of squares
             MosaicFrame window = new MosaicFrame(); 
                           // a window to display the mosaic
             .
             .   // (the rest of the class definition)
             .
          }

Whenever an object of class Mosaic is constructed, another object of type MosaicFrame is automatically constructed, and a reference to the new MosaicFrame is stored in the instance variable named window. (Note again that the statement "MosaicFrame window = new MosaicFrame();" is not executed unless and until an object of class Mosaic is created. And it is executed again for each new object of class Mosaic, so that each Mosaic object gets its own new MosaicFrame object.)


The second way to get initial values into the instance variables of a class is to provide one or more constructors for the class. In fact, constructors can do a whole lot more than just fill in instance variables: They let you program any actions whatsoever that you would like to take place automatically, every time an object of the class is created.

A constructor for a class is defined to be a routine in that class which is called automatically by the system when a new object of that class is created. Syntactically, a consructor can be recognized because its name is the same as the name of the class and it has no return value, not even void.

A constructor can have parameters. You can have several different constructors in one class, provided they have different signatures (that is, provided they have different numbers or types of parameters). A constructor cannot be declared to be static, but, on the other hand, it's not really an instance method either. There is no way to call a constructor directly. The only way you can call a constructor is with the "new" operator, which is used to create new objects. The syntax of a constructor call is is:

new  class-name ( parameter-list )

where the parameter-list is possibly empty. When the computer evaluates a constructor call, it creates a new object, executes the constructor, and returns a reference to the new object. Most often, you will store the returned reference in a variable, but it is also legal to use a constructor call as part of an expression -- for example as a parameter in a subroutine call.

As an example, let's rewrite the Student class that was used in the Section 1:

        public class Student {
        
           private String name;  // Student's name
           private int ID;  // unique ID number for this student
           public double test1, test2, test3;   // grades on three tests
           
           private static int nextUniqueID = 0;
                     // keep track of next available unique ID number
           
           Student(String theName) {
                // constructor for Student objects;
                // provides a name for the Student,
                // and assigns the student a unique
                // ID number
              name = theName;
              nextUniqueID++;
              ID = nextUniqueID;
           }
           
           public String getName() {
                // accessor method for reading value of private
                // instance variable, name
              return name;
           }
           
           public getID() {
                // accessor method for reading value of ID
              return ID;
           }
           
           public double getAverage() {  // compute average test grade
              return (test1 + test2 + test3) / 3;
           }
           
        }  // end of class Student

In this version of the class, I have provided a constructor, Student(String). This constructor has a parameter of type String that specifies the name of the student. I've made the instance variable name and ID into private members, so that I can keep complete control over their values. In fact, by examining the class, you can see that once the values of name and ID have been set by the constructor, there is no way for them ever to be changed.

Notice that since name is a private variable, I've provided a function, getName(), that can be used from outside the class to find out the name of the student. Thus, from outside the class, it's possible to discover the name of a student, but not to change the name. This is a very typical way of controlling access to a variable. The ID instance variable is handled in a similar way.

In the original version of this class, the name and ID had to be assigned by the program that uses the Student class. There was no guarantee that the programmer would always remember to set the name and ID properly. In the new version of the class, there is no way to create a Student object except by calling the constructor, and that constructor automatically sets the name and ID. The programmer's life is made easier, and whole hordes of frustrating bugs are squashed before they even have a change to be born.

I should note, by the way, that if you provide initial values for instance variables, those values are computed and stored in the variables before the constructor is called. It's common to use a combination of initial values and constructors to set up new objects just the way you want them.

Since the constructor in the Student class has a parameter of type String, a value of type String must be included when the constructor is called. Here are some examples of using this constructor to make new Student objects:

        std = new Student("John Smith");
        std1 = new Student("Mary Jones");

You've probably noticed that the previous version of class Student did not include a constructor. Yet, we were able to construct instances of the class using the operator "new Student()". The rule is that if you don't provide any constructor in a class, then a default constructor, with no parameters, is provided automatically. This default constructor doesn't do anything beyond filling in the instance variables with their initial values.


Constructors in Subclasses

Constructors exist to make certain that new objects start out with known initial states. If an object is a member of a subclass, part of its structure is inherited from that class's superclass, and that part of its structure must be initialized by calling a constructor from the superclass.

If the superclass has a default constructor -- that is, one with no parameters -- then the system will call it automatically. (This will also happen if no constructor at all is defined for the superclass; the default constructor that is provided automatically by the system will be called.) However, if all the constructors defined in the superclass require parameters, then any constructor you write for the subclass must explicitly call a constructor for the superclass. Sometimes, even if you aren't forced to do it, you might want to call a particular constructor from the superclass. The syntax for doing so uses the special variable super. Here is an example. Assume that class TextBox, which represents input boxes where the user can type characters, has a constructor that specifies the maximum number of characters that the box can hold. Then, we might define a subclass

        class TextBoxWithDefault extends TextBox {
            TextBoxWithDefault(int maxChars, String defaultContents) {
               super(maxChars);  // call constructor from superclass
               setContents(defaultContents);
            }
            . . . // more stuff
         }

It is also possible for one constructor to call another constructor from the same class. This is done using the special variable this in place of super. The class TextBoxWithDefault, for example, might have a constructor

             TextBoxWithDefault() {
                this(20,"");
             }

This constructor exists only to call another, more complicated constructor and to provide it with reasonable default values for its parameters.

When you call one constructor from another using this special syntax with this or super, it must be the first line of the constructor that you write.


Static Initializers

Constructors allow you to schedule arbitrary actions to be executed when an object is created. Occasionally, you might want to do something similar for a class as a whole. That is, you would like to schedule some actions to be exectued when the class is first loaded. Since classes are not constructed in the same way as objects, you can't do this with constructors. Instead, you have to do it with something called a static initializer. A static initializer is a block of code that will be automatically executed when the class is loaded by the system. A static initializer can be used to initialize the class's static variables, but it can perform other actions as well. Syntactically, a static initializer consists of a block of code, enclosed between { and }, marked with the word static. It has no name, no parameters, and no return type.

For example, suppose that information about students is stored on disk in a database. You might write a Student class that needs access to this database. If so, you could use a static initializer to open the database when the class is loaded:

          class Student {
             
             static {   // static initializer for class Student
                openDataBase();
             }
             
             . . .  // other stuff
             
          }

(This assumes that openDataBase() is a static subroutine defined elsewhere in the class.)

There are also, by the way, instance initializers. An instance initializer consists simply of a block of code, without the word static, and it is executed whenever an object belonging to the class is created. However, anything you could do in an instance initializer could be done just as well in a constructor, and I recommend sticking to constructors for object initiailization.


Garbage Collection

So far, this section has been about creating objects. What about destroying them? In Java, the destruction of objects takes place automatically.

An object exists on the heap, and it can be accessed only through variables that hold references to the object. What happens to an object if there are no variables that refer to it? This can happen. Consider the following two statements (though in reality, you'd never do anything like this):

           Student std = new Student("John Smith");
           std = null;

In the first line, a reference to a newly created Student object is stored in the variable std. But in the next line, the value of std is changed, and the reference to the Student object is gone. In fact, there are now no references whatsoever to that object stored in any variable. So there is no way for the program ever to use the object again. It might as well not exist. In fact, the memory occupied by the object should be reclaimed to be used for another purpose.

Java uses a procedure called garbage collection to reclaim memory occupied by objects that are no longer accessible to a program. It is the responsibility of the system, not the programmer, to keep track of which objects are "garbage". In the above example, it was very easy to see that the Student object had become garbage. Usually, it's much harder. If an object has been used for a while, there might be several references to the object stored in several variables. The object doesn't become garbarge until all those references have been dropped.

In many other programing languages, it's the programmer's responsibility to delete the garbage. Unfortunately, keeping track of memory usage is very error-prone, and many serious program bugs are caused by such errors. A programmer might accidently delete an object even though there are still references to that object. This is called a "dangling pointer" error, and it leads to problems when the program tries to access an object that is no longer there. Another type of error is a "memory leak," where a programmer neglects to delete objects that are no longer in use. This can lead to filling memory with objects that are completely inaccessible, and the program might run out of memory even though, in fact, large amounts of memory are being wasted.

Because Java uses garbage collection, such errors are simply impossible. You might wonder why all languages don't use garbage collection. In the past, it was considered too slow and wasteful. However, research into garbage collection techniques combined with the incredible speed of modern computers have combined to make garbage collection feasible. Programmers should rejoice.


[ Next Section | Previous Section | Chapter Index | Main Index ]