Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
In this article, we’ll explain singleton classes and how we can create them in Kotlin. Singleton implementations in Java can be found here.
Singleton classes are a design pattern used to restrict the instantiation of a class to a single instance. In other words, a singleton class is a class that can only be instantiated once, and any subsequent attempts to instantiate the class will return the same instance that was created previously.
The need for singleton classes arises when it is necessary to maintain a single instance of a class for the entire lifetime of an application. For example, we might use a singleton class to represent a database connection, a configuration manager, or a logging service. By restricting the number of instances of these classes to one, we can ensure that the state of the application remains consistent and that there are no conflicts between multiple instances of the same class.
This section will cover different techniques to create a singleton class in Kotlin.
For the following examples, we’ll use the following unit test to verify that only one instance exists:
val instance1 = // Put instance creation here
val instance2 = // Put instance creation here
Assertions.assertSame(instance1, instance2)
Assertions.assertEquals("Doing something", instance1.doSomething())
First, we create two instances of a singleton. Then we verify that these instances are the same with a simple assertion. In the last step, we also verify that the doSomething method returns as expected.
We can implement a singleton in Kotlin using a companion object. It is a special type of object that is associated with a class and can be used to store static members and methods for the class. To create such a singleton, we store the single instance in the companion object of the singleton class:
class Singleton private constructor() {
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance() =
instance ?: synchronized(this) {
instance ?: Singleton().also { instance = it }
}
}
fun doSomething() = "Doing something"
}
The @Volatile annotation is needed to ensure that the instance property is updated atomically. This prevents other threads from creating more instances and breaking the singleton pattern. We need the synchronized keyword in the static getInstance method to prevent accessing the method from multiple threads simultaneously.
We can retrieve the instance with the following statement:
val instance = Singleton.getInstance()
The object keyword in Kotlin is used to declare a singleton class, also known as an object declaration. An object declaration is a concise way of creating a singleton class without the need to define a class and a companion object. It is an example of eager initialization, which creates the singleton when the class is first accessed. The instance remains in memory until the application is terminated:
object Singleton {
fun doSomething() = "Doing something"
}
We can retrieve the instance with the following statement:
val instance = Singleton
To create a lazy initialization singleton in Kotlin, we can use a lazy delegate. A lazy delegate only allows us to initialize a property when it is first accessed. The instance remains in memory until the application terminates:
class Singleton private constructor() {
companion object {
val instance:Singleton by lazy {
Singleton()
}
}
fun doSomething() = "Doing something"
}
We can retrieve the instance with the following statement:
val instance = Singleton.instance
Note that this implementation is not thread-safe.
Double-checked locking is a mechanism to reduce the overhead of synchronization by checking the lock only once and creating the instance only if the lock is not already held by another thread. Every time we want to retrieve the singleton instance, we perform a null check. If the singleton instance has already been created, we return it. If there is no instance available, we’ll create it.
First, we create a lock using the synchronized keyword. In a multithreaded application, it is possible that, in the meanwhile, another thread created the instance. Therefore, we need another null check, and only if the instance is still null we’ll create it:
class Singleton private constructor() {
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance(): Singleton {
if (instance == null) {
synchronized(this) {
if (instance == null) {
instance = Singleton()
}
}
}
return instance!!
}
}
fun doSomething() = "Doing something"
}
We can retrieve the instance with the following statement:
val instance = Singleton.getInstance()
The double-locking singleton is a powerful and efficient way of creating singleton classes in Kotlin, and it is similar to the double-locking singleton implementation in Java.
We can use enums to ensure that we only create one instance of a class in an application. Enum singletons are thread-safe by default, and they are easy to implement:
enum class Singleton {
INSTANCE;
fun doSomething() = "Doing something"
}
We can retrieve the instance with the following statement:
val instance = Singleton.INSTANCE
In this section, we’ll cover the main advantages and disadvantages of singleton classes.
The advantages of singleton classes include the following:
The disadvantages of singleton classes include the following:
Singleton classes have both advantages and disadvantages. When deciding whether to use a singleton class, it is essential to weigh the advantage and disadvantages and consider the specific requirements of our application.
In this article, we learned that singleton classes are classes that ensure that we only create one instance.
We learned the different ways how we could implement singleton classes in Kotlin. There is, on the one hand, the object keyword, which is Kotlin-specific. On the other hand, we can also create a singleton using an enum or double-locking. In the end, we covered the advantages and disadvantages of singleton classes. Although singletons have several advantages, like easier state management or improved performance, they also have a few disadvantages, which make them more difficult to test.