Java:The Pitfalls of Inheritance Part-2

Listing 3 A Simple inheritance model

Following this example, we would do the following to create an instance of the Car object:

In this, the instance variable of the Car class named model and also the instance variable registration of the super class Vehicle both will get initialized to their default values. By default, the Java Virtual Machine will allocate enough space for all the instance variables of the Objects own class and all the instance variable in all its super classes. Do not forget that in Java you can have multi-level inheritance, that is a Car IS A Automobile IS A Vehicle. In such cases, the same initialization mechanism will be followed. The initialization chain finally leads to the java.lang.Object class as all the classes in Java implicitly inherits from the Object class.

The important thing is however the order of initialization. According to Java Language Specification, the initialization starts with super class fields and end with the fields declared in the Objects own class. Thus, in our previous example, the order of initialization will be:

  1. All variables in the Object class. (No fields declared)
  2. All variables in the Vehicle class. (field registration).
  3. All variables in the Car class (field model).

The logic behind this order of initialization is to ensure that you can use a super class variable within a sub class initialization properly.

The Initialization Mechanism

In Java, the initialization of objects can be done in two ways:

  • The static initialization blocks
  • The constructor

The static initialization block is invoked only when the Class Loader in Java loads the class. The constructor is not invoked unless you explicitly invoke them in order to initialize an instance of an Object.

In Java the first thing that a constructor does is to invoke another constructor in the super class. In order to fully understand this, I would try to iterate the mechanism in the following points to note.

  • In Java if no constructor is explicitly defined for a class, the compiler provides a default no-argument constructor. However, if you explicitly specify a public constructor, compiler will not place any default constructor.
  • When the constructor of the sub class is invoked, at first the compiler will invoke the default constructor in the super class recursively up in the tree.
  • The super class is constructor can also be invoked explicitly by using the super() key word.
  • From within a constructor, you are free to call a super class constructor with any number of arguments. It does not have to be the default constructor only.
  • It is important however to note that the order of constructor invocation starts from the Objects class and proceeds recursively up in the tree. However, if you remember that the order of instance variable initialization proceeds from the base class down to the Objects class.
  • The previous point coincides with the fact that eventually before a constructor of the Objects class has finished its job, a constructor of the super class is always called, which will initialize the super class fields.

The Problem Begins

The above examples were easy to understand in terms of modelling and coding. What is difficult is how all this magic happens behind the scenes. In fact, we have seen a lot more of what goes on behind the scene than we would think normal. Now, we will probe into the depth of the black magic and who knows we might get scared and leave the stage of inheritance magic all together.

Problem 1

Let us return to our example, and now we come up with something like the following in Listing 4.

Listing 4. New version of the Vehicle-Car relationship implementation

Nothing has changed except we have added two member variables to both the classes and added constructors for the classes. However, the problem begins now that the code does not compile any more!

Lesson 1

The super class in an inheritance relationship cannot have a private constructor. The argument here is that the private constructor stops us from directly instantiating any object of that class. If we really don’t want anyone to create a new object of any class, it cannot possibly be used to represent any Object in an IS-A relationship.

The exceptions to this rule are however the abstract classes and classes that prefer to provide a factory method to obtain instances of that class. The abstract classes are never intended to be initialized as they are abstracts and the compiler will never allow you to do so.

On the other hand, some developers are fanatic about providing factory method as opposed to constructors as means to instantiate objects. It is like providing a static method such as getInstance() as we are used to in implementing “Singleton pattern”. The argument here is that you can always change the mechanism of creating instances of that class without affecting the clients of that class.

Problem 2

Now we correct the previous problem and just remove the private constructor; after all it was just a do nothing constructor. But we decide to add another method stop() in the Car class, which returns a boolean if the Car has stopped. Here is code in Listing 5.

Listing 5: Another version of the Vehicle-Car relationship

This class will also not compile. The problem is that the super class already has a method with the same name(stop) but different return type (void).

Lesson 2

The sub class cannot declare a method with the same name of an already existing method in the super class with a different return type. The sub class can however declare a method with the same signature as in super class. We call this “Overriding”.

This is called a “fragile base class” problem. The base class is fragile because before you can extend it, you may require the full knowledge of all its sub classes. Otherwise, you might end up in a situation where you add a method with the same signature as in one of the base classes and the application no longer compiles!

The finale

You might think that the problems so far described are not really problems as being a Java developer; you would probably know all of it, but it is important to notice that all these, point to a bigger picture and a deeper problem. The problem is that you cannot always trust the inheritance relationship.

  • It is too restrictive in the way you have to make the super class available to the sub classes. For example, the presence of a public constructor is a must.
  • Both the super class and sub classes need the knowledge of each other before adding any other methods to them. This is fragile in terms of flexibility of design.
  • The inheritance in one way violates the principle of encapsulation as sub classes can potentially have access to all data and methods of the super class. However, you can restrict visibility by properly using the access modifiers available in Java.
  • The fact that a sub class by default inherits all the implementation and interfaces of the super class makes the constituting API more prone to misuse.

Conclusion

In this article, we have seen the inside of inheritance mechanism in Java and how it works. We have understood some of the problems related to inheritance. A section of the problem arises from the misuse of the inheritance relationship and some are inherent in the inheritance concept itself.

However, as designers and developers we would like to find an alternative. But is there one? There can be arguments both ways. However, to keep the scope of this article simpler, I will just only point to another technique called Composition where one object contains reference to another object. I will present a detailed analysis of Composition and Inheritance in the next article. Until then make sure you understand all that we talked about Inheritance.