Index

Constructors and Initialization

Java Syntax

7.1 Constructor Declaration

In Java, a constructor is a special block of code used to initialize objects when they are created. Unlike regular methods, constructors:

Basic Syntax and Example

Here's a simple constructor that sets the initial values of an object's fields:

public class Person {
    String name;
    int age;

    // Constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

In the example above:

To create an object using this constructor:

Person p = new Person("Alice", 30);

This creates a Person object with the name "Alice" and age 30.

Click to view full runnable Code

public class Main {
    public static void main(String[] args) {
        Person p = new Person("Alice", 30);
        System.out.println("Name: " + p.name);
        System.out.println("Age: " + p.age);
    }
}

class Person {
    String name;
    int age;

    // Constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

How Constructors Differ from Methods

Constructors are often confused with methods, but they are fundamentally different:

Feature Constructor Method
Name Same as class name Any valid identifier
Return type None (not even void) Must have return type
Invocation Automatically during new Manually called

Reflection: Why Constructors Matter

Constructors are essential to creating well-formed objects. They enforce initialization logic, helping avoid partially constructed or invalid objects. For instance, if a BankAccount requires a non-zero starting balance, a constructor can enforce that rule.

By controlling how objects are created, constructors help maintain object integrity, support encapsulation, and make your code more robust and predictable.

Index

7.2 Overloaded Constructors

In Java, constructor overloading allows a class to have multiple constructors with different parameter lists. This gives you the flexibility to create objects with varying levels of detail or different initialization contexts.

Syntax and Example

Consider the following class with overloaded constructors:

public class Rectangle {
    int width;
    int height;

    // Constructor with two parameters
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    // Constructor with one parameter (square)
    public Rectangle(int side) {
        this.width = side;
        this.height = side;
    }

    // No-arg constructor (default values)
    public Rectangle() {
        this.width = 1;
        this.height = 1;
    }
}

You can now create Rectangle objects in different ways:

Rectangle r1 = new Rectangle(10, 5);   // Custom width and height
Rectangle r2 = new Rectangle(7);       // Square with side = 7
Rectangle r3 = new Rectangle();        // Default 1x1 rectangle
Click to view full runnable Code

public class Main {
    public static void main(String[] args) {
        Rectangle r1 = new Rectangle(10, 5);   // Custom width and height
        Rectangle r2 = new Rectangle(7);       // Square with side = 7
        Rectangle r3 = new Rectangle();        // Default 1x1 rectangle

        System.out.println("r1: " + r1.width + " x " + r1.height);
        System.out.println("r2: " + r2.width + " x " + r2.height);
        System.out.println("r3: " + r3.width + " x " + r3.height);
    }
}

class Rectangle {
    int width;
    int height;

    // Constructor with two parameters
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    // Constructor with one parameter (square)
    public Rectangle(int side) {
        this.width = side;
        this.height = side;
    }

    // No-arg constructor (default values)
    public Rectangle() {
        this.width = 1;
        this.height = 1;
    }
}

How Java Chooses the Right Constructor

When you instantiate an object with new, Java checks the constructor signatures and chooses the one that matches the number and type of arguments provided. This resolution is done at compile time, not runtime, and is based on method signature matching.

Reflection: Why Use Overloaded Constructors?

Overloaded constructors make your classes more flexible and user-friendly. They allow developers to initialize objects in different ways without needing to remember default values or create multiple setup methods.

For example, if you're building a User object, you might want one constructor for name and email, another for just the username, and a default constructor for anonymous users.

By offering multiple initialization paths, overloading improves code clarity, reusability, and maintainability while keeping object creation concise and consistent.

Index

7.3 Default and No-Arg Constructors

In Java, constructors are essential for initializing objects. There are two important types of constructors to understand early on: the default constructor and the no-argument (no-arg) constructor.

Default Constructor

A default constructor is a constructor automatically provided by the compiler only if you don't define any constructor in your class. It has no parameters and simply calls the superclass constructor.

Example:

public class Animal {
    // No constructors defined
    // Compiler adds: Animal() { super(); }
}

You can then create an object like:

Animal a = new Animal();

No-Arg Constructor

A no-arg constructor is a constructor that you write explicitly that takes no parameters. It may include custom initialization logic.

Example:

public class Animal {
    public Animal() {
        System.out.println("An animal is created.");
    }
}

Even though this constructor takes no arguments, it's not the compiler-generated defaultβ€”it's user-defined and won't be added automatically.

Important Distinction

If you define any constructor (with or without parameters), Java will no longer generate a default constructor. This means if you define a parameterized constructor and still want to allow object creation without arguments, you must explicitly write your own no-arg constructor.

Example:

public class Animal {
    public Animal(String name) {} // Custom constructor

