Should I create singleton with Object or file-level val?

Viewed 180

We'd like to have a single instance of a class that will be used throughout our codebase. We created a dedicated package to hold this instance. If I understand it right, there are two ways to declare a singleton.

(The examples use Jackson but this is not specific to it in any way.)

Object

package net.goout.jackson

import com.fasterxml.jackson.databind.ObjectMapper
...

object Jackson : ObjectMapper() {
    init {
        configure(...)
    }
}

File-level val

package net.goout.jackson

import com.fasterxml.jackson.databind.ObjectMapper
...

val jackson = ObjectMapper().apply {
    configure(...)
}

In both cases only one instance is ever created and the call sites are identical (apart from the slight difference in the naming).

Is there any practical difference between the two approaches?

1 Answers

I can't spot any practical differences, no!

Looking at the generated bytecode shows that the two cases are more similar than you might think: both have a static initialiser, and a field.  The main difference is that in the object case those are in a new class and the field (INSTANCE) is public, while in the var case it's private and accessed through a getter method in a hidden class named …Kt.  (In practice, the getter will usually get optimised out anyway.)  That might affect the initialisation order, but that won't matter here.

At the language level, the object version will create a new subclass, while the val version won't — so you might see the latter as being slightly ‘purer’, though I can't see how that would matter to you.  And if you were calling from Java, the two cases would have different complications — though the question only mentioned Kotlin.

And the val version is very slightly shorter and simpler — only a tiny advantage, but in the absence of any other factors, brevity and simplicity are good things!

By the way, the question assumes that the configure() method is ‘fluent’, i.e. returns the object on which it's called.  But even if not, you can get the same effect by simply surrounding it in an apply() call.

Related