Constructors

Initializing Objects in Java

← Back to Index

What is a Constructor?

Imagine you're building a car in a factory. Before the car leaves the factory, it needs to be set up: install the engine, paint it a color, add seats, fill it with fuel. A constructor is like that factory setup process - it prepares an object before you can use it!

The concept of constructors dates back to the earliest object-oriented languages. Simula 67, the first OO language, introduced the idea of initialization code that runs when an object is created. Java inherited and refined this concept, making constructors a fundamental part of object lifecycle management. Unlike some languages where initialization is optional or implicit, Java enforces that every object must go through its constructor before being used.

Constructors serve a crucial role in the principle of "object validity" - the idea that an object should never exist in an invalid state. By requiring initialization through constructors, Java ensures that objects are properly set up with valid data from the moment they come into existence. This is foundational to writing robust, bug-free code.

Real-World Analogy

Think about opening a new bank account:

  1. You go to the bank and say "I want to create a new account" (new BankAccount())
  2. The bank clerk sets up your account with:
    • Account number
    • Your name
    • Starting balance (maybe $0)
    • Account type
  3. Now your account is ready to use!

The constructor is step 2 - it sets up the initial state of your object. Without it, your object would be empty and useless!

Simple Example - See It in Action

public class Dog {
    String name;
    int age;

    // This is a CONSTRUCTOR - it runs when you create a Dog object
    public Dog(String dogName, int dogAge) {
        name = dogName;
        age = dogAge;
        System.out.println("A dog named " + name + " was born!");
    }
}

// Creating (constructing) a dog object
Dog myDog = new Dog("Buddy", 3);
// Output: "A dog named Buddy was born!"

// The constructor already set up the values!
System.out.println(myDog.name);  // "Buddy"
System.out.println(myDog.age);   // 3

What Happens Step by Step?

  1. Line 1: Dog myDog = new Dog("Buddy", 3);
    • Java sees the new keyword
    • Allocates memory for a new Dog object
    • Automatically calls the constructor with "Buddy" and 3
  2. Inside Constructor:
    • Sets name = "Buddy"
    • Sets age = 3
    • Prints "A dog named Buddy was born!"
  3. After Constructor:
    • Object is fully set up and ready to use
    • Variable myDog now refers to this object
Constructor Special Rules
  • Same name as the class: Constructor must be named exactly like the class (Dog class → Dog() constructor)
  • No return type: Not even void! It just doesn't have one.
  • Called automatically: You never call a constructor directly - Java calls it when you use new
  • Can be overloaded: You can have multiple constructors with different parameters
  • Always runs first: Before any other method can be called on the object

Constructor vs Regular Method

public class Example {
    // CONSTRUCTOR - no return type, same name as class
    public Example() {
        System.out.println("Constructor called!");
    }

    // REGULAR METHOD - has return type, different name
    public void regularMethod() {
        System.out.println("Regular method called!");
    }
}

Example obj = new Example();  // Constructor runs automatically
obj.regularMethod();  // You must call this explicitly

Now let's explore different types of constructors...

Under the Hood: Object Creation in the JVM

Understanding what happens inside the JVM when you create an object helps explain why constructors work the way they do.

Memory Allocation and Initialization Sequence

┌─────────────────────────────────────────────────────────────────┐
│                   Object Creation Steps                          │
├─────────────────────────────────────────────────────────────────┤
│  1. new Dog("Buddy", 3)                                         │
│     ↓                                                           │
│  2. JVM allocates memory in Heap                                │
│     ┌──────────────────────────┐                                │
│     │  Dog Object (Heap)       │                                │
│     │  ├─ Object Header        │  ← Class pointer, hash, GC    │
│     │  ├─ name = null          │  ← Default values first       │
│     │  └─ age = 0              │                                │
│     └──────────────────────────┘                                │
│     ↓                                                           │
│  3. Instance initializers run (if any)                          │
│     ↓                                                           │
│  4. Constructor body executes                                   │
│     ┌──────────────────────────┐                                │
│     │  Dog Object (Heap)       │                                │
│     │  ├─ Object Header        │                                │
│     │  ├─ name = "Buddy"       │  ← Constructor set values     │
│     │  └─ age = 3              │                                │
│     └──────────────────────────┘                                │
│     ↓                                                           │
│  5. Reference returned to caller                                │
│     myDog ──────────────────────→ [Dog Object]                  │
└─────────────────────────────────────────────────────────────────┘
            

