http://www.developer.com/

Back to article

Inside Constructors


July 15, 2005

This series is intended for anyone who needs to understand fundamental object-oriented concepts before jumping into the code. It can also be used by those who are already coding in an OOP language but do not fully understand the nuances behind the code. Fully understanding the infrustructure of the concepts may improve your coding. These concepts are part of the foundation that any programmer will need to make the paradigm shift from procedural programming to object-oriented programming. See the article The Object-Oriented Thought Process to start at the beginning of the series.

In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems Web site, http://java.sun.com/. You can download the J2SE 1.4.2 SDK (software development kit) to compile and execute these applications and I will provide the code listings for all examples in this article. I have the SDK 1.4.0 loaded on my machine. I will also provide figures and the output (when appropriate) for these examples. See the previous articles in this series for detailed descriptions for compiling and running all the code examples in this series.

In the last column Object Construction, I began a discussion about constructors. Designing effective constructors is one of the responsibilities that must be taken care of when creating classes. In the previous column, I covered constructor basics as well as some finer points of constructor design. Remember that proper construction puts the object in a safe-state. In this article, you will delve deeper into code as you uncover many of the implementation issues of designing constructors.

The Default Constructor

In the previous column, you spent much effort in describing some of the design issues regarding constructors. In this column, you'll investigate and analyze some of the bytecodes that are generated by the Java compiler when it encounters constructors.

First, you will define a simple application that you will use as the basis for this exercise. The initial pass at this application will contain the following classes included in Listing 1:

Listing 1

// Class Shape
public class Shape {

}
//Class TestShape
public class TestShape {
   public static void main (String args[]){
   Shape s = new Shape();
   }
}

This application contains two very small classes. The Shape class actually contains no implementation—or so it may seem. The TestShape class is the actual application (because it contains main()). In this case, the application creates one instance of a Shape class. When this application is run, you get the output presented in Figure 1—which, as expected, is nothing.



Click here for a larger image.

Figure 1

Even though the application is not producing any output, and despite the fact that there is no code in the Shape class, there is action going on behind the scenes.

As you learned in the previous column, if the class provides no explicit constructor, such as in C++, Java, and so forth, the compiler will provide a default constructor. The structure of the default constructor would look like the code in Listing 2:

Listing 2

