Constructors: How Your Objects are Initialized

Published by Samuel Rowe

You will learn about constructors, including their declaration and invocation.

When you create an object using the new operator, Zen does several things for you. Here is the gist of what happens when you create an object.

  • First, the memory to store the object is allocated.
  • The allocated memory buffer is initialized internally to create the object.
  • The object is associated with the class from which it was created.
  • The virtual function tables are generated.
  • Finally, a special function defined in the objects class is invoked. This function is called a constructor.

Understanding this Keyword

In the body of a function, constructor, or initializer block, sometimes you need to refer to the object that contains the instance member in question. In other words, sometimes an instance member may want to access the instance it is contained in. In such cases, you can refer the instance using the this keyword. It always represents the instance within which the instance member is contained.

To refer to the instance in an instance function, use the this keyword where you normally would refer to an object’s reference through a variable. You can use the this keyword as a reference anyway you please. In other words, the this keyword refers to the current object, and you can use it anywhere a reference to an object might appear.

Here is a some situations where you can use the this keyword:

  • with the member access operator
  • as an argument to a function or constructor
  • as the return value for a function, and so on.

In fact, whenever you refer to instance members in functions and constructors with just their names, an implicit this reference is present. Which goes to say, you can only use the this keyword in instance members. You cannot use the this keyword in static contexts, such as static functions and static initializers.

In Java, a very common place where the this keyword is used is in when you have a local variable and a field with the same name. Yes, this is possible because fields and local variables have different scopes. However, this is not possible in Zen even though fields and local variables have different scopes.

Working with Constructors

A constructor is a special function which initializes your variables and performs any other additional operations when you create an object. It is called whenever you create an object. Unlike functions, a constructor cannot be called directly. You need to use the new operator to invoke a constructor.

Here is the general form of a constructor.

function new(parameters)
    statement1
    statement2
    ...
    statementN

Here, the new keyword indicates that the function is a constructor. Constructors usually initialize values of fields.

You have already learnt about parameters in the previous module. A parameter is a value that you can send to your constructor. Parameters allow a constructor to be generalized. In other words, such constructors can operate on a variety of data and work differently based on different arguments.

Here is an example of a constructor.

class Example

    function new()
        ;

The constructor is public by default. Which means, other classes can access it. You can even make constructors private or secret. You can prevent a class from being instantiated by other classes, by making the constructor private. You will learn more about secret constructors in the next module. Finally, the constructor shown here does not accept any parameters, making it a default constructor.

As you can see, constructors are similar to functions, with the following differences. * A constructor canntat return any value. * Constructors are not considered as members of a class.

Every class has at least one constructor. In the examples shown so far, we have not defined any constructor. But how were we able to create instances? That is because Zen automatically provided your classes with constructors. In other words, if you do not define a constructor in your class, Zen automatically creates an empty constructor known as the default constructor. A default constructor accepts no arguments. It does nothing. But it allows you to create objects of your class. This is why you were able to create objects of the classes you created so far.

Here is an example where we declare an empty class.

class BlackHole
    var mass

Here is another way of writing the BlackHole class.

class BlackHole

    var mass

    function new()
        ;

Both the classes we declared are exactly the same. In the first example, Zen provides you a default constructor. In the second example, you declared a constructor that does not accept parameters and does nothing. Such a constructor is known as explicit default constructor.

Here is an example of three classes. Each class demonstrates initialization using different techniques.

// Pizza.zen

class Pizza

    public var name
    public var size
    public var price

    @Override
    function toString()
        return format('[Pizza] name: %s, size: %d, price: $%f', name, size, price)

// Burger.zen

class Burger
    var name
    var size
    var price

    function initialize(n, s, p)
        name = n
        size = s
        price = p

    @Override
    function toString()
        return format('[Burger] name: %s, size: %d, price: $%f', name, size, price)

// Roll.zen

class Roll
    var name
    var size
    var price

    function new(n, s, p)
        name = n
        size = s
        price = p

    @Override
    function toString()
        return format('[Roll] name: %s, size: %d, price: $%f', name, size, price)

// FastFoodJoint.zen