The Complete Initialization Order

When creating an object, Java follows this exact sequence:

public class InitializationDemo {
    // Step 1: Static variables and blocks (once per class)
    private static String staticVar = initStatic();

    static {
        System.out.println("2. Static block");
    }

    private static String initStatic() {
        System.out.println("1. Static variable initializer");
        return "static";
    }

    // Step 2: Instance variables (per object)
    private String instanceVar = initInstance();

    // Step 3: Instance initializer block (per object)
    {
        System.out.println("4. Instance initializer block");
    }

    private String initInstance() {
        System.out.println("3. Instance variable initializer");
        return "instance";
    }

    // Step 4: Constructor body
    public InitializationDemo() {
        System.out.println("5. Constructor body");
    }
}

// Output when creating first object:
// 1. Static variable initializer
// 2. Static block
// 3. Instance variable initializer
// 4. Instance initializer block
// 5. Constructor body

// Output when creating second object:
// 3. Instance variable initializer   (static already done)
// 4. Instance initializer block
// 5. Constructor body

Bytecode View: What new Actually Does

// Java code:
Dog myDog = new Dog("Buddy", 3);

// Simplified bytecode:
// new #2          // Allocate memory for Dog
// dup             // Duplicate reference on stack
// ldc "Buddy"     // Push "Buddy" onto stack
// iconst_3        // Push 3 onto stack
// invokespecial   // Call Dog.<init>(String, int)
// astore_1        // Store reference in myDog
The <init> Method

In bytecode, constructors are compiled into special methods called <init>. This is why constructors can't be called directly like regular methods - they're handled specially by the JVM during object creation.

Default Constructor

If you don't define any constructor, Java provides a no-argument default constructor automatically. This is a convenience feature that allows simple classes to be instantiated without explicit constructor code.

public class Person {
    String name;
    int age;

    // No constructor defined - Java provides default constructor
    // It's equivalent to:
    // public Person() { }
}

// Usage
Person person = new Person();  // Calls default constructor
// person.name = null (default for String)
// person.age = 0 (default for int)
Default Constructor Disappears!

Once you define ANY constructor, the default constructor is no longer provided. This is a common source of errors:

public class Person {
    String name;

    // We defined a parameterized constructor
    public Person(String name) {
        this.name = name;
    }
}

Person p1 = new Person("Alice");  // OK
Person p2 = new Person();          // ERROR! No default constructor

Default Values for Fields

When using the default constructor (or any constructor that doesn't initialize all fields), fields get their type's default value:

// Default values by type:
byte, short, int, long0
float, double0.0
booleanfalse
char'\u0000' (null character)
Object references        → null

Custom Constructors

No-Argument Constructor

A no-argument constructor that you define explicitly (as opposed to the default one) lets you set up default values:

public class Account {
    private String accountNumber;
    private double balance;
    private LocalDateTime createdAt;

    // No-argument constructor with sensible defaults
    public Account() {
        this.accountNumber = generateAccountNumber();
        this.balance = 0.0;
        this.createdAt = LocalDateTime.now();
    }

    private String generateAccountNumber() {
        return "ACC-" + System.currentTimeMillis();
    }
}

Parameterized Constructor

Parameterized constructors allow callers to provide initial values:

public class Person {
    private final String name;      // final = must be set in constructor
    private final int birthYear;
    private String email;

    // Parameterized constructor - required fields
    public Person(String name, int birthYear) {
        // Validate inputs - constructors should ensure object validity
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }
        if (birthYear < 1900 || birthYear > Year.now().getValue()) {
            throw new IllegalArgumentException("Invalid birth year");
        }

        this.name = name;
        this.birthYear = birthYear;
    }

    // Full constructor - all fields
    public Person(String name, int birthYear, String email) {
        this(name, birthYear);  // Delegate to other constructor
        this.email = email;
    }
}

