Creating New Objects: Constructors

You call a constructor to create a new object. A constructor is a special kind of method that you write as part of a class. It looks like an ordinary method, but a constructor magically allocates memory for a new instance of an object, then executes the statements that you write in the constructor body. Your statements typically initialize the fields of the new object.

Like any method, a constructor needs to be given a name. A constructor also needs to indicate that its return type is “an object of the class that this constructor belongs to”. The Java design team chose to roll these two needs together, by adopting the conventions that:

  • A constructor has the same name as the class it belongs to.

  • A constructor is written without an explicit return type. In some sense, its name is the return type.

The purpose of a constructor is to allocate and initialize a newly created object. Here's the Timestamp class with the obvious constructor:

class Timestamp {
        int hrs;
        int mins;
        int secs;

        // a constructor returns a new Timestamp object
        // implicitly allocates memory for the object
        // explicitly initializes the fields of the object
        Timestamp() {
            java.util.Calendar now =
                        java.util.Calendar.getInstance();
            hrs = now.get(java.util.Calendar.HOUR_OF_DAY);
            mins = now.get(java.util.Calendar.MINUTE);
            secs = now.get(java.util.Calendar.SECOND);
        }
}

Notice there is no explicit statement to allocate memory; the run-time library knows that this is a constructor, not an ordinary method, and does the allocation behind-the-scenes for you. Also, there is no “return TimestampObjectRef” statement, even though the constructor does return a Timestamp object reference at the place where it was called. These are just language quirks to get used to.

Here is what a call to a constructor looks like:

t = new Timestamp(); // t now points to an obj

The keyword “new” is always used when you call a constructor, it tips you off that it's not a method call. It's very common to declare a variable and initialize it by a constructor call in one shot:

Timestamp t = new Timestamp();

An expression with the “new” keyword is the only way to get a new object created (sole exception: cloning, described in Chapter 11). Even if it looks like you got an object some other way (like a string literal, or the java.lang.Class.newInstance() method) a constructor was called for you behind the scenes.

A call to a constructor creates a new instance of a class, so the process is also known as instantiation or doing an instantiation.

The default no-arg constructor

All classes always have at least one constructor. If you write a class without any explicit constructors (this is allowed), the default no-arg constructor is assumed for you. The default no-arg (meaning “no arguments”) constructor for a class takes no arguments and does nothing except create an object.

Here's the Timestamp class, with no explicit constructors:

class Timestamp {
        int hrs;
        int mins;
        int secs;
}

The default no-arg constructor is provided by the compiler, and it's exactly equivalent to writing this:

class Timestamp {
        int hrs;
        int mins;
        int secs;

        Timestamp() { } // explicit no-arg constructor
}

When not to allow a no-arg constructor

Some classes shouldn't have a no-arg constructor. An example would be a class that represents Employee records. Assume that every employee must have a unique ID number, and you intend this will be passed as an argument to the constructor. If that class also had a no-arg constructor, that's a potential way to create an object without the ID getting set. So Java says when you provide any constructor at all, you do not get the default no-arg constructor. That ensures that a careless programmer can't call it, and create a half-baked employee object with no ID.

When you want it all

There are other cases where you need some explicit constructors, and you also want a no-arg constructor. If you also want a no-arg constructor, you just write one explicitly, as in the Timestamp code above. You can put some statements in the no-arg constructor body if you wish.

Constructors can call sibling constructors

Most classes have at least one explicit constructor. You can define several different constructors and tell them apart by their parameter types. Sometimes you may want one constructor to call another. It's quite common to have a series of constructors that accept several different types of arguments, and each call a single constructor with the arguments in a standardized form to do the rest of the processing in one place. You use the keyword this() to call a different constructor in the same class. We have seen this in a method means “the object I was invoked on.” Here it is re-used to mean “one of my other constructors; pick the one with the matching signature.”

Here's an example of one constructor calling another. These are two constructors that could be added to Timestamp, allowing a programmer to pass in the hour, minute, and second that he wants the Timestamp to represent. The second Timestamp constructor allows the programmer to just set the hour, and zero should be set in the other fields. The second constructor will call the first.

        Timestamp(int h, int m, int s) {
            hrs = h;
            mins = m;
            secs = s;
        }

        Timestamp(int h) {
            this(h, 0, 0); // calls the other constr.
        }

When one constructor explicitly invokes another, that invocation must be the very first statement in the constructor. This is because you can't call a constructor on something that has already had its memory allocated, and the memory has already been allocated by the time the system starts executing your statements in a constructor.

