Search code examples
kotlinsingleton

When we need to choose companion objects over object class to achieve Singleton?


This question was asked by an interviewer recently. I replied that companion object is class level singleton and thus can't have multiple companions. He further asked that we If we have to create singleton class then we can use 'object' keyword to create singleton class. In which situation you should choose companion object over object keyword. This was confusing to me and i couldn't satisfy the interviewer. So, in which situation it is best to use companion object over object keyword.

I was not able to find any reliable answer on stack overflow.


Solution

  • I don't think there's an "objective" answer to this question, but there are some properties of objects and companion objects that one can compare to come to some kind of strategy of what to choose in which situation.

    Generally speaking, any kind of object is a singleton declaration in Kotlin, basically a class that instantiates itself once at class loading time and stays alive for the entire lifetime of the program. This is true for both object and companion object. More precisely, companion object is a specialization of object, so there really is a lot similar for these two. The specific properties of a companion object that differentiate it from a "regular" object are:

    • It cannot exist top-level (has to be nested, for example, inside a class)
    • There can at most be one companion object per class (as you already correctly pointed out)
    • You don't have to give a companion object an explicit name (in case you don't, it's implicitly called "Companion")
    • You don't have to fully-qualify access to its members, it's enough to specify only the outer class name

    Practical example: You can nest a regular object and a companion object in a class:

    class Foo {
        object Bar {
            val barValue = 1
        }
        
        companion object Baz { // you can omit the name
            val bazValue = 2
        }
    }
    

    But then, there are differences in how to access Bars / Bazs members:

    println(Foo.Bar.barValue) // OK (prints 1)
    println(Foo.Baz.bazValue) // OK (prints 2)
    println(Foo.barValue) // NOT OK (does not compile)
    println(Foo.bazValue) // OK (prints 2)
    

    With all that being said, I would come to the following conclusions regarding usage of objects vs companion objects for the singleton pattern:

    • All objects, including companion objects, are a valid realization of the singleton pattern
    • The design goal of companion objects is to emulate Java's static members, so whenever you need something like static members accompanying a class, companion object is definitely the choice to make.
    • companion objects always have a relation to their enclosing class, it depends on the situation whether that constitutes desirable semantics or is superfluous.
    • If you only have static members in your conceptual singleton, then having a class containing nothing else than a companion object is superfluous in comparison to a toplevel object. Example:
    // this is unneccessary
    class Foo {
        companion object {
            fun myStaticMember() { /*...*/ }
        }
    }
    // refactor to:
    object Foo {
        fun myStaticMember() { /*...*/ }
    }
    
    • There is also a pattern to make the enclosing class open and then making the companion object extend it, basically to provide a "default instance" of the enclosing class:
    open class Foo(val value: Int) {
        companion object Default : Foo(42)
    }
    
    val arbitrary = Foo(12)
    val default = Foo
    println(arbitrary.value) // 12
    println(default.value) // 42
    

    The tl;dr: If you want to realize something like a global service, go for object. If you need static members accompanying a "regular" class, go for companion object.