// Usage
Person person = new Person("Alice", 1990);
Person personWithEmail = new Person("Bob", 1985, "bob@example.com");

Copy Constructor

A copy constructor creates a new object as a copy of an existing object. Unlike languages like C++, Java doesn't have automatic copy constructors, but you can implement them yourself:

public class Address {
    private String street;
    private String city;
    private String zipCode;

    // Regular constructor
    public Address(String street, String city, String zipCode) {
        this.street = street;
        this.city = city;
        this.zipCode = zipCode;
    }

    // Copy constructor - creates a deep copy
    public Address(Address other) {
        this.street = other.street;    // String is immutable, so this is safe
        this.city = other.city;
        this.zipCode = other.zipCode;
    }
}

public class Employee {
    private String name;
    private Address address;  // Mutable object reference
    private List<String> skills;

    public Employee(String name, Address address, List<String> skills) {
        this.name = name;
        this.address = address;
        this.skills = skills;
    }

    // Copy constructor - must handle mutable objects carefully!
    public Employee(Employee other) {
        this.name = other.name;                      // String immutable - OK
        this.address = new Address(other.address);   // Deep copy!
        this.skills = new ArrayList<>(other.skills); // Deep copy!
    }
}

// Usage
Employee original = new Employee("Alice", address, skills);
Employee copy = new Employee(original);  // Independent copy

// Changes to copy don't affect original
copy.getAddress().setCity("New City");
System.out.println(original.getAddress().getCity());  // Still "Old City"
Shallow vs Deep Copy

Shallow copy: Copies references, not the objects they point to. Changes to referenced objects affect both copies.

Deep copy: Creates new copies of all referenced objects. Completely independent objects.

// WRONG: Shallow copy
public Employee(Employee other) {
    this.address = other.address;  // Both point to same Address!
}

// CORRECT: Deep copy
public Employee(Employee other) {
    this.address = new Address(other.address);  // New Address object
}

Constructor Overloading

A class can have multiple constructors with different parameters. This provides flexibility in how objects are created:

public class Rectangle {
    private double width;
    private double height;

    // Constructor 1: No parameters (default size)
    public Rectangle() {
        this.width = 1.0;
        this.height = 1.0;
    }

    // Constructor 2: Square (one parameter)
    public Rectangle(double size) {
        this.width = size;
        this.height = size;
    }

    // Constructor 3: Rectangle (two parameters)
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double area() {
        return width * height;
    }
}

// Usage - different ways to create objects
Rectangle r1 = new Rectangle();  // 1x1
Rectangle r2 = new Rectangle(5.0);  // 5x5 square
Rectangle r3 = new Rectangle(4.0, 6.0);  // 4x6 rectangle

Real-World Example: Connection Configuration

public class DatabaseConnection {
    private final String host;
    private final int port;
    private final String database;
    private final String username;
    private final String password;
    private final int timeout;

    // Full constructor
    public DatabaseConnection(String host, int port, String database,
                               String username, String password, int timeout) {
        this.host = host;
        this.port = port;
        this.database = database;
        this.username = username;
        this.password = password;
        this.timeout = timeout;
    }

    // Simplified: default port and timeout
    public DatabaseConnection(String host, String database,
                               String username, String password) {
        this(host, 5432, database, username, password, 30000);
    }

    // Local development: defaults for everything
    public DatabaseConnection(String database) {
        this("localhost", 5432, database, "dev", "dev", 5000);
    }
}

// Usage scenarios
DatabaseConnection prod = new DatabaseConnection(
    "prod.db.server", 5432, "myapp", "admin", "secret", 60000
);
DatabaseConnection staging = new DatabaseConnection(
    "staging.db.server", "myapp", "user", "pass"
);
DatabaseConnection local = new DatabaseConnection("myapp");

Constructor Chaining

Use this() to call another constructor in the same class. This reduces code duplication and centralizes initialization logic.

public class Employee {
    private String name;
    private int id;
    private double salary;
    private String department;

