Not so Singletons in Kotlin Putting Kotlin's Object Declaration to a test

Scroll this

Singleton pattern is one of the most common design patterns in programming. If you have been programming in Java you will know that creating an optimized and safe singleton in Java is a pain. To ease that, Kotlin makes creating singletons super easy. But are they truly singletons?

What Makes a Good Singleton?

There is no ultimate manual or guide to creating a perfect singleton class. However, there are some guidelines which have developed over time in response to real life problems that defines the quality of a good and safe singleton. Here are the three pillars of a reliable singleton class,

  • Lazy initialization
  • Thread safety
  • Reflection proof

Now, the question that comes is whether Kotlin’s Object Declaration (which is a simplified way of creating a Singleton class) follows these guidelines or not. Let’s have a look at how they are created.

Exploring Kotlin’s Object Declarations

From the surface, object declarations are dead simple. You just put down the keyword object in front of a class name and you get a singleton of that class with no additional steps or effort. The declaration of a simple singleton class boils down to this,

object ForeverAlone {
    var playCount: Int = 0

    fun singLonelySong() {
        println("Lonely, I'm so lonely!")
        playCount++
    }
}

And voila! You can access the function singLonelySong() using the class name, just like this,

ForeverAlone.singLonelySong()

To make things interesting, I decompiled the Kotlin Bytecode of this class to reveal the generated Java class, which is as follows,

public final class ForeverAlone {
   private static int playCount;
   public static final ForeverAlone INSTANCE;

   public final int getPlayCount() {
      return playCount;
   }

   public final void setPlayCount(int var1) {
      playCount = var1;
   }

   public final void singLonelySong() {
      String var1 = "Lonely, I'm so lonely!";
      System.out.println(var1);
      int var2 = playCount++;
   }

   private ForeverAlone() {
      INSTANCE = (ForeverAlone)this;
   }

   static {
      new ForeverAlone();
   }
}

From this class definition above we can observe that if we obtain two instances of this class, they will point to the same object because the constructor is made private(thus cannot be accessed outside the class to create an instance) and the instance of the class is created only once on class loading and supplied whenever needed. This satisfies the base requirement of the Singleton Design Pattern.

So congratulations, we have successfully created a singleton class with basically no extra effort, but how does it fare with our three pillars to a reliable singleton?

Lazy Initialization

Looking from a Java perspective this class definition will say that the object is not being initialized lazily because we might have a public static variable in the class which should be accessed without creating an instance of the class.

From the code snippet in the previous section, it’s evident that the object is being initialized on class load because the initialization is being done in the static block of the class. Therefore, if we try to access a static member of the class an instance of the class will be generated even though we don’t need it to access the static members.

However, this is Kotlin. In Kotlin we access properties of a class by their names which in turn gets resolved by calling their respective getters under the hood which needs an active instance to work. Also, we cannot create static members in Kotlin without the means of an object declaration, so it’s safe to say that this approach emulates lazy initialization.

Thread Safety

To fulfill this criterion our singleton class needs to make sure that if two threads are trying to access the class simultaneously, they should not get two different objects of the class but get a single shared object to work on.

If you remember, our class instantiates an object in its static block, like this,

static {
   new ForeverAlone();
}

Since our class here is getting instantiated in the static block, it’s wise to say that our singleton class is thread safe. This is due to the fact that the static block is executed only once when the class is loaded and therefore the object is instantiated only once as well. There goes two of our requirements satisfied.

Reflection Proof

This is where things get a little nasty. One of the tricks to break the singleton pattern is to use reflection to access the private constructor, turn it to public and create as many objects we want by invoking the constructor.

Let’s see if we can do the same with our singleton class. Consider the following function where we use reflection to perform our little hacking,

fun testSingletonUsingReflection() {
    val firstInstance = ForeverAlone
    val secondInstance = ForeverAlone
    var thirdInstance: ForeverAlone? = null

    val constructors = ForeverAlone::class.java.declaredConstructors
    constructors.forEach {
        it.isAccessible = true
        thirdInstance = it.newInstance() as ForeverAlone
        return@forEach
    }

    println("Reflection Test: 1st Instance - $firstInstance")
    println("Reflection Test: 2nd Instance - $secondInstance")
    println("Reflection Test: 3rd Instance - $thirdInstance")
}

If we run this function we will be getting the following output on the console,

Reflection Test: 1st Instance - co.upcurve.kotlinsampleapp.ForeverAlone@3f4a621
Reflection Test: 2nd Instance - co.upcurve.kotlinsampleapp.ForeverAlone@3f4a621
Reflection Test: 3rd Instance - co.upcurve.kotlinsampleapp.ForeverAlone@84efa46

Since we accessed our singleton class in the standard way for the first two instances, we are supplied the same object for the class, which is clearly evident from the object hash code.

However, for the third object, we did a little trick of our own. We modified the private constructor to a public one and invoked it to create a new object and we can clearly see that the third instance has a different hash code from the first two.

If the class was truly a singleton it would have returned the same object for this case as well.

Verdict

Though Kotlin’s object declaration satisfies the first two criteria of being a reliable singleton class, it falls short of fulfilling the last one. However, it is to be noted that access by reflection is not a very common approach and this class declaration will work reliably for most cases where this reflection trick is not applied.

If you’re willing to ignore that corner case, object declarations are a super convenient way to create singletons in Kotlin and should be used depending on your project requirements.

Submit a comment