Hi! Today we'll consider a very important topic that concerns our objects. Without exaggeration, we can say you'll use this topic in real life every day! We're talking about Java Constructors. This may be the first time you're hearing this term, but you've actually already used constructors. You just didn't realize it :) We convince ourselves of this later.

What in the world are constructors and why are they needed?

Let's consider two examples.

public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 378;

   }
}
We created our car, and set its model and maximum speed. But the Car object would obviously not have 2 fields in a real project. For example, it might have 16 fields!

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

   }

}
We've created a new Car object. There's one problem: we have 16 fields, but we only initialized 12! Look at the code now and try to find the fields we forgot! Not so easy, huh? In this situation, a programmer can easily make a mistake and fail to initialize some field. As a result, the program will behave incorrectly:

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

       System.out.println("Model: Bugatti Veyron. Engine volume: " + bugatti.engineVolume + ". Trunk volume: " + bugatti.trunkVolume + ". Cabin material: " + bugatti.cabinMaterial +
       ". Wheel width: " + bugatti.wheels + ". Purchased in 2018 by Mr. " + bugatti.ownerLastName);

   }

}
Console output: Model: Bugatti Veyron. Engine volume: 6.3. Trunk volume: 0. Cabin material: null. Wheel width: 0. Purchased in 2018 by Mr. null Your buyer, who gave up $2 million for the car, obviously won't like being called "Mr. null"! But seriously, the bottom line is that our program created an object incorrectly: a car with a wheel width of 0 (i.e. no wheels at all), a missing trunk, a cabin made of an unknown material, and above all, an undefined owner. You can only imagine how such a mistake can "go off" when the program is running! We need to avoid such situations somehow. We need to restrict our program: when creating a new Car object, we want the fields, such as the model and maximum speed, to always be specified. Otherwise, we want to prevent the creation of the object. Constructors handle this task with ease. They got their name for a reason. The constructor creates a kind of class "skeleton" that each new object must match. For convenience, let's go back to the simpler version of the Car class with two fields. Considering our requirements, the Car class's constructor will look like this:

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}

// And creating an object now looks like this:

public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 378);
}
Note how a constructor is declared. It's similar to a regular method, but it doesn't have a return type. Moreover, the constructor specifies the class name (Car) starting with an uppercase letter. Additionally, the constructor is used with a keyword that is new for you: this. The keyword this is for indicating a particular object. The code in the constructor

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
can be interpreted almost verbatim: "The model for this car (the one we're creating now) is the model argument passed to the constructor. The maxSpeed for this car (the one we're creating) is the maxSpeed argument passed to the constructor." And that's just what happens:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car("Bugatti Veyron", 378);
       System.out.println(bugatti.model);
       System.out.println(bugatti.maxSpeed);
   }

}
Console output: Bugatti Veyron 378 The constructor correctly assigned the required values. You may have noticed that a constructor is very similar to an ordinary method! So it is. A constructor is really a method, but with specific features :) Just like with methods, we passed arguments to our constructor. And just like calling a method, calling a constructor won't work unless you specify them:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car(); // Error!
   }
  
}
You can see that the constructor accomplishes what we were trying to achieve. Now you can't create a car without a speed or model! The similarity between constructors and methods doesn't end here. Just like methods, constructors can be overloaded. Imagine you have 2 pet cats at home. You got one of them as a kitten. But the second one you took in from the street when it was already grown, and you don't know exactly how old it is. In this case, we want our program to be able to create two kinds of cats: those with a name and age (for the first cat), and those with only a name (for the second cat). For this, we will overload the constructor:

public class Cat {

   String name;
   int age;

   // For the first cat
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For the second cat
   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
In addition to the original constructor with "name" and "age" parameters, we added one more with only a name parameter. In precisely the same way that we overloaded methods in previous lessons. Now we can create both kinds of cats :)
Why do we need constructors? - 2
Remember that at the beginning of the lesson we said that you've already used constructors without realizing it? We meant what we said. The fact is that every class in Java has what is called a default constructor. It doesn't take any arguments, but it is invoked every time you create any object of any class.

public class Cat {

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
At first glance, it's invisible. We created an object, so what? Where is the constructor doing anything here? To see it, let's explicitly write an empty constructor for the Cat class. We'll display some phrase inside it. If the phrase is displayed, then the constructor was invoked.

public class Cat {