    // Primary constructor - all validation and logic here
    public Employee(String name, int id, double salary, String department) {
        // All validation in one place
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name required");
        }
        if (salary < 0) {
            throw new IllegalArgumentException("Salary cannot be negative");
        }

        this.name = name;
        this.id = id;
        this.salary = salary;
        this.department = department;
    }

    // Chain to primary - default department
    public Employee(String name, int id, double salary) {
        this(name, id, salary, "Unassigned");
    }

    // Chain to above - default salary
    public Employee(String name, int id) {
        this(name, id, 50000.0);
    }

    // Chain to above - auto-generate ID
    public Employee(String name) {
        this(name, generateId());
    }

    private static int nextId = 1000;
    private static int generateId() {
        return nextId++;
    }
}
this() Rules
  • this() must be the first statement in constructor
  • You cannot have both this() and super() in the same constructor
  • Reduces code duplication - validation logic in one place
  • Improves maintainability - changes propagate automatically
  • Constructors should chain "upward" to the most specific one

Constructor Chain Visualization

┌────────────────────────────────────────────────────────────────┐
│                    Constructor Chaining                         │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Employee("Alice")                                             │
│        │                                                        │
│        ▼                                                        │
│   Employee("Alice", 1000)     ← generateId() returns 1000      │
│        │                                                        │
│        ▼                                                        │
│   Employee("Alice", 1000, 50000.0)     ← default salary        │
│        │                                                        │
│        ▼                                                        │
│   Employee("Alice", 1000, 50000.0, "Unassigned")  ← default    │
│        │                                                        │
│        ▼                                                        │
│   [Validation + Field Assignment]    ← All logic here          │
│                                                                 │
└────────────────────────────────────────────────────────────────┘
            

Calling Parent Constructors

Use super() to call the parent class constructor. This is essential for inheritance.

public class Animal {
    protected String name;
    protected int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Animal constructor called");
    }
}

public class Dog extends Animal {
    private String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);  // MUST be first statement
        this.breed = breed;
        System.out.println("Dog constructor called");
    }
}

// Usage
Dog dog = new Dog("Buddy", 3, "Golden Retriever");
// Output:
// Animal constructor called
// Dog constructor called

Inheritance Constructor Rules

┌────────────────────────────────────────────────────────────────┐
│              Inheritance Constructor Execution                  │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│   new Dog("Buddy", 3, "Golden")                                │
│        │                                                        │
│        ▼                                                        │
│   Dog constructor starts                                        │
│        │                                                        │
│        ▼                                                        │
│   super(name, age) called → Animal constructor runs FIRST      │
│        │                                                        │
│        ▼                                                        │
│   Animal constructor completes                                  │
│        │                                                        │
│        ▼                                                        │
│   Dog constructor body continues                                │
│        │                                                        │
│        ▼                                                        │
│   Object fully initialized                                      │
│                                                                 │
└────────────────────────────────────────────────────────────────┘
            
super() Rules
  • super() must be the first statement in the constructor
  • If you don't call super(), Java automatically inserts super() (no-arg)
  • If parent has no no-arg constructor, you MUST explicitly call super(args)
  • You cannot use both this() and super() in the same constructor

Multi-Level Inheritance

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

public class Mammal extends Animal {
    public Mammal() { System.out.println("Mammal"); }
}

public class Dog extends Mammal {
    public Dog() { System.out.println("Dog"); }
}

Dog dog = new Dog();
// Output (parent first, then child):
// Animal
// Mammal
// Dog

Private Constructors

Private constructors prevent external instantiation. They're used in several design patterns:

Singleton Pattern

public class DatabaseConnection {
    private static DatabaseConnection instance;

    // Private constructor - can't create from outside
    private DatabaseConnection() {
        // Initialize connection
    }

    // Only way to get an instance
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
}

// Usage
DatabaseConnection db = DatabaseConnection.getInstance();
// DatabaseConnection db2 = new DatabaseConnection(); // ERROR!

Utility Classes

public final class StringUtils {
    // Private constructor - utility class should not be instantiated
    private StringUtils() {
        throw new AssertionError("Cannot instantiate utility class");
    }