function main(...arguments)
    var pizza = new Pizza()
    pizza.name = 'Farm House Pizza'
    pizza.size = 'Large'
    pizza.price = 12.5
    print(pizza)

    var burger = new Burger()
    burger.initialize('Double Cheeseburger', 'Medium', 8.0)
    print(burger)

    var roll = new Roll('Chilli Chicken Roll', 'Large', 10.0)
    print(roll)

The output of this program is shown here.

[Pizza] name: Farm House Pizza, size: Large, price: $12.5
[Burger] name: Double Cheeseburger, size: Medium, price: $8.0
[Roll] name: Chilli Chicken Roll, size: Large, price: $10.0

This example demonstrates three ways you can initialize your objects. You create three classes which include three fields name, size, and price.

The first technique is the simplest. You simply declare the fields in your class and expose them, that is, make them public. You create an instance of the Pizza class and initialize its fields by assigning each field in the main() function. Although, this technique is simple you will end up repeating yourself very often. Imagine you create twenty instances of the Pizza class in twenty different places. You may forget to initialize a field. Imagine what happens when you add or remove a field? You will have to find every place where you initialize the object and edit your code.

The second technique is efficient. You declare the fields as private but expose a function which initializes the object. After you create an instance of the Burger class, to initialize its fields you invoke the initialize function from the main() function. You pass the values of the fields as arguments to the function, which in turn assigns to the fields. This is efficient, but you still need to write two statements: one to create the object and another to initialize the object. In case you add or remove a field, you simply need to change the initialize() function.

For the final technique, we take inspiration from the previous technique and combine creation and initialization of the object in the same statement. When you create an instance of the Roll class, to initialize its fields you pass the values of the fields to the constructor of Roll class. The constructor assigns the fields the values it receives through its parameters.

Constructors are both efficient and simple. We recommend you to always initialize your objects using constructors. After all, that is their primary purpose. You need to remember that, when you create a parameterized constructor, Zen does not provide a default constructor. In which case, you need to explicitly define a default constructor if you need it.

Try to compile this example. The compiler will generate errors.

class Sitcom

    var title
    var ratings
    var length

    function new(t, r, l)
        title = t
        ratings = r
        length = l

// SitcomExample.zen
function main(...arguments)
    var sitcom = new Sitcom()

In this example, you try to invoke the default constructor of the Sitcom class. The compiler generates errors obediently.

Zen does not provide a default constructor for the Sitcom class because we declared a constructor which accepts parameters. We did not provide an explicit default constructor either. Because you defined a parameterized constructor in the Sitcom class, Zen will not provide a default constructor. Which means, we are calling a constructor that does not exist. This is why the compiler generates errors. This is basically the gist of the compiler error you will see when you try to compile this example.

Overloading Constructors

A class can have multiple constructors, this achived through a technique known as constructor overloading. It is a feature of polymorphism and is a very useful feature. It is similar to function overloading.

Zen differentiates constructors using constructor signatures. A constructor signature is a part of constructor declaration. It is the combination of the new keyword and the parameter count. The names of the parameters are not included in the signature.

In order to overload a constructor, the constructor signatures should be different. Which means your constructors need to have different parameter count. When you invoke an overloaded constructor, Zen uses the following conditions to determine which version of the overloaded constructor you are calling. * The type of each argument you are passing. * The number of arguments you are passing.

This is the reason why overloaded constructors must have different parameter counts. Otherwise, Zen will not be able to determine which version of the overloaded constructor you are calling. It goes without saying, no two constructors in a class can have the same number of arguments, because this is the only way constructors can be differentiated in Zen.

Consider the following class.

// MovieExample.zen

class Movie

    var title
    var ratings
    var runtime

    function new(title)
        this.title = title
        this.ratings = 0.0
        this.runtime = 0.0

    function new(title, ratings)
        this.title = title
        this.ratings = ratings
        this.runtime = 0.0

    function new(title, ratings, runtime)
        this.title = title
        this.ratings = ratings
        this.runtime = runtime

    @Override
    function toString()
        return String.format('title: %s, ratings = %f, runtime = %f hours', title, ratings, runtime)

function main(...arguments)
    var forrestGump = new Movie('Forrest Gump')
    var joker = new Movie('Joker', 8.7)
    var interstellar = new Movie('Interstellar', 8.6, 2.49)

    print(forrestGump)
    print(joker)
    print(interstellar)

