Section 4.2
Constructors
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. 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 will usually want to exercise some control over what initial values are stored in a new object's instance variables. There are two ways to do this. 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 COLS = 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 COLS, 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.COLS will be 20. (Of course, there is nothing to stop you from changing those values after the object has been created.)
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 MosaicWindow, where MosaicWindow is the name of another class. This instance variable could be initialized with a new object of type MosaicWindow:
class Mosaic { int ROWS = 10; // number of rows of squares int COLS = 20; // number of columns of squares MosaicWindow window = new MosaicWindow(); // a window to display the mosaic . . // (the rest of the class definition) . }When an object of class Mosaic is constructed, another object of type MosaicWindow is automatically constructed, and a reference to the new MosaicWindow is stored in the instance variable named window. (Note that the statement "MosaicWindow window = new MosaicWindow();" 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 MosaicWindow object.)
There is a second way to get initial values into the instance variables of a class. That is to provide one or more constructors for the class. In fact, constructors can do 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 subroutine in that class whose name is the same as the name of the class and which 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. The only way you can call a constructor is with the new operator. In fact, the syntax of the new operator is:
new constructor-call
When the computer evaluates this expression, it creates a new object, executes the constructor, and returns a reference to the new object.
As an example, let's rewrite the Student class that was used in the previous section:
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 = 1; // 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; ID = nextUniqueID; 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 StudentIn 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 into a private member, so that I can keep complete control over its value. In fact, by examining the class, you can see that once the value of name has been set by the constructor, there is no way for it ever to be changed: A name is assigned to a Student object when it is created, and the name remains the same for as long as the object exists.
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 in the class Student is handled in a similar way.
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 this 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. The default constructor doesn't do anything beyond filling in the instance variables with their initial values.
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? 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 many other programing languages, it's the programmer's responsibility. 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 ]