Take another look at how we've been creating Circle objects:
Circle c = new Circle();
What are those parentheses doing there? They make it look like we're calling a method. In fact, that is exactly what we're doing. Every class in Java has at least one constructor, which is a method that has the same name as the class and whose purpose is to perform any necessary initialization for a new object. Since we didn't explicitly define a constructor for our Circle class in Example 3-1, Java gave us a default constructor that takes no arguments and performs no special initialization.
Here's how a constructor works. The new operator creates a new, but uninitialized, instance of the class. The constructor method is then called, with the new object passed implicitly (a this reference, as we saw earlier), and whatever arguments that are specified between parentheses passed explicitly. The constructor can use these arguments to do whatever initialization is necessary.
There is some obvious initialization we could do for our circle objects, so let's define a constructor. Example 3-2 shows a new definition for Circle that contains a constructor that lets us specify the radius of a new Circle object. The constructor also uses the this reference to distinguish between a method parameter and an instance field that have the same name.
public class Circle { public static final double PI = 3.14159; // A constant public double r; // An instance field that holds the radius of the circle // The constructor method: initialize the radius field public Circle(double r) { this.r = r; } // The instance methods: compute values based on the radius public double circumference() { return 2 * PI * r; } public double area() { return PI * r*r; } }
When we relied on the default constructor supplied by the compiler, we had to write code like this to initialize the radius explicitly:
Circle c = new Circle(); c.r = 0.25;
With this new constructor, the initialization becomes part of the object creation step:
Circle c = new Circle(0.25);
Here are some important notes about naming, declaring, and writing constructors:
The constructor name is always the same as the class name.
Unlike all other methods, a constructor is declared without a return type, not even void.
The body of a constructor should initialize the this object.
A constructor should not return this or any other value.
Sometimes you want to initialize an object in a number of different ways, depending on what is most convenient in a particular circumstance. For example, we might want to initialize the radius of a circle to a specified value or a reasonable default value. Since our Circle class has only a single instance field, there aren't too many ways we can initialize it, of course. But in more complex classes, it is often convenient to define a variety of constructors. Here's how we can define two constructors for Circle:
public Circle() { r = 1.0; } public Circle(double r) { this.r = r; }
It is perfectly legal to define multiple constructors for a class, as long as each constructor has a different parameter list. The compiler determines which constructor you wish based on the number and type of arguments you supply. This is simply an example of method overloading, which we discussed in Chapter 2, "Java Syntax from the Ground Up".
There is a specialized use of the this keyword that arises when a class has multiple constructors; it can be used from a constructor to invoke one of the other constructors of the same class. In other words, we can rewrite the two previous Circle constructors as follows:
// This is the basic constructor: initialize the radius public Circle(double r) { this.r = r; } // This constructor uses this() to invoke the constructor above public Circle() { this(1.0); }
The this() syntax is a method invocation that calls one of the other constructors of the class. The particular constructor that is invoked is determined by the number and type of arguments, of course. This is a useful technique when a number of constructors share a significant amount of initialization code, as it avoids repetition of that code. This would be a more impressive example, of course, if the one-parameter version of the Circle() constructor did more initialization than it does.
There is an important restriction on using this(): it can appear only as the first statement in a constructor. It may, of course, be followed by any additional initialization a particular version of the constructor needs to do. The reason for this restriction involves the automatic invocation of superclass constructor methods, which we'll explore later in this chapter.
Not every field of a class requires initialization. Unlike local variables, which have no default value and cannot be used until explicitly initialized, the fields of a class are automatically initialized to the default values shown in Table 3-2. Essentially, every field of a primitive type is initialized to a default value of false or zero, as appropriate. All fields of reference type are, by default, initialized to null. These default values are guaranteed by Java. If the default value of a field is appropriate, you can simply rely on it without explicitly initializing the field. This default initialization applies to both instance fields and class fields.
As we've seen, the syntax for declaring a field of a class is a lot like the syntax for declaring a local variable. Both class and instance field declarations can be followed by an equals sign and an initial value, as in:
public static final double PI = 3.14159; public double r = 1.0;
As we discussed in Chapter 2, "Java Syntax from the Ground Up", a variable declaration is a statement that appears within a Java method; the variable initialization is performed when the statement is executed. Field declarations, however, are not part of any method, so they cannot be executed as statements are. Instead, the Java compiler generates instance-field initialization code automatically and puts it in the constructor or constructors for the class. The initialization code is inserted into a constructor in the order it appears in the source code, which means that a field initializer can use the initial values of fields declared before it. Consider the following code excerpt, which shows a constructor and two instance fields of a hypothetical class:
public class TestClass { ... public int len = 10; public int[] table = new int[len]; public TestClass() { for(int i = 0; i < len; i++) table[i] = i; } // Rest of the class is omitted... }
In this case, the code generated for the constructor is actually equivalent to the following:
public TestClass() { len = 10; table = new int[len]; for(int i = 0; i < len; i++) table[i] = i; }
If a constructor begins with a this() call to another constructor, the field initialization code does not appear in the first constructor. Instead, the initialization is handled in the constructor invoked by the this() call.
So, if instance fields are initialized in constructor methods, where are class fields initialized? These fields are associated with the class, even if no instances of the class are ever created, so they need to be initialized even before a constructor is called. To support this, the Java compiler generates a class initialization method automatically for every class. Class fields are initialized in the body of this method, which is guaranteed to be invoked exactly once before the class is first used (often when the class is first loaded). As with instance field initialization, class field initialization expressions are inserted into the class initialization method in the order they appear in the source code. This means that the initialization expression for a class field can use the class fields declared before it. The class initialization method is an internal method that is hidden from Java programmers. If you disassemble the byte codes in a Java class file, however, you'll see the class initialization code in a method named <clinit>.
So far, we've seen that objects can be initialized through the initialization expressions for their fields and by arbitrary code in their constructor methods. A class has a class initialization method, which is like a constructor, but we cannot explicitly define the body of this method as we can for a constructor. Java does allow us to write arbitrary code for the initialization of class fields, however, with a construct known as a static initializer. A static initializer is simply the keyword static followed by a block of code in curly braces. A static initializer can appear in a class definition anywhere a field or method definition can appear. For example, consider the following code that performs some nontrivial initialization for two class fields:
// We can draw the outline of a circle using trigonometric functions // Trigonometry is slow, though, so we precompute a bunch of values public class TrigCircle { // Here are our static lookup tables and their own simple initializers private static final NUMPTS = 500; private static double sines[] = new double[NUMPTS]; private static double cosines[] = new double[NUMPTS]; // Here's a static initializer that fills in the arrays static { double x = 0.0, delta_x; delta_x = (Circle.PI/2)/(NUMPTS-1); for(int i = 0, x = 0.0; i < NUMPTS; i++, x += delta_x) { sines[i] = Math.sin(x); cosines[i] = Math.cos(x); } } // The rest of the class is omitted... }
A class can have any number of static initializers. The body of each initializer block is incorporated into the class initialization method, along with any static field initialization expressions. A static initializer is like a class method in that it cannot use the this keyword or any instance fields or instance methods of the class.
In Java 1.1 and later, classes are also allowed to have instance initializers. An instance initializer is like a static initializer, except that it initializes an object, not a class. A class can have any number of instance initializers, and they can appear anywhere a field or method definition can appear. The body of each instance initializer is inserted at the beginning of every constructor for the class, along with any field initialization expressions. An instance initializer looks just like a static initializer, except that it doesn't use the static keyword. In other words, an instance initializer is just a block of arbitrary Java code that appears within curly braces.
Instance initializers can initialize arrays or other fields that require complex initialization. They are sometimes useful because they locate the initialization code right next to the field, instead of separating it off in a constructor method. For example:
private static finale int NUMPTS = 100; private int[] data = new int[NUMPTS]; { for(int i = 0; i < NUMPTS; i++) data[i] = i; }
In practice, however, this use of instance initializers is fairly rare. Instance initializers were introduced in Java to support anonymous inner classes, and that is their main utility (we'll discuss anonymous inner classes later in this chapter).
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |