You are currently viewing Stranger things in Java: Constructors

Stranger things in Java: Constructors

What you are reading is one in a series of articles titled “Stranger things in Java”, inspired by the contents of my book “Java for Aliens”. These articles are dedicated to insights of the Java language. Deepening the topics we use every day, will allow us to master the Java coding even in the strangest scenario.

In this post, we will explore some scenarios in which the use of a Java programming base concept such as the constructor, can hide some pitfalls. In particular, after having clarified some fundamental definitions, we will explore the relationships between constructors and inheritance, constructors and polymorphism, and the hidden work of the compiler.

 

Definition and properties

We already know that a constructor is considered a special method, which by its nature, is especially suitable for the initialization of instance variables. A simple example of a constructor is the one contained within the following class:

public class Book {
    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle () {
        return title;
    }
}

Note that, while methods are invoked using the dot operator (the “.” symbol):

String bookTitle = book.getTitle();

the constructors are invoked through the operator new:

Book book = new Book("Java for Aliens");

In particular, unlike an ordinary method, a constructor has the following characteristics:

  • has the same name as the class to which it belongs;
  • has no return type;
  • is called automatically (and only) whenever an object of the class to which it belongs is instantiated, for that object;
  • is present in every class.

The last point is clarified in the next section.

 

Default Constructor

There is always a constructor in every compiled class. In fact, even if a class does not declare a constructor explicitly, the compiler will automatically add the so-called default constructor within the bytecode. The default constructor contains only one statement which we will discuss shortly, and also has no parameters. But an explicitly declared parameterless constructor is not a default constructor. For example, the constructor in the following class is not a default constructor, but simply a parameterless constructor:

public class Book {
    public Book() {
        System.out.println("Parameterless constructor (non-default)");
    }
}

The compiler in this case will not add a default constructor, because an explicit constructor in this class already exists. Note that the compiler’s decision does not depend on the number of parameters in the constructor, but only on whether there is an explicit constructor or not. For example, no default constructor would be added, even in the case of the first example of this article. In fact, the compiler adds the default constructor, only to allow the class to be instantiated using the new operator. In this way, it is possible to learn object-oriented programming without knowing the constructor concept, and make the learning curve less steep in the early days.

 

Inheritance and Constructors

Inheritance is not applicable to constructors. Even when they are declared public, constructors are not inherited for a very simple reason: their own name. For example, let’s consider the following JavaBook class extension of the Book class:

public class JavaBook extends Book {

}

If the constructors were to inherit, the JavaBook class would inherit a constructor called Book. In practice it is as if the JavaBook class were written in the following way:

public class JavaBook extends Book {
    public Book() {
        System.out.println("Parameterless constructor (non-default)");
    }
}

But a constructor called Book in a class called JavaBook can never be called! In fact, to instantiate an object from the JavaBook class, you must necessarily call a constructor called JavaBook using the new operator.

For example, if we want to instantiate a JavaBook object we will write:

JavaBook javaBook = new JavaBook();

invoking the constructor of the JavaBook class with the new operator.

Now suppose we inherited the Book constructor in the JavaBook class. In order to use this constructor, we should be able to write the following statement:

JavaBook javaBook = new Book();

which, however, implies the creation of an object of the Book class, and not of an object of the JavaBook class, also resulting in a compilation error, because it is not possible to assign a JavaBook type reference to a Book type object. In fact, follows the output:

error: incompatible types: Book cannot be converted to JavaBook
        JavaBook javaBook = new Book();

 

Constructors and Paradigms

The fact that constructors are not inherited from subclasses, is consistent with the syntax of the language, but at the same time contradicts the principles of object-oriented programming. In particular, the paradigms of reuse and abstraction seem to be violated. In fact, when the developer decided to implement the inheritance mechanism, he had to test its validity using the so-called is a relationship. To the question: “can an object instantiated by the candidate subclass also be considered an object of the candidate superclass?” he has indeed answered affirmatively.

In this particular case, a Java book is a book, and therefore it must have all the characteristics of a book. In particular, the constructor must also be reused. Not being able to inherit it, however, abstraction seems to have been violated. Instead, it is precisely in such a situation that Java demonstrates its consistency. In fact, we can add another property to the constructor definition:

  • any constructor (even the default constructor), always invokes a superclass constructor in its first statement.

For example, let’s add some convenience constructors to the Book and JavaBook classes

 

public class Book {
    public Book(){
        System.out.println("Book created!");
    }
}

public class JavaBook extends Book {
    public JavaBook() {
        System.out.println("Java Book created!");
    }
}

The reader, having learned that the constructors are not inherited, should conclude that the instance of a JavaBook, through a syntax of the type:

new JavaBook(); /* The assignment of a reference is not
  mandatory to instantiate an object */

would output the following string:

Java Book created!

The output instead will be:

Book created!
Java Book created!

In fact, the JavaBook constructor first invokes the constructor of the superclass Book and then it executes. The mandatory call to a superclass constructor is made using the super keyword, which is introduced below.

 

Constructors, super Keywords and Compiler Hidden Work

If the this keyword represents an implicit reference to the current object, we can define the super keyword as an implicit reference to the intersection between the current object and its superclass. This reference allows us to access the components of the superclass, and in particular its constructor.

In fact, in every constructor, there is always a call to the constructor of the superclass, through a special syntax that uses the super keyword, which is used as a method to pass parameters to. Again, if the instruction is not explicitly present, it will be added in the bytecode at compile time. For example, in the JavaBook class, the constructor will be modified by the compiler as follows:

public class JavaBook extends Book {
    public JavaBook() {
        // super(); //instruction added if not explicitly provided.
        System.out.println("Java Book created!");
    }
}

That is why the (parameterless) constructor of the Book class is invoked by the constructor of the JavaBook class. We can make the call to super explicit, but if we don’t, the compiler will implicitly add this statement. The call to a superclass constructor is therefore inevitable.

Note that the constructor of the Book class will also call the constructor of its superclass Object, by means of a super() statement added implicitly at compile time.

Now suppose we want to modify the superclass Book as we saw in the first example of this article:

public class Book {
    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle () {
        return title;
    }
}

This class can compile successfully, but its JavaBook subclass no longer:

JavaBook.java:1: error: constructor Book in class Book 
  cannot be applied to given types;
public class JavaBook extends Book {
       ^
  required: String
  found: no arguments
  reason: actual and formal argument lists differ in length
1 error

So, modifying the superclass, we introduce an error in the subclass. By interpreting the compiler’s error message, however, one can guess what happened: it all depends on the hidden work of the compiler.

In fact, the subclass constructor, through the implicit super() instruction, tried to invoke a constructor of the superclass that does not exist: the constructor without parameters. In fact, since we have explicitly added a constructor in the Book class, the compiler’s default constructor will no longer be implicitly added in it. The compiler, however, has inserted a call to the constructor without parameters of the superclass Book, as the first statement of the constructor of the subclass JavaBook (see code). But now the parameterless constructor (the one that was the default constructor) in the Book superclass is gone.

To fix the problem, the best solution seems to modify the JavaBook class as follows:

public class JavaBook extends Book {
    public JavaBook(String title) {
        super(title);
    }
}

With the previous syntax, we have explicitly invoked the superclass constructor which takes a string as input.

If we had multiple constructors in the superclass, we could choose which one to call. For example, if the Book class defined multiple constructors as follows:

public class Book {
    public Book(String title, String author) {
        this(title); // Call to the second constructor (see next section)
        setAuthor(author);
    }

    public Book(String title) {
        this.title = title;
    } 
    // rest of code omitted
}

the JavaBook class could call the most suitable constructor depending on the case:

public class JavaBook extends Book {
    public JavaBook(String title) {
        super(title);
    }

    public JavaBook(String title, String author) {
        super(title, author);
    }
    // rest of code omitted
}

 

Overload, Constructors, and the this Keyword

We now know that we can invoke a constructor of a superclass using the super keyword, but actually we can also use the this keyword to invoke the constructors of the same class. Here is a simple example of a constructor overloading that uses the this keyword as a call to a constructor method of the same class:

public class Customer {
    private String name;
    private String address;
    private int phoneNumber;
    public Customer() {
        // constructor explicitly inserted (this is not the default constructor)
    }
    public Customer(String name) {
        this.name = name;
    }
    public Customer(String name, String address) {
        this(name);
        this.address = address;
    }
    public Customer(String name, String address, int phoneNumber) {
        this(name, address);
        this.phoneNumber = phoneNumber;
    }
    // rest of code omitted
}

Note how the constructors invoke each other avoiding duplicating already written code.

In this way the reuse paradigm will be favored.

 

Clarification on how Constructors Call Each Other

The call via the super keyword to a superclass constructor or through the this keyword to a constructor of the same class, can only be used as the first instruction in a constructor. This implies that only one instruction between super or this will be present in a constructor. If we explicitly add the this statement, then the super statement cannot be placed in the same constructor. However, note that if a constructor calls another constructor using the this() command, this other constructor, in turn, either calls a third constructor via another this() statement, or calls a superclass constructor with the super() statement. In short, the superclass constructor will be called in any case sooner or later. For example, the following class that represents a character (font) to be used in an editor:

public class Character {
    private String type;
    private int size;

    public Character (String type) {
        this(type, 12);
    }

    public Character (String type, int size) {
        //Here a super() invocation will be inserted from the compiler
        setType(type);
        setSize(size);
    }
    //Accessor and mutator methods omitted...
}

declares a first constructor that invokes the second with the this() statement. In the second constructor, however, the compiler explicitly adds the super() statement as the first statement. The call to a superclass constructor (which in this case is the Object class) has only been postponed.

We cannot even call a constructor using the super or this keywords from an ordinary method. Only constructor can use these instructions.

 

Override and Constructors

We know that there is no constructor inheritance, but the instantiation of a subclass, thanks to the super statement, will always cause a call to a superclass constructor. In any case, since there is no inheritance, it makes no sense to speak of override of constructors, or constructor polymorphism. But there is a situation that concerns the constructors and the order in which they are executed with respect to the initialization of the attributes, to be taken into account, and which we will explain below.

Since the scenario is a bit complex to explain in the abstract, let’s get into the context of a code example. Let’s consider the following abstract class Tool:

abstract class Tool {
    public Tool () {
        doWork();
    }
    public void doWork() {
        System.out.println("Working...");
    }
}

This class declares a constructor that invokes the doWork method defined within the same class. Now consider the following Hammer class extending Tool and overriding the doWork method:

class Hammer extends Tool {
    String data ="nail";
    public Hammer () {
        //implicit call to super();
    }
    @Override
    public void doWork() {
        System.out.println("Hammering on "+ data);
    }
}

Finally, consider the following class that instantiates the Hammer subclass:

public class ToolsTest {
    public static void main(String[] args) {
        Tool tool = new Hammer();
    }
}

These classes are compiled correctly, but by launching the ToolsTest class, we will get the following output at runtime:

Hammering on null

in fact, the data variable is used before it can be initialized in the subclass, and therefore still has a null value. In particular, when we invoked the constructor of the Hammer class, as the first (implicit) instruction, the constructor of the Tool superclass was invoked, which in turn directly invokes the doWork method, which prints the message when the data variable had not yet been initialized.

In this case, we only got an incorrect output, but actually, the use of references with null values can easily lead to the runtime being interrupted by NullPointerException type exceptions. For example, if we modify the doWork method in the subclass as follows:

public void doWork() {
    if (!data.isEmpty()) {
        System.out.println("Hammering on "+ data);
    }
}

then the program would print the following output:

Exception in thread "main" java.lang.NullPointerException
    at Hammer.doWork(ToolsTest.java:17)
    at Tool.<init>(ToolsTest.java:3)
    at Hammer.<init>(ToolsTest.java:12)
    at ToolsTest.main(ToolsTest.java:24)

 

Conclusion

In this article, we have seen how a basic argument of the language such as constructors actually hides even complex scenarios. As usual, having a solid theoretical basis will allow us to manage all use cases without surprises. With the keywords this and super, we will be able to correctly manage reuse and abstraction paradigms even with constructors, although they are not inherited and consequently it is not possible to rewrite them in subclasses. We have also seen how important it is to know the compiler behavior. In fact, although the insertions of implicit code in the bytecode have been designed to facilitate learning and reduce the verbosity of the language, actually they can also lead to malfunctions of the code if not properly mastered.

 

Author’s Notes

This article is based on a few paragraphs from my English book “Java for Aliens“.

 

Leave a Reply