Sunday, April 30, 2023

Kotlin Singleton Comparative Analysis with Java

 This is the most popular single guy in the neighborhood. Everybody knows him, everybody talks about him, and anybody can find him easily. Even people who will frown when other design patterns are mentioned will know it by name. At some point, it was even proclaimed an anti-pattern, but only because of its wide popularity. So, for those who are hearing about it for the first time, what is this pattern about? Usually, if you have an object, you can create as many of its instances as you want. 

Say, for example, you have the Cat class:

class Cat

You can produce as many of its instances (cats, to be precise), as you want: 

val firstCat = Cat()

val secondCat = Cat()

val yetAnotherCat = Cat() 

And there's no problem with that. What if we wanted to disallow such behavior? Clearly, we have to create an object in some way for the first time. But from the second time on, we need to recognize that this object was initialized once already, and returns its instance instead. That's the main idea behind being a Singleton. In Java and some other languages, this task is quite complex. It's not enough to simply make the constructor private and remember that the object was initialized at least once already. We also need to prevent race conditions, where two separate threads try to initialize it exactly at the same time. If we allowed that, it would break the entire concept of a Singleton, as two threads would hold references to two instances of the same object. Solving this problem in Java requires doing one of the following: Accepting that a Singleton will initialize eagerly when your application starts, and not when it is first accessed Writing some smart code to prevent such race conditions and still stay performant Using a framework that already solves it Kotlin just introduces a reserved keyword for that. Behold, an object as follows:

 object MySingelton{} 

You don't need curly brackets there. They're just for visual consistency.

This combines declaration and initialization in one keyword. From now on, SingletonAnalysisWithJavacan be accessed from anywhere in your code, and there'll be exactly one instance of it. Of course, this object doesn't do anything interesting. Let's make it count the number of invocations instead: 

import java.util.concurrent.atomic.AtomicInteger

object SingletonAnalysisWithJava {
private val counter = AtomicInteger(0)

fun increment() = println(counter.incrementAndGet())
}

We won't test it for thread safety yet Threads and Coroutines. For now, we test it only to see how we

 call our Singleton: for (i in 1..10) {

    println(CounterSingleton.increment())

}

fun main(args: Array<String>) {

// Singleton test
for (i in 1..10){
SingletonAnalysisWithJava.increment()
}
}

This will print numbers between 1 and 10, as expected. As you can see, we don't need the getInstance() method at all. The object keyword is used for more than just creating Singletons. We'll discuss it in depth later. Objects can't have constructors. If you want some kind of initialization logic for your Singleton, such as loading data from the database or over the network for the first time, you can use the init block instead: 

object CounterSingleton {

 init {

        println("I was accessed for the first time")

    }

    // More code goes here

}

It is also demonstrated that Singletons in Kotlin are initialized lazily, and not eagerly, as some could suspect from the ease of their declaration. Just like regular classes, objects can extend other classes and implement interfaces.

Moral of the story is 

The Singleton pattern is widely known and popular in the neighborhood. It's a way to ensure that only one instance of an object is created and can be accessed from anywhere in the code. Typically, when an object is created, multiple instances can be produced without any problem. However, with the Singleton pattern, after the first initialization, the object returns the same instance every time it's called. In Java and other languages, implementing this pattern can be complex due to race conditions that could break the concept of a Singleton. To solve this, one could either accept that the Singleton initializes eagerly when the application starts, write smart code to prevent race conditions, or use a framework that already handles it. In Kotlin, declaring a Singleton is easier with the "object" keyword . The "object" keyword is used to create more than just Singletons and can extend classes and implement interfaces like regular classes. Initialization logic for a Singleton can be placed in the "init" block.


1 comment:

Anonymous said...

Very well explained