    // No default or no-arg constructor now!
}
Click to view full runnable Code

public class Main {
    public static void main(String[] args) {
        // Using class with no constructor defined - compiler provides default constructor
        AnimalDefault animalDefault = new AnimalDefault();
        System.out.println("Created AnimalDefault");

        // Using class with explicit no-arg constructor
        AnimalNoArg animalNoArg = new AnimalNoArg();

        // Using class with parameterized constructor only
        AnimalParam animalParam = new AnimalParam("Buddy");
        // The following line would cause a compile error if uncommented because no no-arg constructor exists:
        // AnimalParam animalParamNoArg = new AnimalParam();
    }
}

// No constructor defined - compiler adds default constructor
class AnimalDefault {
    // no constructors explicitly defined
}

// Explicit no-arg constructor with custom logic
class AnimalNoArg {
    public AnimalNoArg() {
        System.out.println("An animal is created.");
    }
}

// Class with only parameterized constructor, no default or no-arg constructor
class AnimalParam {
    public AnimalParam(String name) {
        System.out.println("Animal created with name: " + name);
    }
}

Reflection

Define your own no-arg constructor when you want objects to be easily created without needing parameters, such as in frameworks, serialization, or testing. It ensures better control over object state and prevents unintended behavior due to missing constructors.

Index

7.4 Constructor Chaining

Constructor chaining allows one constructor to call another within the same class using the special keyword this(...). This helps eliminate code duplication by centralizing common initialization logic, which improves readability and maintainability.

Syntax of Constructor Chaining

To call another constructor from the current one, use this(...) as the first statement in the constructor.

public class Book {
    String title;
    int pages;

    // Constructor 1
    public Book() {
        this("Untitled", 0); // Calls Constructor 2
    }

    // Constructor 2
    public Book(String title) {
        this(title, 100); // Calls Constructor 3
    }

    // Constructor 3
    public Book(String title, int pages) {
        this.title = title;
        this.pages = pages;
    }
}

In this example:

Why Use Constructor Chaining?

Without chaining, each constructor would repeat field initialization:

public Book(String title) {
    this.title = title;
    this.pages = 100;
}

If you have three constructors initializing the same fields with different defaults, you'd have to repeat code multiple times. This repetition can introduce bugs if one constructor is updated and others are not.

By chaining constructors, only one place contains the actual logic for setting values, making the code easier to maintain and modify.

Click to view full runnable Code

public class Main {
    public static void main(String[] args) {
        Book b1 = new Book();
        System.out.println("b1: " + b1.title + ", pages: " + b1.pages);

        Book b2 = new Book("Java Programming");
        System.out.println("b2: " + b2.title + ", pages: " + b2.pages);

        Book b3 = new Book("Effective Java", 350);
        System.out.println("b3: " + b3.title + ", pages: " + b3.pages);
    }
}

class Book {
    String title;
    int pages;

    // Constructor 1
    public Book() {
        this("Untitled", 0); // Calls Constructor 3
    }

    // Constructor 2
    public Book(String title) {
        this(title, 100); // Calls Constructor 3
    }

    // Constructor 3
    public Book(String title, int pages) {
        this.title = title;
        this.pages = pages;
    }
}

Reflection

Constructor chaining encourages the DRY principle (Don't Repeat Yourself). It also gives flexibility by allowing multiple ways to create an object while ensuring consistent initialization. However, always use this(...) carefullyβ€”make sure it forms a clear and non-circular chain, or the compiler will reject it.

Index

7.5 Instance Initializer Blocks

In Java, instance initializer blocks are blocks of code enclosed in {} that are not part of any method or constructor, but are executed every time an object is created. These blocks run after field initializations and before any constructor code.

Syntax and Execution Order

Here's how you declare an instance initializer block:

public class Sample {
    int x;

    {
        // Instance initializer block
        System.out.println("Initializer block running...");
        x = 10;
    }

    public Sample() {
        System.out.println("Constructor running...");
    }
}

Output when creating a new object:

Initializer block running...
Constructor running...

This shows that the initializer block executes before the constructor, but after field initializers, in the order they appear in the code.

Field Initializer vs. Initializer Block vs. Constructor

public class OrderExample {
    int a = initializeA();

    {
        System.out.println("Instance initializer block");
    }

    public OrderExample() {
        System.out.println("Constructor");
    }

    int initializeA() {
        System.out.println("Field initializer");
        return 42;
    }
}

Output:

Field initializer
Instance initializer block
Constructor
Click to view full runnable Code

public class Main {
    public static void main(String[] args) {
        System.out.println("Creating Sample object:");
        Sample sample = new Sample();

        System.out.println("\nCreating OrderExample object:");
        OrderExample order = new OrderExample();
    }
}

class Sample {
    int x;

    {
        // Instance initializer block
        System.out.println("Initializer block running...");
        x = 10;
    }

    public Sample() {
        System.out.println("Constructor running...");
    }
}

class OrderExample {
    int a = initializeA();

    {
        System.out.println("Instance initializer block");
    }

    public OrderExample() {
        System.out.println("Constructor");
    }

    int initializeA() {
        System.out.println("Field initializer");
        return 42;
    }
}

When to Use Initializer Blocks

Instance initializer blocks can be helpful when:

However, they can also be confusing if overused or mixed with complex constructor logic. Most initialization should happen in constructors or field initializers. Use initializer blocks only when necessary, and keep them simple for readability.

Summary: Instance initializer blocks offer fine-grained control over object initialization order, but should be used judiciously to avoid obscuring object creation flow.

Index