Definition
A constructor is a special method called when an object is created with new.
- Name: same as the class. Return type: none (not even
void). - Default constructor: if you write no constructor, Java provides a no-arg constructor. If you write any constructor, the default is not provided.
- Overloading: a class can have multiple constructors with different parameter lists.
- Private constructor pattern 1: Singleton ensures only one instance exists.
privateconstructor +static getInstance()returns the single instance. - Private constructor pattern 2: Factory Method forces callers to use named static methods like
Account.createSavings("SAV001")instead of ambiguousnew Account(...). - Private constructor pattern 3: Utility Class prevents instantiation of a class with only static methods (like
Math,Collections).
A constructor is like the account opening form at National Bank of India: filled out when a new account is created (accountId, holderName, initial balance). Private constructor patterns: (1) Singleton: the bank's RBI connection manager has only one instance for the entire bank. (2) Factory: Account.createSavings("SAV001") is clearer than new Account("SAV001", "SAVINGS", 0.04, 1000). (3) Utility: InterestCalculator has only static methods; there is no reason to create an instance.
Understanding the Concepts
What is a constructor and can it be private?
A constructor initializes an object when created with new. It has the same name as the class and no return type.
What are the constructor rules?
package com.bank.core;
import java.math.BigDecimal;
public class Account {
private final String accountId;
private BigDecimal balance;
// Constructor: same name as class, no return type
public Account(String accountId, BigDecimal initialBalance) {
this.accountId = accountId;
this.balance = initialBalance;
}
// Overloaded constructor
public Account(String accountId) {
this(accountId, BigDecimal.ZERO); // chains to first constructor
}
}
// Rules:
// 1. Same name as class
// 2. No return type (not even void)
// 3. Called automatically by new
// 4. Can be overloaded
// 5. If you write none: Java provides default no-arg
// 6. If you write any: Java does not provide default
How does the Singleton pattern use a private constructor?
public class RbiConnectionManager {
private static final RbiConnectionManager INSTANCE = new RbiConnectionManager();
private RbiConnectionManager() {
// Initialize connection pool, SSL certificates
System.out.println("RBI connection initialized (happens once)");
}
public static RbiConnectionManager getInstance() {
return INSTANCE;
}
public String sendReport(String report) {
return "Sent to RBI: " + report;
}
}
// RbiConnectionManager.getInstance() == RbiConnectionManager.getInstance() -> true
// new RbiConnectionManager() -> compile error: constructor is private
// Modern alternative: enum Singleton (thread-safe, serialization-safe)
public enum RbiConnection {
INSTANCE;
public String sendReport(String report) { return "Sent: " + report; }
}
How does the Factory Method pattern use a private constructor?
public class Account {
private final String accountId;
private final String type;
private final BigDecimal balance;
private final BigDecimal interestRate;
private Account(String id, String type, BigDecimal balance, BigDecimal rate) {
this.accountId = id;
this.type = type;
this.balance = balance;
this.interestRate = rate;
}
public static Account createSavings(String id, BigDecimal balance) {
return new Account(id, "SAVINGS", balance, new BigDecimal("0.04"));
}
public static Account createCurrent(String id, BigDecimal balance) {
return new Account(id, "CURRENT", balance, BigDecimal.ZERO);
}
}
// Account.createSavings("SAV2001", new BigDecimal("125000")) is clear and self-documenting.
// new Account("SAV2001", "SAVINGS", new BigDecimal("125000"), new BigDecimal("0.04")) is unclear.
How does the Utility Class pattern use a private constructor?
public final class InterestCalculator {
private InterestCalculator() {
throw new UnsupportedOperationException("Utility class");
}
public static BigDecimal simpleInterest(BigDecimal principal, BigDecimal rate, int years) {
return principal.multiply(rate).multiply(BigDecimal.valueOf(years));
}
public static BigDecimal compoundInterest(BigDecimal principal, BigDecimal rate, int years) {
return principal.multiply(BigDecimal.ONE.add(rate).pow(years)).subtract(principal);
}
}
// InterestCalculator.simpleInterest(...) works.
// new InterestCalculator() -> compile error.
How do the three patterns compare?
| Pattern | Purpose | Instance count | Example |
|---------|---------|---------------|----------|
| Singleton | One shared instance | Exactly 1 | RBI connection manager |
| Factory Method | Named creation | Many | Account.createSavings() |
| Utility Class | No instances | Zero | InterestCalculator |
What are common mistakes?
The most common mistake is forgetting that writing any constructor removes the default no-arg constructor.
// WRONG: parent has parameterized constructor, child expects default
public class Account {
public Account(String id) { this.id = id; } // no default no-arg
}
public class SavingsAccount extends Account {
public SavingsAccount() {
// Java inserts super() -> compile error: Account has no no-arg constructor
}
}
// CORRECT: explicitly call super(args)
public class SavingsAccount extends Account {
public SavingsAccount() {
super("DEFAULT"); // explicit call to parameterized constructor
}
}
What should a senior engineer know?
- For Singleton, use an enum (thread-safe, serialization-safe) or Spring @Component (singleton scope by default).
- For factory methods, records with static of() or from() methods are the modern approach.
- Utility classes should be marked final to prevent subclassing.
- Effective Java Item 1: consider static factory methods instead of constructors.
Visual Overview
THREE PRIVATE CONSTRUCTOR PATTERNS ────────────────────────────────────────── SINGLETON (exactly 1 instance) ┌─────────────────────────┐ │ private constructor() │ │ private static INSTANCE │ │ public static getInstance│ │ return INSTANCE │ └─────────────────────────┘ FACTORY METHOD (many instances) ┌─────────────────────────┐ │ private constructor() │ │ public static createX() │ │ public static createY() │ │ return new Account(...) │ └─────────────────────────┘ UTILITY CLASS (zero instances) ┌─────────────────────────┐ │ private constructor() │ │ throws Unsupported │ │ public static method1() │ │ public static method2() │ └─────────────────────────┘
Key Takeaway
A constructor initializes an object when created with new. It has the same name as the class, no return type, and can be overloaded. If no constructor is written, Java provides a default no-arg constructor. If any constructor is written, the default is not provided. Yes, constructors can be private: three production patterns use this. Singleton ensures only one instance exists (RBI connection manager). Factory method provides named creation methods that are self-documenting (Account.createSavings versus a confusing multi-parameter constructor). Utility class prevents meaningless instantiation of a class with only static methods (InterestCalculator).
In Pankaj's banking system, the Singleton ensures only one RBI connection exists. The factory makes Account.createSavings("SAV001", new BigDecimal("50000")) immediately clear compared to new Account("SAV001", "SAVINGS", new BigDecimal("50000"), new BigDecimal("0.04")) where parameter meaning is ambiguous. The utility class throws UnsupportedOperationException even if accessed via reflection.
Remember one sentence: a constructor initializes state on new, and a private constructor controls instantiation for Singleton (one instance), Factory Method (named creation), and Utility Class (no instances).
---