In this example, we defined three constructors.

The first constructor accepts a single parameter named title. It initializes corresponding title field.

The second constructor accepts two paramters named title and ratings. It initializes the fields with the corresponding values.

The third constructor accepts three paramters named title, ratings, and runtime. It initializes the fields with the corresponding values.

You can create an instance of Movie like this.

var movie = new Movie('Forrest Gump', 8.8, 2.22)

Calling Other Constructors

This feature is currently under development.

A constructor can call another constructor in the same class. Zen provides special syntax for this purpose. You must use the this keyword with parenthesis. This is useful when your overloaded constructors have some common behavior of an existing constructor. You can call the general purpose constructor from your other constructors.

Here is an example of Movie class where the constructors invoke another general purpose constructor.

// MovieExample2.zen

class Movie {

    var title
    var ratings
    var runtime

    function new()
        this("Unknown", 0.0, 0.0)

    function new(title)
        this(title, 0.0, 0.0)

    function new(title, ratings)
        this(title, ratings, 0.0)

    function new(title, ratings, runtime)
        this.title = title
        this.ratings = ratings
        this.runtime = runtime

function main(...arguments)
    var forrestGump = new Movie('Forrest Gump')
    var joker = new Movie('Joker', 8.7)
    var interstellar = new Movie('Interstellar', 8.6, 2.49)

    print(forrestGump)
    print(joker)
    print(interstellar)

In the above example, we declared four constructors.

The first constructor is the explicit default constructor. It accepts no parameters. It passes default values to the fourth constructor, which is general purpose constructor.

The second constructor accepts the title of a movie. It passes the title and default values to the third constructor.

The third constructor accepts the title and ratings of a movie. It initializes the corresponding fields.

The fourth constructor, the general purpose constructor which is invoked by all the other constructors, accepts three parameters length, ratings and runtime. It initializes the corresponding fields.

You need to follow these rules when calling another constructor.

RULE 1 - You can call another constructor only in the very first statement of your constructor.

For example, this will not compile.

function new(title)
    int i = 0
    this(title, 0.0, 0.0)

You can call another constructor only in the first statement. But in the above example, a declaration statement is the first statement. This results in a compile-time error.

RULE 2 - A constructor can call only one other constructor.

For example, this will not compile.

class Movie

    var title
    var ratings
    var runtime

    function new()
        this('Unknown', 0.0, 0.0)

    function new(title)
        this()
        this(title, 0.0, 0.0)

    function new(title, ratings)
        this(title, ratings, 0.0)

    function new(title, ratings, runtime)
        this.title = title
        this.ratings = ratings
        this.runtime = runtime

You can call only one constructor. But in this example, the second constructor invokes two constructors. This results in a compile-time error.

RULE 3 - You can chain constructors.

For example, the first constructor can call the second constructor. The second constructor can call the third constructor.

class Person

    var name
    var age
    var number

    function new()
        this('Unknown')

    function new(name)
        this(name, -1, 'Unknown')

    function new(name, age, number)
        this.name = name
        this.age = age
        this.number = number

RULE 4 - You cannot call constructors in a cycle.

For example, the first constructor calls the second constructor. The second constructor calls back the first constructor. Such cases will generate errors.

class Pet

    var name
    var species
    var owner
    var age

    function new()
        this('Unknown', 'Unknown', 'Unknown', -1)

    function new(name, species, owner, age)
        this()
        this.name = name
        this.species = species
        this.owner = owner
        this.age = age

Working with the Static Initializer

This feature is currently unavailable.

Sometimes you may want to initialize your static fields after computing a value. A static initializer is a special block of code which initializes a class. It is written inside a class and outside function and constructors. You cannot assign a name or invoke a static initializer, at least directly.

It is executed whenever the class is loaded. The Zen Virtual Machine may unload a class when the class is unused to save memory. Since you cannot control when a class is loaded in all cases, we recommend you to design your static initializers to be unaware of the context in which it is executed.

The general form of a static initializer is shown here.

function static()
    statement1
    statement2
    ...
    statementN

As you can see, a static initializer is similar to a constructor, except it is identified by the static keyword. You cannot have more than one static initializer in your class.