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

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.
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 CourseUsing 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.
GO TO FULL VERSION