A constructor cannot be invoked explicitly by the programmer other than in object creation, although this might otherwise be quite a useful operation to, say, reset an object to a known initial state.

A second glimpse at inheritance

To fully understand constructors, we need to say a few more words about inheritance. All the constructor calls we have seen so far have been like this:

Timestamp t = new Timestamp();

But you may also see a constructor called with a different name (and therefore a different type) from the reference variable type:

Fruit lime = new Citrus();

Here we are getting a Citrus-type object back from the constructor, but we are assigning it to a variable of type Fruit. You can only do this with compatible types.

One class can be based on another class. That class is compatible with the class it is based on. The technique of basing one class on another is called “inheritance.” Inheritance is what you get from your parent, and a child class has all the fields and methods that are in the parent.

It's a neat thing: A class can be related to another class in a parent/child relationship. You are saying “this class extends that class over there, with these additional members or changes.” A variable of a parent type can also hold a value that points to a child object. When you see a line of code as above, you can conclude that since Fruit can hold a Citrus object pointer, Fruit must be the parent of Citrus (or grandparent or somewhere above it). So there must be a definition similar to this somewhere:

   class Fruit {  /* */  }

   class Citrus extends Fruit { /*  */ }

In those lines of code, there is obviously a class called Fruit and a class called Citrus that extends Fruit. That means Citrus has all the methods and fields that Fruit does, plus its own special operations that apply only to citrus fruit. Citrus is a child class of Fruit. It is also termed a subclass or subtype. Subclass objects can always be assigned to parent objects, but not the other way round. All Citrus are Fruit. But not all Fruit are Citrus (it might be AppleVariety or FruitGrownOnVine).

Even if you don't explicitly give it one, all classes that you write have a parent class. If you do not specify a parent, your class extends the fundamental system class known as Object. A class is a type, and a child class is just a subtype. All objects in the system are subtypes of java.lang.Object, and have all the members of that root class.

What happens during instantiation

When an object is instantiated, this is the order in which things happen:

  1. The memory for the object is allocated.

  2. That memory is cleared, causing data fields to be filled in with the default values zero, 0.0, null, and so on. Thus, all objects start with a known state.

  3. The constructor is called and might explicitly call another constructor in the same class.

  4. A constructor in the object's parent class is always called, either explicitly with super(someArguments),or implicitly. This is recursive, meaning that the parent then calls a constructor in its parent and so on all the way back to the constructor in the java.lang.Object class.

  5. Any data fields with explicit initializers have those initial assignments executed.

  6. Any Instance Initializers are executed. Most of your classes won't use instance initializers, but see the following box for information about them.

  7. The rest of the statements in the object's constructor are executed.

People sometimes doubt that a constructor calls a constructor in the parent class, even if you didn't explicitly code it that way. Take a look at this example. There are three classes (Grandparent, Parent, and Child) in a superclass, class, subclass relationship. The phrase “class Child extends Parent” makes class Child a child of class Parent. You can use any names; I used Child and Parent to make the hierarchy obvious.

class Grandparent {
      Grandparent() {
          // parent constructor (java.lang.Object) is always called here
          // or you may call it explicitly with super();
        System.out.println("Grandparent");
     }
}

class Parent extends Grandparent {
   Parent() {
          // parent constructor is always called here
          // or you may call it explicitly with super();
          System.out.println("Parent");
     }
}

public class Child extends Parent {

     public static void main(String[] args) {
         Child c = new Child();// invokes the chain of parent constrs.
         System.out.println("Child");
     }
}

The class called “Child” has a default no-arg constructor. However, when you instantiate a child object, you will see that the parent and grandparent (and so on) constructors are called, even though there is no explicit statement doing that. The right way to think about it is that constructors do object-specific initialization. So you definitely want the parent constructor called because it knows best how to initialize the fields that you got from the parent. You should compile and run the program, and check that you see this output:

Grandparent
Parent
Child

Grandparent is printed first because its constructor is called before the first statement in the Parent constructor is executed. Constructing a new object can take several method calls and be expensive in terms of processor time and system resources.

No destructors

Java has automatic storage management, including automatic reclamation of objects no longer in use. The run-time system does not need to be told when an object has reached the end of its lifetime.

Accordingly, there are no “destructor” methods to reclaim an object. Just delete, overwrite, or null out the last reference to an object, and it becomes available for destruction so the memory can be reused.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.136.186