Introduction
Imagine having a secret room that only specific people can enter—you control exactly how and when someone can access it. That's exactly what private constructors do in Java! They give you complete control over how objects of a class are created, enabling powerful patterns like singletons, utility classes, and builder patterns.
Private constructors are like having a VIP entrance to your class—only the class itself can decide when to create instances and how they're constructed.
What are Private Constructors?
A private constructor is a constructor that can only be accessed within the class itself. It prevents other classes from instantiating the class using the new keyword directly.
Key Characteristics:
- 🔒 Restricted access: Only accessible within the class
- 🎯 Controlled instantiation: You decide how objects are created
- 🏗️ Design patterns: Enables singleton, utility classes, builder pattern
- 📚 Encapsulation: Full control over object creation lifecycle
- ⚡ Performance: Can cache and reuse instances
Code Explanation with Examples
Example 1: Singleton Pattern with Private Constructor
public class SingletonDemo {
public static void main(String[] args) {
System.out.println("=== SINGLETON PATTERN WITH PRIVATE CONSTRUCTOR ===");
// Get singleton instances
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
Singleton singleton3 = Singleton.getInstance();
// All references point to the same object
System.out.println("singleton1 == singleton2: " + (singleton1 == singleton2));
System.out.println("singleton2 == singleton3: " + (singleton2 == singleton3));
// Using the singleton
singleton1.showMessage();
singleton2.showMessage();
singleton3.showMessage();
// Singleton with configuration
System.out.println("\n=== CONFIGURABLE SINGLETON ===");
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
db1.connect();
db2.query("SELECT * FROM users");
System.out.println("Same database instance? " + (db1 == db2));
// Thread-safe singleton
System.out.println("\n=== THREAD-SAFE SINGLETON ===");
ThreadSafeSingleton ts1 = ThreadSafeSingleton.getInstance();
ThreadSafeSingleton ts2 = ThreadSafeSingleton.getInstance();
System.out.println("Thread-safe same instance? " + (ts1 == ts2));
}
}
// Basic Singleton Pattern
class Singleton {
// Private static instance of the class
private static Singleton instance;
// Private constructor - prevents instantiation from outside
private Singleton() {
System.out.println("Singleton instance created!");
// Initialization code here
}
// Public method to provide access to the instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// Instance methods
public void showMessage() {
System.out.println("Hello from Singleton! Instance: " + this.hashCode());
}
// Business methods
public void doWork() {
System.out.println("Singleton is working...");
}
}
// Singleton with configuration and lazy initialization
class DatabaseConnection {
private static DatabaseConnection instance;
private String connectionString;
private boolean isConnected;
// Private constructor with initialization
private DatabaseConnection() {
this.connectionString = "jdbc:mysql://localhost:3306/mydatabase";
this.isConnected = false;
System.out.println("DatabaseConnection instance created");
}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void connect() {
if (!isConnected) {
System.out.println("Connecting to database: " + connectionString);
isConnected = true;
System.out.println("Connected successfully!");
} else {
System.out.println("Already connected to database");
}
}
public void disconnect() {
if (isConnected) {
System.out.println("Disconnecting from database");
isConnected = false;
System.out.println("Disconnected successfully!");
}
}
public void query(String sql) {
if (isConnected) {
System.out.println("Executing query: " + sql);
// Simulate query execution
System.out.println("Query executed successfully");
} else {
System.out.println("Error: Not connected to database");
}
}
public String getConnectionString() {
return connectionString;
}
}
// Thread-safe Singleton using synchronized
class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton() {
System.out.println("ThreadSafeSingleton instance created!");
}
// Synchronized method for thread safety
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
public void performTask() {
System.out.println("Performing thread-safe task...");
}
}
// Eager initialization singleton
class EagerSingleton {
// Instance created at class loading time
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
System.out.println("EagerSingleton instance created immediately!");
}
public static EagerSingleton getInstance() {
return instance;
}
}
Output:
=== SINGLETON PATTERN WITH PRIVATE CONSTRUCTOR === Singleton instance created! singleton1 == singleton2: true singleton2 == singleton3: true Hello from Singleton! Instance: 1324119927 Hello from Singleton! Instance: 1324119927 Hello from Singleton! Instance: 1324119927 === CONFIGURABLE SINGLETON === DatabaseConnection instance created Connecting to database: jdbc:mysql://localhost:3306/mydatabase Connected successfully! Executing query: SELECT * FROM users Query executed successfully Same database instance? true === THREAD-SAFE SINGLETON === ThreadSafeSingleton instance created! Thread-safe same instance? true
Example 2: Utility Classes with Private Constructors
public class UtilityClassesDemo {
public static void main(String[] args) {
System.out.println("=== UTILITY CLASSES WITH PRIVATE CONSTRUCTORS ===");
// Using math utilities
System.out.println("MathUtilities.max(10, 20): " + MathUtilities.max(10, 20));
System.out.println("MathUtilities.min(5.5, 3.2): " + MathUtilities.min(5.5, 3.2));
System.out.println("MathUtilities.average(10, 20, 30): " + MathUtilities.average(10, 20, 30));
// Using string utilities
System.out.println("\nStringUtilities.isBlank(null): " + StringUtilities.isBlank(null));
System.out.println("StringUtilities.isBlank(''): " + StringUtilities.isBlank(""));
System.out.println("StringUtilities.isBlank(' '): " + StringUtilities.isBlank(" "));
System.out.println("StringUtilities.reverse('hello'): " + StringUtilities.reverse("hello"));
// Using validation utilities
System.out.println("\nValidationUtils.isValidEmail('[email protected]'): " +
ValidationUtils.isValidEmail("[email protected]"));
System.out.println("ValidationUtils.isValidEmail('invalid'): " +
ValidationUtils.isValidEmail("invalid"));
System.out.println("ValidationUtils.isStrongPassword('Weak'): " +
ValidationUtils.isStrongPassword("Weak"));
// Using date utilities
System.out.println("\nDateUtils.getCurrentTimestamp(): " + DateUtils.getCurrentTimestamp());
System.out.println("DateUtils.formatDate(new Date()): " + DateUtils.formatDate(new java.util.Date()));
// ❌ Cannot instantiate utility classes
// MathUtilities utils = new MathUtilities(); // COMPILER ERROR!
}
}
// Math utility class - cannot be instantiated
final class MathUtilities {
// Private constructor to prevent instantiation
private MathUtilities() {
throw new AssertionError("Cannot instantiate utility class!");
}
// Static methods only
public static int max(int a, int b) {
return Math.max(a, b);
}
public static int min(int a, int b) {
return Math.min(a, b);
}
public static double max(double a, double b) {
return Math.max(a, b);
}
public static double min(double a, double b) {
return Math.min(a, b);
}
public static double average(int... numbers) {
if (numbers.length == 0) return 0.0;
int sum = 0;
for (int num : numbers) {
sum += num;
}
return (double) sum / numbers.length;
}
public static boolean isEven(int number) {
return number % 2 == 0;
}
public static boolean isPrime(int number) {
if (number <= 1) return false;
if (number == 2) return true;
if (number % 2 == 0) return false;
for (int i = 3; i * i <= number; i += 2) {
if (number % i == 0) return false;
}
return true;
}
}
// String utility class
final class StringUtilities {
private StringUtilities() {
throw new AssertionError("Cannot instantiate utility class!");
}
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
public static boolean isNotEmpty(String str) {
return !isBlank(str);
}
public static String reverse(String str) {
if (str == null) return null;
return new StringBuilder(str).reverse().toString();
}
public static String capitalize(String str) {
if (isBlank(str)) return str;
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
public static int countWords(String str) {
if (isBlank(str)) return 0;
return str.trim().split("\\s+").length;
}
public static boolean containsIgnoreCase(String str, String searchStr) {
if (str == null || searchStr == null) return false;
return str.toLowerCase().contains(searchStr.toLowerCase());
}
}
// Validation utility class
final class ValidationUtils {
private ValidationUtils() {
throw new AssertionError("Cannot instantiate utility class!");
}
public static boolean isValidEmail(String email) {
if (email == null) return false;
// Simple email validation regex
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
return email.matches(emailRegex);
}
public static boolean isStrongPassword(String password) {
if (password == null || password.length() < 8) return false;
// Check for at least one uppercase, one lowercase, one digit, one special char
boolean hasUpper = password.matches(".*[A-Z].*");
boolean hasLower = password.matches(".*[a-z].*");
boolean hasDigit = password.matches(".*\\d.*");
boolean hasSpecial = password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*");
return hasUpper && hasLower && hasDigit && hasSpecial;
}
public static boolean isValidPhoneNumber(String phone) {
if (phone == null) return false;
// Simple phone validation (US format)
return phone.matches("\\d{3}-\\d{3}-\\d{4}");
}
public static boolean isInRange(int value, int min, int max) {
return value >= min && value <= max;
}
}
// Date utility class
final class DateUtils {
private DateUtils() {
throw new AssertionError("Cannot instantiate utility class!");
}
private static final java.text.SimpleDateFormat TIMESTAMP_FORMAT =
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final java.text.SimpleDateFormat DATE_FORMAT =
new java.text.SimpleDateFormat("MM/dd/yyyy");
public static String getCurrentTimestamp() {
return TIMESTAMP_FORMAT.format(new java.util.Date());
}
public static String formatDate(java.util.Date date) {
return DATE_FORMAT.format(date);
}
public static boolean isWeekend(java.util.Date date) {
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTime(date);
int dayOfWeek = cal.get(java.util.Calendar.DAY_OF_WEEK);
return dayOfWeek == java.util.Calendar.SATURDAY || dayOfWeek == java.util.Calendar.SUNDAY;
}
public static java.util.Date addDays(java.util.Date date, int days) {
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTime(date);
cal.add(java.util.Calendar.DAY_OF_MONTH, days);
return cal.getTime();
}
}
Output:
=== UTILITY CLASSES WITH PRIVATE CONSTRUCTORS ===
MathUtilities.max(10, 20): 20
MathUtilities.min(5.5, 3.2): 3.2
MathUtilities.average(10, 20, 30): 20.0
StringUtilities.isBlank(null): true
StringUtilities.isBlank(''): true
StringUtilities.isBlank(' '): true
StringUtilities.reverse('hello'): olleh
ValidationUtils.isValidEmail('[email protected]'): true
ValidationUtils.isValidEmail('invalid'): false
ValidationUtils.isStrongPassword('Weak'): false
DateUtils.getCurrentTimestamp(): 2024-01-15 10:30:45
DateUtils.formatDate(new Date()): 01/15/2024
Example 3: Builder Pattern with Private Constructor
public class BuilderPatternDemo {
public static void main(String[] args) {
System.out.println("=== BUILDER PATTERN WITH PRIVATE CONSTRUCTOR ===");
// Creating complex objects using builder pattern
Computer gamingPC = new Computer.Builder()
.setCpu("Intel i9-13900K")
.setGpu("NVIDIA RTX 4090")
.setRam(32)
.setStorage(2000)
.setPowerSupply(850)
.setHasRGB(true)
.build();
System.out.println("Gaming PC: " + gamingPC);
// Office computer with minimal configuration
Computer officePC = new Computer.Builder()
.setCpu("Intel i5-12400")
.setRam(16)
.setStorage(512)
.build();
System.out.println("Office PC: " + officePC);
// Person builder with validation
try {
Person person = new Person.Builder()
.setFirstName("John")
.setLastName("Doe")
.setAge(30)
.setEmail("[email protected]")
.setPhone("123-456-7890")
.build();
System.out.println("Person: " + person);
} catch (IllegalArgumentException e) {
System.out.println("Error creating person: " + e.getMessage());
}
// Invalid person (will throw exception)
try {
Person invalidPerson = new Person.Builder()
.setFirstName("") // Empty first name
.setAge(-5) // Negative age
.build();
} catch (IllegalArgumentException e) {
System.out.println("Expected error: " + e.getMessage());
}
// Database configuration builder
DatabaseConfig dbConfig = new DatabaseConfig.Builder()
.setHost("localhost")
.setPort(5432)
.setDatabaseName("mydb")
.setUsername("admin")
.setPassword("secret")
.setUseSSL(true)
.setConnectionTimeout(30)
.build();
System.out.println("Database Config: " + dbConfig);
}
}
// Computer class with builder pattern
class Computer {
private final String cpu;
private final String gpu;
private final int ram; // in GB
private final int storage; // in GB
private final int powerSupply; // in Watts
private final boolean hasRGB;
// Private constructor - only Builder can create instances
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.gpu = builder.gpu;
this.ram = builder.ram;
this.storage = builder.storage;
this.powerSupply = builder.powerSupply;
this.hasRGB = builder.hasRGB;
}
// Static builder class
public static class Builder {
// Required parameters with defaults
private String cpu = "Intel i5";
private String gpu = "Integrated";
private int ram = 8;
private int storage = 256;
// Optional parameters with defaults
private int powerSupply = 500;
private boolean hasRGB = false;
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder setGpu(String gpu) {
this.gpu = gpu;
return this;
}
public Builder setRam(int ram) {
if (ram < 4) throw new IllegalArgumentException("RAM must be at least 4GB");
this.ram = ram;
return this;
}
public Builder setStorage(int storage) {
if (storage < 128) throw new IllegalArgumentException("Storage must be at least 128GB");
this.storage = storage;
return this;
}
public Builder setPowerSupply(int powerSupply) {
if (powerSupply < 300) throw new IllegalArgumentException("Power supply must be at least 300W");
this.powerSupply = powerSupply;
return this;
}
public Builder setHasRGB(boolean hasRGB) {
this.hasRGB = hasRGB;
return this;
}
public Computer build() {
// Validate and create computer
validate();
return new Computer(this);
}
private void validate() {
// Additional validation logic
if (gpu.equals("NVIDIA RTX 4090") && powerSupply < 750) {
throw new IllegalArgumentException("RTX 4090 requires at least 750W power supply");
}
}
}
@Override
public String toString() {
return String.format(
"Computer{CPU='%s', GPU='%s', RAM=%dGB, Storage=%dGB, PSU=%dW, RGB=%s}",
cpu, gpu, ram, storage, powerSupply, hasRGB
);
}
}
// Person class with validation in builder
class Person {
private final String firstName;
private final String lastName;
private final int age;
private final String email;
private final String phone;
private Person(Builder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.email = builder.email;
this.phone = builder.phone;
}
public static class Builder {
private String firstName;
private String lastName;
private int age;
private String email;
private String phone;
public Builder setFirstName(String firstName) {
if (firstName == null || firstName.trim().isEmpty()) {
throw new IllegalArgumentException("First name cannot be null or empty");
}
this.firstName = firstName.trim();
return this;
}
public Builder setLastName(String lastName) {
if (lastName == null || lastName.trim().isEmpty()) {
throw new IllegalArgumentException("Last name cannot be null or empty");
}
this.lastName = lastName.trim();
return this;
}
public Builder setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
this.age = age;
return this;
}
public Builder setEmail(String email) {
if (email != null && !email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$")) {
throw new IllegalArgumentException("Invalid email format");
}
this.email = email;
return this;
}
public Builder setPhone(String phone) {
if (phone != null && !phone.matches("\\d{3}-\\d{3}-\\d{4}")) {
throw new IllegalArgumentException("Phone must be in format: XXX-XXX-XXXX");
}
this.phone = phone;
return this;
}
public Person build() {
// Validate required fields
if (firstName == null) {
throw new IllegalStateException("First name is required");
}
if (lastName == null) {
throw new IllegalStateException("Last name is required");
}
return new Person(this);
}
}
@Override
public String toString() {
return String.format(
"Person{firstName='%s', lastName='%s', age=%d, email='%s', phone='%s'}",
firstName, lastName, age, email, phone
);
}
}
// Database configuration with builder
class DatabaseConfig {
private final String host;
private final int port;
private final String databaseName;
private final String username;
private final String password;
private final boolean useSSL;
private final int connectionTimeout;
private DatabaseConfig(Builder builder) {
this.host = builder.host;
this.port = builder.port;
this.databaseName = builder.databaseName;
this.username = builder.username;
this.password = builder.password;
this.useSSL = builder.useSSL;
this.connectionTimeout = builder.connectionTimeout;
}
public static class Builder {
private String host = "localhost";
private int port = 3306;
private String databaseName;
private String username;
private String password;
private boolean useSSL = false;
private int connectionTimeout = 30;
public Builder setHost(String host) {
this.host = host;
return this;
}
public Builder setPort(int port) {
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Port must be between 1 and 65535");
}
this.port = port;
return this;
}
public Builder setDatabaseName(String databaseName) {
this.databaseName = databaseName;
return this;
}
public Builder setUsername(String username) {
this.username = username;
return this;
}
public Builder setPassword(String password) {
this.password = password;
return this;
}
public Builder setUseSSL(boolean useSSL) {
this.useSSL = useSSL;
return this;
}
public Builder setConnectionTimeout(int timeout) {
if (timeout < 1) {
throw new IllegalArgumentException("Connection timeout must be positive");
}
this.connectionTimeout = timeout;
return this;
}
public DatabaseConfig build() {
if (databaseName == null) {
throw new IllegalStateException("Database name is required");
}
if (username == null) {
throw new IllegalStateException("Username is required");
}
return new DatabaseConfig(this);
}
}
@Override
public String toString() {
return String.format(
"DatabaseConfig{host='%s', port=%d, database='%s', username='%s', ssl=%s, timeout=%d}",
host, port, databaseName, username, useSSL, connectionTimeout
);
}
}
Output:
=== BUILDER PATTERN WITH PRIVATE CONSTRUCTOR ===
Gaming PC: Computer{CPU='Intel i9-13900K', GPU='NVIDIA RTX 4090', RAM=32GB, Storage=2000GB, PSU=850W, RGB=true}
Office PC: Computer{CPU='Intel i5-12400', GPU='Integrated', RAM=16GB, Storage=512GB, PSU=500W, RGB=false}
Person: Person{firstName='John', lastName='Doe', age=30, email='[email protected]', phone='123-456-7890'}
Expected error: First name cannot be null or empty
Database Config: DatabaseConfig{host='localhost', port=5432, database='mydb', username='admin', ssl=true, timeout=30}
Example 4: Factory Pattern and Object Pooling
public class FactoryAndPoolDemo {
public static void main(String[] args) {
System.out.println("=== FACTORY PATTERN AND OBJECT POOLING ===");
// Factory pattern usage
System.out.println("\n=== FACTORY PATTERN ===");
Vehicle car = VehicleFactory.createVehicle("car");
Vehicle motorcycle = VehicleFactory.createVehicle("motorcycle");
Vehicle truck = VehicleFactory.createVehicle("truck");
car.drive();
motorcycle.drive();
truck.drive();
// Object pooling usage
System.out.println("\n=== OBJECT POOLING ===");
ConnectionPool pool = ConnectionPool.getInstance();
// Acquire connections
DatabaseConnection conn1 = pool.acquireConnection();
DatabaseConnection conn2 = pool.acquireConnection();
DatabaseConnection conn3 = pool.acquireConnection();
// Use connections
conn1.executeQuery("SELECT * FROM users");
conn2.executeQuery("SELECT * FROM products");
// Return connections to pool
pool.releaseConnection(conn1);
pool.releaseConnection(conn2);
// Acquire more connections (should reuse from pool)
DatabaseConnection conn4 = pool.acquireConnection();
DatabaseConnection conn5 = pool.acquireConnection();
conn4.executeQuery("SELECT * FROM orders");
// Show pool status
pool.printPoolStatus();
// Clean up
pool.releaseConnection(conn3);
pool.releaseConnection(conn4);
pool.releaseConnection(conn5);
// Logger factory
System.out.println("\n=== LOGGER FACTORY ===");
Logger fileLogger = LoggerFactory.getLogger("file");
Logger consoleLogger = LoggerFactory.getLogger("console");
Logger databaseLogger = LoggerFactory.getLogger("database");
fileLogger.log("This is a file log message");
consoleLogger.log("This is a console log message");
databaseLogger.log("This is a database log message");
}
}
// Factory pattern with private constructors
class VehicleFactory {
// Private constructor - utility class pattern
private VehicleFactory() {
throw new AssertionError("Cannot instantiate factory class!");
}
public static Vehicle createVehicle(String type) {
switch (type.toLowerCase()) {
case "car":
return new Car();
case "motorcycle":
return new Motorcycle();
case "truck":
return new Truck();
default:
throw new IllegalArgumentException("Unknown vehicle type: " + type);
}
}
}
interface Vehicle {
void drive();
void stop();
}
class Car implements Vehicle {
// Private constructor - only factory can create
private Car() {
System.out.println("Car instance created");
}
@Override
public void drive() {
System.out.println("Car is driving smoothly...");
}
@Override
public void stop() {
System.out.println("Car is stopping with brakes...");
}
}
class Motorcycle implements Vehicle {
private Motorcycle() {
System.out.println("Motorcycle instance created");
}
@Override
public void drive() {
System.out.println("Motorcycle is zooming...");
}
@Override
public void stop() {
System.out.println("Motorcycle is stopping...");
}
}
class Truck implements Vehicle {
private Truck() {
System.out.println("Truck instance created");
}
@Override
public void drive() {
System.out.println("Truck is hauling heavy load...");
}
@Override
public void stop() {
System.out.println("Truck is stopping slowly...");
}
}
// Object pooling pattern
class ConnectionPool {
private static ConnectionPool instance;
private final int MAX_POOL_SIZE = 5;
private final java.util.Queue<DatabaseConnection> availableConnections;
private final java.util.Set<DatabaseConnection> inUseConnections;
// Private constructor for singleton
private ConnectionPool() {
availableConnections = new java.util.LinkedList<>();
inUseConnections = new java.util.HashSet<>();
// Pre-populate pool
initializePool();
System.out.println("ConnectionPool initialized with " + MAX_POOL_SIZE + " connections");
}
public static ConnectionPool getInstance() {
if (instance == null) {
instance = new ConnectionPool();
}
return instance;
}
private void initializePool() {
for (int i = 0; i < MAX_POOL_SIZE; i++) {
availableConnections.offer(new DatabaseConnection("Connection-" + (i + 1)));
}
}
public DatabaseConnection acquireConnection() {
DatabaseConnection connection = availableConnections.poll();
if (connection != null) {
inUseConnections.add(connection);
System.out.println("Acquired connection: " + connection.getId());
} else {
System.out.println("No available connections in pool");
}
return connection;
}
public void releaseConnection(DatabaseConnection connection) {
if (inUseConnections.remove(connection)) {
availableConnections.offer(connection);
System.out.println("Released connection: " + connection.getId());
}
}
public void printPoolStatus() {
System.out.println("Pool Status - Available: " + availableConnections.size() +
", In Use: " + inUseConnections.size() +
", Total: " + MAX_POOL_SIZE);
}
}
class DatabaseConnection {
private final String id;
private boolean isConnected;
// Package-private constructor - only pool can create
DatabaseConnection(String id) {
this.id = id;
this.isConnected = true;
}
public String getId() {
return id;
}
public void executeQuery(String query) {
if (isConnected) {
System.out.println(id + " executing: " + query);
// Simulate query execution
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} else {
throw new IllegalStateException("Connection is closed: " + id);
}
}
public void close() {
isConnected = false;
System.out.println("Connection closed: " + id);
}
public boolean isConnected() {
return isConnected;
}
}
// Logger factory pattern
class LoggerFactory {
private LoggerFactory() {
throw new AssertionError("Cannot instantiate factory class!");
}
public static Logger getLogger(String type) {
switch (type.toLowerCase()) {
case "file":
return new FileLogger();
case "console":
return new ConsoleLogger();
case "database":
return new DatabaseLogger();
default:
return new ConsoleLogger(); // Default fallback
}
}
}
interface Logger {
void log(String message);
}
class FileLogger implements Logger {
private FileLogger() {
System.out.println("FileLogger instance created");
}
@Override
public void log(String message) {
System.out.println("[FILE] " + java.time.LocalDateTime.now() + " - " + message);
}
}
class ConsoleLogger implements Logger {
private ConsoleLogger() {
System.out.println("ConsoleLogger instance created");
}
@Override
public void log(String message) {
System.out.println("[CONSOLE] " + java.time.LocalDateTime.now() + " - " + message);
}
}
class DatabaseLogger implements Logger {
private DatabaseLogger() {
System.out.println("DatabaseLogger instance created");
}
@Override
public void log(String message) {
System.out.println("[DATABASE] " + java.time.LocalDateTime.now() + " - " + message);
}
}
Output:
=== FACTORY PATTERN AND OBJECT POOLING === === FACTORY PATTERN === Car instance created Motorcycle instance created Truck instance created Car is driving smoothly... Motorcycle is zooming... Truck is hauling heavy load... === OBJECT POOLING === ConnectionPool initialized with 5 connections Acquired connection: Connection-1 Acquired connection: Connection-2 Acquired connection: Connection-3 Connection-1 executing: SELECT * FROM users Connection-2 executing: SELECT * FROM products Released connection: Connection-1 Released connection: Connection-2 Acquired connection: Connection-1 Acquired connection: Connection-2 Connection-1 executing: SELECT * FROM orders Pool Status - Available: 2, In Use: 3, Total: 5 Released connection: Connection-3 Released connection: Connection-1 Released connection: Connection-2 === LOGGER FACTORY === FileLogger instance created ConsoleLogger instance created DatabaseLogger instance created [FILE] 2024-01-15T10:30:45.123 - This is a file log message [CONSOLE] 2024-01-15T10:30:45.124 - This is a console log message [DATABASE] 2024-01-15T10:30:45.124 - This is a database log message
Example 5: Advanced Patterns and Best Practices
public class AdvancedPatternsDemo {
public static void main(String[] args) {
System.out.println("=== ADVANCED PATTERNS AND BEST PRACTICES ===");
// Enum singleton pattern
System.out.println("\n=== ENUM SINGLETON ===");
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE;
EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE;
enumSingleton1.doSomething();
System.out.println("Same enum instance? " + (enumSingleton1 == enumSingleton2));
// Private constructor with static factory methods
System.out.println("\n=== STATIC FACTORY METHODS ===");
Configuration config1 = Configuration.fromFile("app.properties");
Configuration config2 = Configuration.fromEnvironment();
Configuration config3 = Configuration.defaultConfig();
System.out.println("Config1: " + config1);
System.out.println("Config2: " + config2);
System.out.println("Config3: " + config3);
// Immutable objects with private constructor
System.out.println("\n=== IMMUTABLE OBJECTS ===");
Money money = Money.of(100.50, "USD");
Money moreMoney = money.add(Money.of(50.25, "USD"));
System.out.println("Original: " + money);
System.out.println("After addition: " + moreMoney);
System.out.println("Original unchanged: " + money);
// Service locator pattern
System.out.println("\n=== SERVICE LOCATOR ===");
ServiceLocator locator = ServiceLocator.getInstance();
AuthService authService = locator.getService(AuthService.class);
PaymentService paymentService = locator.getService(PaymentService.class);
authService.authenticate("user", "pass");
paymentService.processPayment(100.0);
// Demonstrating best practices and pitfalls
System.out.println("\n=== BEST PRACTICES DEMONSTRATION ===");
demonstrateBestPractices();
}
public static void demonstrateBestPractices() {
// ✅ Good: Use private constructor for utility classes
System.out.println("MathUtils.add(5, 3): " + MathUtils.add(5, 3));
// ✅ Good: Use builder pattern for complex objects
EmailMessage email = EmailMessage.builder()
.to("[email protected]")
.subject("Hello")
.body("This is a test email")
.build();
System.out.println("Email created: " + email);
// ❌ Bad: Trying to instantiate utility class (commented out to avoid error)
// MathUtils utils = new MathUtils(); // COMPILER ERROR!
System.out.println("Best practices demonstrated successfully!");
}
}
// Enum singleton pattern (Joshua Bloch's preferred approach)
enum EnumSingleton {
INSTANCE;
private int value;
// Enum constructor is implicitly private
EnumSingleton() {
System.out.println("EnumSingleton instance created");
this.value = 42;
}
public void doSomething() {
System.out.println("EnumSingleton is working with value: " + value);
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
// Configuration with static factory methods
class Configuration {
private final String source;
private final java.util.Properties properties;
// Private constructor - use static factory methods
private Configuration(String source, java.util.Properties properties) {
this.source = source;
this.properties = properties;
}
// Static factory methods
public static Configuration fromFile(String filename) {
java.util.Properties props = new java.util.Properties();
// Simulate loading from file
props.setProperty("db.url", "jdbc:mysql://localhost:3306/mydb");
props.setProperty("app.name", "MyApp");
return new Configuration("file:" + filename, props);
}
public static Configuration fromEnvironment() {
java.util.Properties props = new java.util.Properties();
// Simulate loading from environment
props.setProperty("db.url", System.getenv().getOrDefault("DB_URL", "default"));
props.setProperty("app.name", System.getenv().getOrDefault("APP_NAME", "DefaultApp"));
return new Configuration("environment", props);
}
public static Configuration defaultConfig() {
java.util.Properties props = new java.util.Properties();
props.setProperty("db.url", "jdbc:h2:mem:testdb");
props.setProperty("app.name", "DefaultApp");
return new Configuration("default", props);
}
public static Configuration empty() {
return new Configuration("empty", new java.util.Properties());
}
public String getProperty(String key) {
return properties.getProperty(key);
}
@Override
public String toString() {
return String.format("Configuration{source='%s', properties=%s}", source, properties);
}
}
// Immutable money class
final class Money {
private final double amount;
private final String currency;
// Private constructor - use static factory
private Money(double amount, String currency) {
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
if (currency == null || currency.trim().isEmpty()) {
throw new IllegalArgumentException("Currency cannot be null or empty");
}
this.amount = amount;
this.currency = currency.toUpperCase();
}
// Static factory method
public static Money of(double amount, String currency) {
return new Money(amount, currency);
}
// Operations return new instances
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currencies must match");
}
return new Money(this.amount + other.amount, this.currency);
}
public Money subtract(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currencies must match");
}
return new Money(this.amount - other.amount, this.currency);
}
public Money multiply(double factor) {
return new Money(this.amount * factor, this.currency);
}
// Getters only - no setters
public double getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
@Override
public String toString() {
return String.format("%.2f %s", amount, currency);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return Double.compare(money.amount, amount) == 0 && currency.equals(money.currency);
}
@Override
public int hashCode() {
return java.util.Objects.hash(amount, currency);
}
}
// Service locator pattern
class ServiceLocator {
private static ServiceLocator instance;
private final java.util.Map<Class<?>, Object> services;
private ServiceLocator() {
services = new java.util.HashMap<>();
registerServices();
}
public static ServiceLocator getInstance() {
if (instance == null) {
instance = new ServiceLocator();
}
return instance;
}
private void registerServices() {
services.put(AuthService.class, new AuthService());
services.put(PaymentService.class, new PaymentService());
services.put(EmailService.class, new EmailService());
}
@SuppressWarnings("unchecked")
public <T> T getService(Class<T> serviceClass) {
T service = (T) services.get(serviceClass);
if (service == null) {
throw new IllegalArgumentException("Service not found: " + serviceClass.getName());
}
return service;
}
public <T> void registerService(Class<T> serviceClass, T service) {
services.put(serviceClass, service);
}
}
// Service interfaces and implementations
interface Service {
String getName();
}
class AuthService implements Service {
private AuthService() {} // Private constructor
@Override
public String getName() {
return "AuthService";
}
public boolean authenticate(String username, String password) {
System.out.println("Authenticating user: " + username);
// Simulate authentication logic
return username != null && password != null;
}
}
class PaymentService implements Service {
private PaymentService() {} // Private constructor
@Override
public String getName() {
return "PaymentService";
}
public boolean processPayment(double amount) {
System.out.println("Processing payment: $" + amount);
// Simulate payment processing
return amount > 0;
}
}
class EmailService implements Service {
private EmailService() {} // Private constructor
@Override
public String getName() {
return "EmailService";
}
public void sendEmail(String to, String subject, String body) {
System.out.println("Sending email to: " + to);
System.out.println("Subject: " + subject);
System.out.println("Body: " + body);
}
}
// Utility class with private constructor
final class MathUtils {
private MathUtils() {
throw new AssertionError("Cannot instantiate utility class!");
}
public static int add(int a, int b) {
return a + b;
}
public static int subtract(int a, int b) {
return a - b;
}
public static int multiply(int a, int b) {
return a * b;
}
public static double divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Cannot divide by zero");
}
return (double) a / b;
}
}
// Email message with builder pattern
class EmailMessage {
private final String to;
private final String subject;
private final String body;
private final String cc;
private final String bcc;
private EmailMessage(Builder builder) {
this.to = builder.to;
this.subject = builder.subject;
this.body = builder.body;
this.cc = builder.cc;
this.bcc = builder.bcc;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String to;
private String subject;
private String body;
private String cc;
private String bcc;
public Builder to(String to) {
this.to = to;
return this;
}
public Builder subject(String subject) {
this.subject = subject;
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder cc(String cc) {
this.cc = cc;
return this;
}
public Builder bcc(String bcc) {
this.bcc = bcc;
return this;
}
public EmailMessage build() {
// Validation
if (to == null || to.trim().isEmpty()) {
throw new IllegalStateException("Recipient (to) is required");
}
if (subject == null || subject.trim().isEmpty()) {
throw new IllegalStateException("Subject is required");
}
return new EmailMessage(this);
}
}
@Override
public String toString() {
return String.format("Email{to='%s', subject='%s', body='%s'}", to, subject, body);
}
}
Output:
=== ADVANCED PATTERNS AND BEST PRACTICES ===
=== ENUM SINGLETON ===
EnumSingleton instance created
EnumSingleton is working with value: 42
Same enum instance? true
=== STATIC FACTORY METHODS ===
Config1: Configuration{source='file:app.properties', properties={app.name=MyApp, db.url=jdbc:mysql://localhost:3306/mydb}}
Config2: Configuration{source='environment', properties={app.name=DefaultApp, db.url=default}}
Config3: Configuration{source='default', properties={app.name=DefaultApp, db.url=jdbc:h2:mem:testdb}}
=== IMMUTABLE OBJECTS ===
Original: 100.50 USD
After addition: 150.75 USD
Original unchanged: 100.50 USD
=== SERVICE LOCATOR ===
Authenticating user: user
Processing payment: $100.0
=== BEST PRACTICES DEMONSTRATION ===
MathUtils.add(5, 3): 8
Email created: Email{to='[email protected]', subject='Hello', body='This is a test email'}
Best practices demonstrated successfully!
Private Constructor Usage Patterns
| Pattern | Purpose | Key Feature |
|---|---|---|
| Singleton | One instance per JVM | private constructor + static getInstance() |
| Utility Class | No instantiation needed | private constructor + throw AssertionError |
| Builder Pattern | Complex object creation | private constructor + public Builder |
| Factory Pattern | Controlled instantiation | private constructor + static factory methods |
| Object Pooling | Resource reuse | private constructor + pool management |
Best Practices
✅ Do:
- Use private constructors for singletons and utility classes
- Throw AssertionError in utility class constructors
- Use static factory methods for flexible object creation
- Apply builder pattern for complex objects
- Consider enum singletons for thread safety
- Document the purpose of private constructors
❌ Don't:
- Forget to provide alternative creation methods
- Use reflection to bypass private constructors
- Create unnecessary singletons - use dependency injection when possible
- Ignore thread safety in singleton implementations
- Overuse private constructors without good reason
Common Use Cases
- Singleton Pattern: Database connections, configuration managers
- Utility Classes: Math utilities, string utilities, validation helpers
- Builder Pattern: Complex objects with many parameters
- Factory Pattern: Object creation with logic
- Object Pooling: Database connections, thread pools
- Immutable Objects: Money, configuration, value objects
- Service Locator: Dependency management
Thread Safety Considerations
- Eager initialization: Thread-safe but may waste resources
- Lazy initialization: Requires synchronization
- Double-checked locking: Performance optimization
- Enum singletons: Naturally thread-safe
- Static factory methods: Can be made thread-safe
Performance Benefits
- Object pooling: Reduces garbage collection
- Singleton: Reduces memory usage
- Utility classes: No object creation overhead
- Builder pattern: Avoids telescoping constructors
- Factory pattern: Enables caching and pooling
Conclusion
Private constructors are Java's way of saying "I'll decide how I'm created!":
- 🔒 Access control: Complete control over instantiation
- 🎯 Design patterns: Enables powerful creation patterns
- 🏗️ Object lifecycle: Manages how and when objects are created
- 📚 Encapsulation: Keeps construction logic internal
- ⚡ Performance: Enables caching and reuse
Key Takeaways:
- Private constructors prevent direct instantiation with
new - Use for singletons to ensure one instance
- Utility classes should have private constructors
- Builder pattern provides fluent object creation
- Factory methods offer flexible instantiation
Mastering private constructors empowers you to create robust, maintainable, and well-architected Java applications with controlled object creation!