Why do we need constructors?

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 constructors.

pic1

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 :)

pic2

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 1

Boom! 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.

Was published on CodeGym blog .

Comments (1)

Mark's photo

Interesting article, I think it's good that you emphasize leaving objects in a valid state.

The default constructor is eliminated from a class once you create a constructor with arguments.

Very small nitpick: also when you create a constructor without arguments, which you may want to do to make it non-public. For example if the object should be constructed by some factory, or is a singleton.

And this sort of code can very easily lead to errors.

Maybe this is worth it's own thread, but I'm curious why people say logic in constructors is bad.

I'm not very convinced by the example. It'd still be a bug if the code were moved to printFactoryInfo.

Two reasons I can think of to move logic to a static method that calls the constructor:

  • You want to be able to control something other than the new object, i.e. an Optional<MyThing> or null or a Pair. Or a instance rather than a newly constructed one. Or not wanting that now, but keeping open the option to ever do that in the future.
  • You have multiple ways to construct the object, and naming them (with static methods) is clearer than just overloads.

But other than that, I've never been sure why people are against it...