    public static boolean isEmpty(String str) {
        return str == null || str.isEmpty();
    }

    public static String capitalize(String str) {
        if (isEmpty(str)) return str;
        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
    }
}

Factory Pattern with Static Factory Methods

public class Color {
    private final int red, green, blue;

    // Private constructor
    private Color(int red, int green, int blue) {
        this.red = red;
        this.green = green;
        this.blue = blue;
    }

    // Static factory methods - more readable than constructors
    public static Color fromRGB(int r, int g, int b) {
        validateRange(r, g, b);
        return new Color(r, g, b);
    }

    public static Color fromHex(String hex) {
        // Parse hex and create color
        int r = Integer.parseInt(hex.substring(1, 3), 16);
        int g = Integer.parseInt(hex.substring(3, 5), 16);
        int b = Integer.parseInt(hex.substring(5, 7), 16);
        return new Color(r, g, b);
    }

    // Pre-defined colors
    public static Color red()   { return new Color(255, 0, 0); }
    public static Color green() { return new Color(0, 255, 0); }
    public static Color blue()  { return new Color(0, 0, 255); }

    private static void validateRange(int... values) {
        for (int v : values) {
            if (v < 0 || v > 255) {
                throw new IllegalArgumentException("Value must be 0-255");
            }
        }
    }
}

// Usage - much more readable!
Color c1 = Color.fromRGB(255, 128, 0);
Color c2 = Color.fromHex("#FF8000");
Color c3 = Color.red();

In Practice: Real-World Patterns

Builder Pattern (Alternative to Many Constructors)

When a class has many optional parameters, constructors become unwieldy. The Builder pattern solves this:

public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;
    private final int timeout;
    private final boolean followRedirects;

    // Private constructor - only Builder can create
    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Map.copyOf(builder.headers);
        this.body = builder.body;
        this.timeout = builder.timeout;
        this.followRedirects = builder.followRedirects;
    }

    public static class Builder {
        // Required
        private final String url;

        // Optional with defaults
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body = null;
        private int timeout = 30000;
        private boolean followRedirects = true;

        public Builder(String url) {
            this.url = url;
        }

        public Builder method(String method) {
            this.method = method;
            return this;
        }

        public Builder header(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public Builder body(String body) {
            this.body = body;
            return this;
        }

        public Builder timeout(int timeout) {
            this.timeout = timeout;
            return this;
        }

        public Builder followRedirects(boolean follow) {
            this.followRedirects = follow;
            return this;
        }

        public HttpRequest build() {
            return new HttpRequest(this);
        }
    }
}

// Usage - clear and readable!
HttpRequest request = new HttpRequest.Builder("https://api.example.com/data")
    .method("POST")
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer token123")
    .body("{\"name\": \"test\"}")
    .timeout(5000)
    .build();

Record Classes (Java 16+)

For simple data carriers, Java records provide automatic constructors:

// Record automatically provides:
// - Constructor with all fields
// - Getters for all fields
// - equals(), hashCode(), toString()
public record Point(int x, int y) {
    // Compact constructor for validation
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("Coordinates must be positive");
        }
        // No need for this.x = x; - done automatically
    }

    // Additional constructor
    public Point() {
        this(0, 0);  // Must delegate to canonical constructor
    }
}

// Usage
Point p1 = new Point(10, 20);
Point p2 = new Point();  // (0, 0)

Best Practices

Constructor Best Practices
  1. Validate all inputs: Constructors should ensure the object is in a valid state
    public Person(String name, int age) {
        if (name == null) throw new NullPointerException("name");
        if (age < 0) throw new IllegalArgumentException("age < 0");
        this.name = name;
        this.age = age;
    }
  2. Initialize all fields: Don't leave fields with default values unless intentional
  3. Use constructor chaining: DRY - Don't Repeat Yourself
  4. Make defensive copies: For mutable parameters, copy them
    public Event(Date date) {
        this.date = new Date(date.getTime()); // Defensive copy
    }
  5. Don't call overridable methods: Can cause issues with subclasses
  6. Keep constructors simple: Avoid complex logic; consider factory methods
  7. Document constructor requirements: Use @throws in Javadoc