   public Cat() {
       System.out.println("A cat has been created!");
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
Console output: A cat has been created! There's the confirmation! The default constructor is always invisibly present in your classes. But you need to know one more thing about it. The default constructor is eliminated from a class once you create a constructor with arguments. In fact, we've already seen proof of this above. It was in this code:

public class Cat {

   String name;
   int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); //Error!
   }
}
We couldn't create a Cat without a name and age, because we declared a Cat constructor with string and int parameters. This caused the default constructor to immediately vanish from the class. So be sure to remember that if you need several constructors in your class, including a no-argument constructor, you'll have to declare it separately. For example, suppose we are creating a program for a veterinary clinic. Our clinic wants to do good deeds and help homeless kittens whose names and ages are unknown. Then our code should look like this:

public class Cat {

   String name;
   int age;

   // For cats with owners
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For street cats
   public Cat() {
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCat = new Cat();
   }
}
Now that we have written an explicit default constructor, we can create both types of cats :) As with any method, the order of arguments passed to a constructor is very important. Let's swap the name and age arguments in our constructor.

public class Cat {

   String name;
   int age;

   public Cat(int age, String name) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 10); // Error!
   }
}
An error! The constructor clearly stipulates that when a Cat object is created, it must be passed a number and a string, in this order. So, our code doesn't work. Be sure to remember this and keep it in mind when declaring your own classes:

public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}

public Cat(int age, String name) {
   this.age = age;
   this.name = name;
}
These are two totally different constructors! If we were to express in a single sentence the answer to the question "Why do I need a constructor?", we might say, "To ensure that objects always have a valid state". When you use constructors, all your variables will be correctly initialized. Your programs won't have any cars with a speed of 0 or any other "invalid" objects. Their main benefit is for the programmer. If you initialize fields manually (after creating an object), there's a great risk that you'll miss something and introduce a bug. But this won't happen with a constructor: if you fail to pass all the required arguments or you pass the wrong types of arguments, the compiler will immediately register an error. We must also separately say that you should not put your program's logic inside a constructor. This is what methods are for. Methods are where you should define all the required functionality. Let's see why adding logic to a constructor is a bad idea:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factory is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
We have a CarFactory class that describes the car factory. Inside the constructor, we initialize all the fields and include some logic: we display some information about the factory. It seems like there's nothing bad about this. The program works fine. Console output: Our car factory is called Ford It was founded 115 years ago Since that time, it has produced 50000000 cars On average, it produces 434782 cars per year But we've actually laid a time-delayed mine. And this sort of code can very easily lead to errors. Suppose that now we are talking not about Ford, but about a new factory called "Amigo Motors", which has existed for less than a year and has produced 1000 cars:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factor is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
Console output: Our car factory is called Amigo Motors Exception in thread "main" java.lang.ArithmeticException: / by zero It was founded 0 years ago Since that time, it has produced 1000 cars at CarFactory.(CarFactory.java:15) at CarFactory.main(CarFactory.java:23) Process finished with exit code 1Boom! The program ends with some kind of incomprehensible error. Can you try to guess the cause? The problem is in the logic we put into the constructor. More specifically, this line:

System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
Here you're performing a calculation and dividing the number of cars produced by the factory's age. And since our factory is new (i.e. it's 0 years old), we divide by 0, which we can't do in math. Consequently, the program terminates with an error.

What should we have done?

Put all the logic in a separate method. Let's call it printFactoryInfo(). You can pass a CarFactory object to it as an argument. You can put all the logic there, and simultaneously handle potential errors (like ours involving zero years). To each his own. Constructors are needed to set valid object state. We have methods for business logic. Don't mix one with the other. To reinforce what you learned, we suggest you watch a video lesson from our Java Course

Using the super Keyword to Call Superclass Constructors

This section explores the super keyword, its role in constructor chaining, and Java's default behavior when no superclass constructor is explicitly called.

The super keyword in Java is used to refer to the immediate superclass of a subclass. It allows a subclass constructor to explicitly call a constructor from its superclass, ensuring that the superclass is properly initialized before the subclass adds its own initialization.

Example: Calling the Superclass Constructor


class Animal {
    Animal(String name) {
        System.out.println("Animal constructor called with name: " + name);
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name);  // Calling the superclass constructor
        System.out.println("Dog constructor called with name: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy");
    }
}

Output:

  • Animal constructor called with name: Buddy
  • Dog constructor called with name: Buddy

In this example, the super(name) call ensures that the Animal class is initialized before the Dog class.

Importance of Calling super() as the First Statement

In Java, the call to a superclass constructor using the super keyword must be the first statement in a subclass constructor. This rule ensures that the superclass is fully initialized before the subclass constructor executes its own logic.

Incorrect Usage Example:


class Animal {
    Animal() {
        System.out.println("Animal constructor");
    }
}

class Dog extends Animal {
    Dog() {
        System.out.println("Initializing Dog"); // Error: must call super() first
        super();
    }
}

