DEV Community

Ban Duong
Ban Duong

Posted on

[Java] Singleton and Static

Context

Both the singleton pattern and the static keyword are concepts that involve shared resources within an application. But when should we use each of them? This article aims to provide clarity on this matter.

What is singleton pattern?

The singleton pattern is one of the common design patterns. It ensures that the class has only one instance created. For example, we only need one instance when using a logger or accessing the configuration of the application.

public class Logger {
    private static Logger instance;

    private Logger() {
        // Private constructor to prevent instantiation outside this class
    }

    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }

    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}
Enter fullscreen mode Exit fullscreen mode

What is static keyword in Java?

In Java, the static keyword can be used for variables, methods, and classes.

With static variables and methods, it means these entities belong to the class itself rather than to individual objects. They can be accessed through the class without the need to create an instance.

Regarding static classes, only nested classes can be static; the outer class cannot be static. However, a common use case is to make the class final and have static methods inside it.

public final class Utility {

    public static final int num = 10;

    public static void doSomething() {
    }
}
Enter fullscreen mode Exit fullscreen mode

So which one we should use?

1.Initialization

If initialization is required for an instance, we should use a singleton because it allows initialization in the singleton constructor, while static methods provide straightforward access without initialization. Conversely, if initialization is not needed, we can use a static class because it simplifies the code and offers better performance due to static binding at compile time.

Example of the singleton approach

public class Logger {
    private static Logger instance;
    private PrintWriter writer;

    private Logger() {
        try {
            writer = new PrintWriter(new FileWriter("log.txt", true));
            writer.println("Log file initialized.");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }

    public void log(String message) {
        writer.println(message);
        writer.flush();
    }
}
Enter fullscreen mode Exit fullscreen mode

Example of the static approach

public class Logger {
    private static PrintWriter writer;

    static {
        try {
            writer = new PrintWriter(new FileWriter("log.txt", true));
            writer.println("Log file initialized.");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void log(String message) {
        writer.println(message);
        writer.flush();
    }
}
Enter fullscreen mode Exit fullscreen mode

In the static approach, the 'writer' isn't initialized in the constructor method; instead, it's initialized in a static block and will be created when the class is loaded. In the singleton approach, the 'writer' is initialized in the constructor method and is created when we call the getInstance method.

2.Extension

With singletons, we can utilize inheritance and polymorphism to extend a base class, implement an interface, and provide different implementations through method overriding. However, static methods cannot be overridden since their resolution occurs at compile time rather than runtime.

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }

    static void eat() {
        System.out.println("Animal eats");
    }
}

class Dog extends Animal {
    // Overriding the makeSound method
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }

    // Hiding the eat method (not overriding)
    static void eat() {
        System.out.println("Dog eats bones");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Animal dog = new Dog();

        // Calling non-static method, resolved at runtime
        animal.makeSound(); // Output: Animal makes a sound
        dog.makeSound();    // Output: Dog barks (method overridden)

        // Calling static method, resolved at compile time
        Animal.eat(); // Output: Animal eats
        Dog.eat();    // Output: Dog eats bones (method hidden)
    }
}

Enter fullscreen mode Exit fullscreen mode

3.Loading

Singletons can be initialized via lazy loading, while static classes are initialized during the first load.

Let's use the example in section one: Initialization. In the singleton approach, the 'writer' will only be created when the constructor method is invoked through the getInstance method. This means if we haven't used the Logger instance yet, it won't be initialized. However, with the static approach, the 'writer' will be created when the class is loaded.

4.Managing State

Singletons are a better option when state management is required because they offer control over initialization and finalization, which static classes do not provide.

Example of the singleton approach

public class DatabaseConnection {
    private static DatabaseConnection instance;
    private boolean isConnected;

    private DatabaseConnection() {
        isConnected = false;
    }

    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }

    public void connect() {
        isConnected = true;
        System.out.println("Connected to database");
    }

    public void disconnect() {
        isConnected = false;
        System.out.println("Disconnected from database");
    }
}

Enter fullscreen mode Exit fullscreen mode

Example of the static approach

public class DatabaseConnection {
    private static boolean isConnected;

    private DatabaseConnection() {}

    public static void connect() {
        isConnected = true;
        System.out.println("Connected to database");
    }

    public static void disconnect() {
        isConnected = false;
        System.out.println("Disconnected from database");
    }
}

Enter fullscreen mode Exit fullscreen mode

In a static approach, everything from methods to variables needs to be static as well. This makes it less encapsulated, and it doesn't have an instance state to manage because static variables belong to the class itself, not the instance. With a singleton, we can control initialization and finalization at runtime, but with static, it's automatically created when the application starts and only destroyed when the application exits.

Conclusion

If the purpose is a class with utility functions, requiring no state or initialization, opt for a static class. Otherwise, if state management or initialization is necessary, choose a singleton instead.

Top comments (0)