Common Pitfalls

Pitfall 1: Forgetting super() When Parent Has No Default Constructor
public class Animal {
    private String name;

    public Animal(String name) {  // Only parameterized constructor
        this.name = name;
    }
}

// BAD: Compilation error!
public class Dog extends Animal {
    public Dog() {
        // Java inserts super() implicitly
        // But Animal has no no-arg constructor!
    }
}

// GOOD: Explicit super() call
public class Dog extends Animal {
    public Dog() {
        super("Unknown");  // Explicit call with argument
    }
}
Pitfall 2: Calling Overridable Methods in Constructor
public class Parent {
    public Parent() {
        init();  // DANGEROUS! Can be overridden
    }

    public void init() {
        System.out.println("Parent init");
    }
}

public class Child extends Parent {
    private String value = "initialized";

    @Override
    public void init() {
        System.out.println("Value: " + value);  // value is null!
    }
}

new Child();  // Prints "Value: null" - field not yet initialized!

// SOLUTION: Use final methods or private helper methods
public class Parent {
    public Parent() {
        initInternal();  // Private - can't be overridden
    }

    private void initInternal() {
        System.out.println("Safe init");
    }
}
Pitfall 3: Storing Mutable Parameters Directly
// BAD: External code can modify internal state
public class Schedule {
    private Date[] dates;

    public Schedule(Date[] dates) {
        this.dates = dates;  // Stores reference directly!
    }
}

Date[] myDates = { new Date() };
Schedule s = new Schedule(myDates);
myDates[0] = null;  // Now schedule's internal array has null!

// GOOD: Make defensive copy
public class Schedule {
    private Date[] dates;

    public Schedule(Date[] dates) {
        this.dates = Arrays.copyOf(dates, dates.length);
        // Deep copy if Date objects are mutable
        for (int i = 0; i < this.dates.length; i++) {
            this.dates[i] = new Date(this.dates[i].getTime());
        }
    }
}
Pitfall 4: Exception in Constructor Leaves Partial Object
// BAD: Resource leak if second allocation fails
public class ResourceHolder {
    private Connection conn;
    private FileInputStream fis;

    public ResourceHolder(String dbUrl, String filePath) throws Exception {
        conn = DriverManager.getConnection(dbUrl);  // OK
        fis = new FileInputStream(filePath);  // If this throws, conn leaks!
    }
}

// GOOD: Clean up on failure
public class ResourceHolder {
    private Connection conn;
    private FileInputStream fis;

    public ResourceHolder(String dbUrl, String filePath) throws Exception {
        try {
            conn = DriverManager.getConnection(dbUrl);
            fis = new FileInputStream(filePath);
        } catch (Exception e) {
            // Clean up partially created resources
            if (conn != null) conn.close();
            if (fis != null) fis.close();
            throw e;
        }
    }
}

Performance Considerations

Object Creation Cost

// Object creation involves:
// 1. Memory allocation (very fast in Java)
// 2. Field initialization (zero-fill, then explicit values)
// 3. Constructor execution

// AVOID: Creating objects in tight loops when possible
for (int i = 0; i < 1000000; i++) {
    StringBuilder sb = new StringBuilder();  // 1M objects created!
    sb.append(i);
    process(sb.toString());
}

// BETTER: Reuse when possible
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
    sb.setLength(0);  // Reset instead of creating new
    sb.append(i);
    process(sb.toString());
}

Constructor Chaining Overhead

// Deep constructor chains have minimal overhead
// JVM inlines small methods, including constructor calls

// This is FINE - JVM optimizes it:
public Employee(String name) {
    this(name, 0);
}
public Employee(String name, int id) {
    this(name, id, 50000);
}
public Employee(String name, int id, double salary) {
    // actual initialization
}

Static Factory Methods vs Constructors

// Static factory methods can cache instances
public class Boolean {
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

    // Returns cached instance - no object creation!
    public static Boolean valueOf(boolean b) {
        return b ? TRUE : FALSE;
    }
}