// Class Shape
public class Shape {
public Shape() { // not actually in source code }
}

It is important to realize that this code is only inserted in the bytecodes generated by the compiler. The default constructor does not show up at all in the source code. In fact, if you decompiled the bytecodes, you would see it then.

After the memory is allocated, the default constructor calls the constructor of the superclass—if there is one (actually there is always a super class—every written Java class ultimately will implicitly inherit from the Object class).

Now, look at these points much more closely.

The Java distribution comes with a binary application called  javap. This application will actually take bytecodes and re-engineer them into something resembling the original source code. There are several Java decompilers currently available; however, because javap is provided with the Java distribution and it serves the purpose here, you will use this tool.

You can call  javap from the console by using the following command-line call:

C:j2sdk1.4.2_05binjavap -classpath . Shape

In this case, you are using  javap to decompile the Shape class. As you can see in Figure 2, the output includes the default constructor in the Shape class even though it was not in the original source code. To review, the default constructor has no parameters.



Click here for a larger image.

Figure 2

It is also interesting to note that the Shape class actually extends  java.lang.Object, just as you would expect. (Remember that all classes in Java ultimately inherit from the Object class.)

This is proof that the compiler actually inserts the default constructor into the Shape class—and in any class that does not provide a constructor.

The key phrase here is in any class that does not provide a constructor. Now, add a non-default constructor to the code as illustrated in Listing 3.

Listing 3

// Class Shape
public class Shape {
   public Shape(int x) {
   }
}
//Class TestShape
public class TestShape {
   public static void main (String args[]){
   Shape s = new Shape(5);    // must pass an integer
   }
}

In Listing 3, you have added a constructor that takes one integer parameter. Thus, you must pass an integer to the constructor when you instantiate a Shape object.

Now, if you run the Shape class through  javap, you get the results in Figure 3.



Click here for a larger image.

Figure 3

Note now that the default constructor does not show up in the decompiled code of the Shape class. Again, this is more proof of the implementation issue that you may have studied in an object-oriented book or class—that the default constructor is provided only if you do not provide your own constructor. As long as you provide at least one constructor in the code, the compiler will not add the default.

What happens if you provide a default constructor? You can actually do this—because the definition of the default constructor is simply a constructor with no parameters. Take a look at Listing 4.

Listing 4

// Class Shape
public class Shape {
   public Shape() {
   }
}

In this case, you are simply doing what the compiler would do; however, there is one major difference. In this case you can add code specific to the construction of the object. Look at the output when you run the Shape class (from Listing 4) through  javap.



Click here for a larger image.

Figure 4

The output in Figure 4 is identical to the output in Listing 3. This is further evidence that the default constructor provided by the compiler is identical to the default constructor that you can provide in your code.

Constructors and Inheritance

One of the more interesting issues that you can cover regarding construction is the order of events that occur when inheritance is used in the design. You may have noticed in the earlier examples that you were instantiating an actual Shape object. Generally, Shape would be designed as an abstract class. You then would create a concrete class, such as Circle, to inherit from Shape. This has been implemented in the code presented in Listing 5. Note that in this revised design, you must instantiate a Circle, not a Shape.

Listing 5

//Class TestShape
public class TestShape {
   public static void main (String args[]){
   Circle s = new Circle();
   }
}
// Class Shape
public abstract class Shape {
   public Shape () {
      System.out.println("Inside Shape Constructor.");
   }
}
// Class Circle
public class Circle extends Shape{
   public Circle () {
      System.out.println("Inside Circle Constructor.");
   }
}

An obvious question here is how many constructors actually exist? If this does not seem like an obvious question, you can explore this point.

Remember that, in this application, only a Circle is actually instantiated.

Circle s = new Circle();
A Shape is never instantiated—in fact it can't be, because it is abstract. Because Circle extends Shape, it inherits everything that Shape contains. Thus, if Shape contains an attribute called color, Circle inherits it. However, does Circle inherit Shape constructor? You can run the code and see whether the output from the Shape constructor is printed—see Figure 5.



Click here for a larger image.

Figure 5

As you can see, Shape's constructor is actually called when the application is run. In fact, Circle contains is own constructor that calls its parent—which in this case is, of course, Shape. Shape's constructor is called first even though a Shape class is never actually instantiated. It is interesting to look at the decompiled code from Circle using  javap seen in Figure 6—using the following invocation:

javap -c Circle



Click here for a larger image.

Figure 6

The output in Figure 6 shows information about the bytecodes that make up the Circle.class file. You can see that in the Method Circle (Circle's constructor), line 1 contains the call to the Method Shape (Shape's constructor). This is done before any code in Circle's constructor is executed. This confirms the rules that were presented in the previous column in this series.

You can mimic the default code by inserting the super() method in Circle's constructor, as indicated in Listing 6.

Listing 6

// Class Circle
public class Circle extends Shape{
   public Circle () {
      super();    // invoke the parent's constructor
      System.out.println("Inside Circle Constructor.");
   }}

When dealing with the default constructors, placing super() in the code does not provide any real functionality. However, when using designs with multiple constructors, using super() does come into play. You will explore this issue in the next article in the series.

One interesting question that you can investigate is what if you want the call to super() to occur after some other code in the constructor? In other words, does super() have to be first? As always, you can see what the compiler says.



Click here for a larger image.

Figure 7

According to the compiler, the call to super() must be the first statement in the constructor. As a review, let me cover the rules for the flow of events when using constructors in an inheritance situation.

After the new keyword is encountered and the object is allocated, the following steps occur:

  1. The first thing that happens inside the constructor is that the constructor of the class's superclass is called.
  2. Then, each class attribute of the object is initialized. These are the attributes that are part of the class definition (instance variables), not the attributes inside the constructor or any other method (local variables).
  3. Finally, the rest of the code in the constructor executes.

Conclusion

This article continued a discussion focusing on the topic of constructing objects. You covered some of the technical aspects of actually using constructors. Constructing objects properly is a very important object-oriented responsibility. It is very useful to take a look inside the bytecodes of classes to see exactly how the objects behave. Next month, you will further explore how the bytecodes are affected by overloading constructors.

References

  • Gilbert, Stephen, and Bill McCarty: Object-Oriented Design in Java. The Waite Group, 1998.
  • Meyers, Scott: Effective C++. Addison-Wesley, 1992.
  • Tyma, Paul, Gabriel Torok, and Troy Downing: Java Primer Plus. The Waite Group, 1996.
  • Ambler, Scott: The Object Primer. Cambridge University Press, 1998.
  • Jaworski, Jamie: Java 1.1 Developers Guide. Sams Publishing, 1997.

About the Author

Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, and C# .NET as well as various web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management. Besides the first edition of The Object-Oriented Thought Process, Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb's Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date