- Understand constructor chaining with this() and super()
- Learn the execution order of chained constructors
- Master best practices for constructor chaining
Constructor Chaining in Java
Picture this: you've got three constructors in your class, and they all need to set up the object in slightly different ways. Do you copy-paste the common initialization code into all three? No way. That's where constructor chaining saves the day by letting one constructor call another.
What is Constructor Chaining?
Constructor chaining occurs when one constructor explicitly calls another constructor. This can happen within the same class (using this()) or in the parent class (using super()).
Within the Same Class (this())
public class Person {
private String name;
private int age;
private String address;
// Constructor with all parameters
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
// Constructor chaining to the full constructor
public Person(String name, int age) {
this(name, age, "Unknown"); // Calls the constructor above
}
// Another chained constructor
public Person(String name) {
this(name, 18, "Unknown"); // Calls the two-parameter constructor
}
}
To Parent Class (super())
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Dog extends Animal {
private String breed;
// Constructor chaining to parent class
public Dog(String name, int age, String breed) {
super(name, age); // Calls parent constructor first
this.breed = breed;
}
}
Rules of Constructor Chaining
1. Constructor Call Must Be First Statement
The this() or super() call must be the very first statement in the constructor.
public class Example {
public Example() {
System.out.println("This won't work");
// this(10); // Compilation error: must be first
}
public Example(int value) {
this(); // OK: first statement
System.out.println("Value: " + value);
}
}
2. Cannot Use Both this() and super()
A constructor cannot call both this() and super().
public class InvalidExample extends Parent {
public InvalidExample() {
// this(); // Cannot use both
// super(); // Cannot use both
}
}
3. No Circular Constructor Calls
Constructors cannot call each other in a circular manner.
public class CircularExample {
public CircularExample() {
this(10); // OK
}
public CircularExample(int value) {
this(); // Compilation error: circular call
}
}
Constructor Execution Order
When constructor chaining occurs, constructors execute in a specific order:
- Parent class constructor (if
super()is called or implicit) - Current class constructor body
Example Execution Order
class Parent {
public Parent() {
System.out.println("Parent constructor");
}
}
class Child extends Parent {
private int value;
public Child() {
// super(); // Implicit call to parent constructor
System.out.println("Child constructor");
}
public Child(int value) {
this(); // Calls Child() first
this.value = value;
System.out.println("Child constructor with value: " + value);
}
}
// Output when creating Child(10):
// Parent constructor
// Child constructor
// Child constructor with value: 10
Default Constructor Chaining
If a constructor doesn't explicitly call this() or super(), Java automatically inserts super() as the first statement.
public class Example {
public Example() {
// Java automatically inserts: super();
System.out.println("Example constructor");
}
}
Constructor Chaining Patterns
1. Telescoping Constructor Pattern
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
2. Builder Pattern Alternative
public class Computer {
private String cpu;
private int ram;
private int storage;
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
}
public static class Builder {
private String cpu = "Intel i5";
private int ram = 8;
private int storage = 256;
public Builder cpu(String cpu) { this.cpu = cpu; return this; }
public Builder ram(int ram) { this.ram = ram; return this; }
public Builder storage(int storage) { this.storage = storage; return this; }
public Computer build() { return new Computer(this); }
}
}
3. Validation in Constructors
public class Email {
private String address;
public Email(String address) {
this(validateAndNormalize(address));
}
private Email(String validatedAddress) {
this.address = validatedAddress;
}
private static String validateAndNormalize(String address) {
if (address == null || !address.contains("@")) {
throw new IllegalArgumentException("Invalid email address");
}
return address.toLowerCase().trim();
}
}
Inheritance and Constructor Chaining
Parent Constructor Calls
When a subclass constructor is called, it must ensure the parent class is properly initialized.
class Vehicle {
private String make;
public Vehicle(String make) {
this.make = make;
System.out.println("Vehicle created: " + make);
}
}
class Car extends Vehicle {
private String model;
public Car(String make, String model) {
super(make); // Must call parent constructor first
this.model = model;
System.out.println("Car created: " + make + " " + model);
}
}
Default Super() Call
If a subclass constructor doesn't call super(), Java automatically calls the parent's no-argument constructor.
class Parent {
public Parent() {
System.out.println("Parent default constructor");
}
}
class Child extends Parent {
public Child() {
// super(); // Automatically inserted by Java
System.out.println("Child constructor");
}
}
Common Mistakes and Solutions
1. Forgetting to Call Super()
class Parent {
private String name;
public Parent(String name) {
this.name = name;
}
}
class Child extends Parent {
public Child() {
// super(); // Compilation error: no default constructor in Parent
}
}
Solution: Either provide a default constructor in Parent or call super(name) in Child.
2. Incorrect Constructor Call Order
class Example {
private int value;
public Example(int value) {
this.value = value;
this(); // Compilation error: must be first statement
}
public Example() {
// Default initialization
}
}
Solution: Move the this() call to the beginning of the constructor.
Wrapping Up
Constructor chaining keeps your initialization code DRY (Don't Repeat Yourself). Use this() to call other constructors in the same class, and super() for parent class constructors. Just remember: the chaining call has to be first, and you can't use both in the same constructor. Get these patterns down, and your object initialization will be clean and maintainable.