Error: Constructor call must be the first statement in a constructor

Correct Usage Example:


class Animal {
    Animal() {
        System.out.println("Animal constructor");
    }
}

class Dog extends Animal {
    Dog() {
        super();  // Correct: super() is the first statement
        System.out.println("Dog constructor");
    }
}

Output:

  • Animal constructor
  • Dog constructor

Default Behavior When No super() Call is Explicitly Made

If a subclass constructor does not explicitly call a superclass constructor using super(), Java automatically inserts a call to the no-argument constructor of the superclass.

Example: Implicit super() Call


class Animal {
    Animal() {
        System.out.println("Animal no-arg constructor");
    }
}

class Dog extends Animal {
    Dog() {
        // Implicit call to super() happens here
        System.out.println("Dog constructor");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

Output:

  • Animal no-arg constructor
  • Dog constructor

However, if the superclass does not have a no-argument constructor, the compiler will throw an error unless a specific superclass constructor is called explicitly.

Example: Error When No super() is Called


class Animal {
    Animal(String name) {
        System.out.println("Animal constructor with name: " + name);
    }
}

class Dog extends Animal {
    Dog() {
        // Error: No implicit call to Animal(String name)
        System.out.println("Dog constructor");
    }
}

Compilation Error: constructor Animal in class Animal cannot be applied to given types;

Solution: Explicitly Call the Superclass Constructor


class Animal {
    Animal(String name) {
        System.out.println("Animal constructor with name: " + name);
    }
}

class Dog extends Animal {
    Dog() {
        super("Buddy");  // Explicitly calling the superclass constructor
        System.out.println("Dog constructor");
    }
}

Private Constructors: Restricting Object Creation

This section explores how private constructors restrict object creation, their role in design patterns like Singleton, and provides practical examples to solidify understanding.

A private constructor prevents external classes from instantiating a class. This is useful when object creation needs to be controlled or restricted, ensuring that the class cannot be instantiated from outside its own code.

Use Cases for Private Constructors:

  • **Utility Classes:** Prevent instantiation of classes that only contain static methods.
  • **Singleton Pattern:** Ensure only one instance of a class exists.
  • **Immutable Classes:** Control object creation for immutable instances.

Example: Private Constructor in a Utility Class

public class MathUtils {
    // Private constructor prevents instantiation
    private MathUtils() {
        throw new UnsupportedOperationException("Utility class cannot be instantiated");
    }

    public static int add(int a, int b) {
        return a + b;
    }

    public static int subtract(int a, int b) {
        return a - b;
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        int sum = MathUtils.add(5, 3);
        System.out.println("Sum: " + sum);
        // MathUtils utils = new MathUtils(); // Error: Constructor is private
    }
}

Attempting to create an instance of MathUtils will result in a compilation error because its constructor is private.

Private Constructors in the Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is typically achieved by using a private constructor to restrict object creation and a static method to control access.

Example: Singleton Pattern Implementation

public class Singleton {
    // Private static instance of the class
    private static Singleton instance;

    // Private constructor prevents instantiation
    private Singleton() {
        System.out.println("Singleton instance created");
    }

    // Public method to provide access to the instance
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();  // Creates the instance
        Singleton s2 = Singleton.getInstance();  // Returns the existing instance

        System.out.println(s1 == s2);  // Output: true
    }
}

Explanation: The private constructor prevents external instantiation, and the getInstance() method ensures that only one instance of Singleton exists.

Advantages of Using Private Constructors in Singleton:

  • Controlled Instantiation: Prevents multiple instances.
  • Lazy Initialization: The instance is created only when needed.
  • Global Access: Provides a single point of access to the object.

More Examples Demonstrating Private Constructors

Example: Private Constructor for Immutable Class

public final class Configuration {
    private static final Configuration instance = new Configuration("config.properties");
    private String configFilePath;

    // Private constructor
    private Configuration(String path) {
        this.configFilePath = path;
    }

    public static Configuration getInstance() {
        return instance;
    }

    public String getConfigFilePath() {
        return configFilePath;
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Configuration config = Configuration.getInstance();
        System.out.println("Config Path: " + config.getConfigFilePath());
    }
}

Explanation: This design ensures that Configuration is immutable and has only one instance throughout the application.

Example: Preventing Subclassing with Private Constructor


public class FinalUtility {
    private FinalUtility() {
        throw new UnsupportedOperationException("Cannot be instantiated");
    }

    public static void printMessage() {
        System.out.println("Utility method called");
    }
}

// Attempting to extend FinalUtility will result in an error

Marking a constructor as private, combined with the final keyword, prevents subclassing and instantiation.