// Prefer valueOf() over new Boolean() - it's cached!
Boolean b1 = Boolean.valueOf(true);  // Uses cached instance
Boolean b2 = new Boolean(true);      // Creates new object (wasteful)

Troubleshooting Guide

Common Compilation Errors

"constructor X in class Y cannot be applied to given types"

Cause: Calling constructor with wrong argument types or count

public class Person {
    public Person(String name, int age) { }
}

Person p = new Person("Alice");  // ERROR: missing age argument
Person p = new Person(25, "Alice");  // ERROR: wrong order

Fix: Match the constructor's parameter list exactly

"call to this must be first statement in constructor"

Cause: Code before this() or super()

public Employee(String name) {
    System.out.println("Creating...");  // ERROR!
    this(name, 0);
}

// Fix: this() must be first
public Employee(String name) {
    this(name, 0);
    System.out.println("Creating...");  // OK - after this()
}
"variable x might not have been initialized"

Cause: Final field not set in all constructor paths

public class Config {
    private final String value;

    public Config(boolean useDefault) {
        if (useDefault) {
            value = "default";
        }
        // ERROR: value not set when useDefault is false!
    }
}

// Fix: Set in all paths
public Config(boolean useDefault) {
    if (useDefault) {
        value = "default";
    } else {
        value = "custom";
    }
}
"recursive constructor invocation"

Cause: Constructor calls itself (directly or indirectly)

public MyClass() {
    this();  // ERROR: calls itself!
}

public MyClass() {
    this(0);
}
public MyClass(int x) {
    this();  // ERROR: circular chain!
}

Fix: Ensure constructor chain terminates

Interview Questions

Q1: What is the difference between a constructor and a method?

Answer:

  • Name: Constructor has the same name as the class; methods can have any name
  • Return type: Constructor has no return type (not even void); methods must have one
  • Invocation: Constructor is called automatically with new; methods are called explicitly
  • Purpose: Constructor initializes objects; methods perform operations
  • Inheritance: Constructors are not inherited; methods can be
Q2: Can a constructor be private? When would you use it?

Answer: Yes, constructors can be private. Use cases include:

  • Singleton pattern: Ensure only one instance exists
  • Utility classes: Prevent instantiation of classes with only static methods
  • Factory pattern: Force use of static factory methods for controlled object creation
  • Builder pattern: Allow only the nested Builder class to create instances
Q3: What happens if you don't call super() in a subclass constructor?

Answer: Java automatically inserts super() (no-argument) as the first statement. If the parent class doesn't have a no-argument constructor, you'll get a compilation error and must explicitly call super(args) with appropriate arguments.

Q4: Can you call this() and super() in the same constructor?

Answer: No. Both must be the first statement, so you can only have one. If you use this(), the chained constructor will eventually call super().

Q5: What is constructor chaining and why is it useful?

Answer: Constructor chaining is when one constructor calls another using this() or super(). Benefits:

  • Reduces code duplication (DRY principle)
  • Centralizes validation and initialization logic
  • Makes maintenance easier - change one place, affects all constructors
  • Ensures consistency across different ways of creating objects
Q6: What is the order of initialization when creating an object?

Answer: When creating a new object:

  1. Static variables and static blocks (only first time class is loaded)
  2. Instance variables (set to default values)
  3. Instance initializer blocks (in order of appearance)
  4. Constructor body

For inheritance, parent's initialization completes before child's.

Q7: What is a copy constructor? How do you implement deep copy?

Answer: A copy constructor creates a new object by copying another object of the same class. For deep copy:

  • Primitive fields: Copy directly (already values)
  • Immutable objects (String, Integer, etc.): Can copy reference safely
  • Mutable objects: Must create new copies, not copy references
  • Collections: Create new collection and copy contents
Q8: When should you use a Builder pattern instead of telescoping constructors?

Answer: Use Builder when:

  • Class has many parameters (more than 4-5)
  • Many parameters are optional
  • Parameters have the same type (easy to mix up order)
  • Object construction requires multiple steps or validation
  • You want immutable objects with many fields

Builder provides readable code, named parameters, and optional-parameter handling.