The object construction and instantiation architecture is one of the key concepts to master as you get up to speed on Java and object-oriented programming. In our previous article, we looked at the basics of constructing and initializing objects in Java. Now it’s time to focus in more detail on this topic and consider some of the intricacies and dangers of Java-object construction and initialization.
Catch up on past articles covering the transition to OOP with Java
- · "Transitioning into object-oriented programming using Java"
- · ”Intro to OOP: Understanding classes and objects”
- · "Intro to OOP: A closer look at inheritance and polymorphism"
- · "Intro to OOP: Restricting access to object properties"
- · "Intro to OOP: Java scope rules"
To create an instance of an object, you must use one of Java's instantiation mechanisms. One of these is the new operator. The new operator returns a new instance of an object that has been initialized according to the behavior defined by the object's specified constructor, as in the following examples:
Foo anObject = new Foo();
Foo anotherObject = new Foo("This is a string argument");
Java provides additional mechanisms to create instances of objects. One in particular is the newInstance method of the java.lang.Class class. This method is used in the following fashion:
Object anObject = Class.forName("com.acme.utils.Foobar").newInstance();
Notice how the fully qualified name of the class is passed to the forName method. You can also pass a String argument containing the class name to the forName method. This is very useful when the class name is discovered at runtime, as in the situation where the class name is loaded from a property file on disk or across a network connection.
Another instantiation mechanism is the instantiate method of the java.beans.Beans class. The instantiate method requires two parameters, a classloader and a fully qualified class name. The following is an example of the instantiate method that uses the classloader of the current class:
Object anObject = Beans.instantiate(this.getClassLoader(), "com.acme.utils.Foobean");
Each of the newInstance and instantiate methods instruct the Java virtual machine (JVM) to create an instance of a class and call the default constructor on the instance. The default constructor doesn’t have any arguments and is sometimes referred to as the "no-arg" constructor. The newInstance and instantiate methods assume that a default constructor is, in fact, defined for the class that is being instantiated. This brings us to an interesting discussion about Java and default constructors.
In Java, if a class does not declare any constructors at all, the JVM will automatically generate a default constructor at the byte-code level. If a class declares constructors of any kind, the JVM will not automatically generate the default constructor. This presents a problem. If a class defines a constructor with one or more arguments and neglects to define a default constructor, the class will be left without a default constructor, and both the newInstance and instantiate methods will cause a java.lang.InstantiationException to be thrown at runtime.
This and Super
As with C++, Java implicitly provides a keyword, called this, in each method of an instantiated object. The this keyword is used to explicitly refer to the member fields and methods belonging to the object. For example, in the following code snippet, we declare a field called state and a method argument with the same name. Since both variables exist in different scopes, the Java compiler does not complain. To avoid confusion and possible errors, we use the this keyword to refer to the member field.
public class Foo
private int state = 0;
public void setState(int state)
this.state = state;
For clarity, you can also use the this keyword to make an explicit method call on an object, as in the following snippet:
int returnValue = this.performSomeCalculation();
From within a constructor in Java, you can explicitly invoke another constructor of the same class by using the this() statement. This approach is useful if you have several overloaded constructors defined for a class, all of which contain duplicate code. For example, the snippet in Listing A contains duplicate code in multiple constructors.
We can eliminate most of the duplicate code by using the this() statement, as in the example shown in Listing B.
From within a constructor in Java, you can also explicitly invoke a specific constructor of an object's superclass by using the super() statement. This is useful for passing specific parameters to the constructor of a superclass. We will do this in Listing C, assuming that the superclass contains the declaration for all of the member fields.
There are a couple of rules for using this() and super():
The this() or super() statement must be the first statement in the constructor.
Member field initialization
In Java, the member fields of an object are initialized starting with the fields declared in the base class and ending with the fields declared in the child class. Assuming our Foo class extends the Bar class and a Foo constructor is called, the order of initialization of fields would be:
- · The member fields defined by java.lang.Object (since all objects in Java implicitly inherit from java.lang.Object).
- · The member fields defined by Bar.
- · The member fields defined by Foo.
Initializing the member fields in the superclass first prevents them from being used before they are initialized to their desired values. Since the superclass is guaranteed to be initialized first, you can safely use its fields or call its methods from the constructor of a child of the superclass.
In Java, member field definitions can include a specific value. If a value is not specified, the fields are automatically initialized to default values by the JVM. Let's look at an example:
public class Foo
private int state = 1;
private long initTime;
private String id;
private String logFileName = "";
In this case, state is explicitly initialized to 1, initTime is implicitly initialized to 0 by the JVM, id is implicitly initialized to null by the JVM, and logFileName is explicitly initialized to a zero-length string.
Tricky, but worth it
Java provides an object construction and initialization architecture that is flexible and fairly straightforward; however, there are a few subtle dangers to look out for and tricks that can be used to write more efficient and robust code. In our next article, we will discuss the primitive data types defined by Java, their default initialization values, their range of values, and some of the things you can and can't do with them.