skip to content

Show how a Kotlin `object` replaces the classic Java singleton idiom, and explain what the compiler generates for JVM/Java interop.

level: middleimportance: should knowfreq 55%

answer

  1. Java: private ctor + static getInstance (+ volatile DCL)
  2. Kotlin: just `object Name`
  3. Compiler emits `public static final INSTANCE`
  4. Java reaches it via Name.INSTANCE.member
  5. @JvmStatic / const val / @JvmField for clean Java API

basics

~20 s

In Java you wrote a private constructor and a static getInstance method. In Kotlin you just write object Name. Behind the scenes Kotlin creates one static INSTANCE field, which is how Java code reaches it.

solid answer

~40 s

The Java singleton needed: a `private` constructor, a `private static` field, and a `public static getInstance()` (often with double-checked locking). A Kotlin `object Name { ... }` replaces all of it. The compiler emits a final class with a `private` constructor and a `public static final Name INSTANCE` field initialized in the static block, so initialization is once-and-thread-safe via class loading. Kotlin call sites use `Name.member` directly. **Java** callers must go through the field: `Name.INSTANCE.member()`. To make object members callable as plain statics from Java, annotate them `@JvmStatic`, or expose constants with `const val`/`@JvmField`. This removes the hand-rolled locking boilerplate and the risk of getting double-checked locking subtly wrong.

code

kotlin · 8 lines
kotlin
object IdGenerator {
    private val seq = java.util.concurrent.atomic.AtomicLong()
    @JvmStatic fun next(): Long = seq.incrementAndGet()
    const val PREFIX = "id-"
}
// Kotlin: IdGenerator.next()
// Java:   IdGenerator.next()  (thanks to @JvmStatic)
//         IdGenerator.PREFIX  (const)

go deeper

for a junior

Knows object replaces the boilerplate Java singleton.

for a middle

Shows the generated INSTANCE field and Java access pattern.

for a senior

Adds @JvmStatic/const val/@JvmField interop and why class-init beats DCL.

for a principal

Frames singletons as global state, weighing testability/DI trade-offs against convenience.

## The Java idiom being replaced ```java public final class Config { private static volatile Config instance; private Config() {} public static Config getInstance() { if (instance == null) { synchronized (Config.class) { if (instance == null) instance = new Config(); } } return instance; } public String name() { return "cfg"; } } ``` This is error-prone: forgetting `volatile`, or the second null check, breaks correctness on the JVM memory model. ## The Kotlin equivalent ```kotlin object Config { fun name() = "cfg" } // Kotlin call site: Config.name() ``` That's the entire singleton. Lazy + thread-safe initialization is handled by the JVM class-loader (see the static-init mechanism). ## What the compiler generates Roughly: - a final class `Config`, - a `private` constructor (so no one else can instantiate), - a `public static final Config INSTANCE` field, - a static initializer assigning `INSTANCE = new Config()`. ## Java interop knobs Because members live on the *instance*, Java sees them through the field by default: ```java String n = Config.INSTANCE.name(); ``` To improve the Java-facing API: - **`@JvmStatic`** on a function/property generates a real static method so Java writes `Config.name()`. - **`const val X = ...`** (compile-time constants) become `public static final` fields, inlined at use sites. - **`@JvmField`** exposes a property as a public field (no getter), useful for simple shared constants. ```kotlin object Config { const val MAX = 100 // Java: Config.MAX @JvmStatic fun name() = "cfg" // Java: Config.name() } ``` ## Why prefer the object - No locking boilerplate, no memory-model footguns. - Less code, single source of truth. - Still flexible: the object can implement interfaces and hold state. The one caution: like any singleton, an `object` is global mutable state if it holds mutable fields — that hurts testability and should be used deliberately (constants, stateless helpers, registries), not as a dumping ground.

  • What does Java write to call an object function that is NOT @JvmStatic?
    `ObjectName.INSTANCE.func()` — it goes through the generated static `INSTANCE` field.
  • Why is the Kotlin version safer than naive Java?
    It relies on guaranteed once-only class initialization, so there's no risk of getting double-checked locking or `volatile` wrong.

saying these in an interview costs you the question

  • Saying Java calls an object's method directly as Name.method() without @JvmStatic
  • Forgetting the generated INSTANCE field exists
  • Claiming you still need to write getInstance in Kotlin
  • Not acknowledging the testability cost of global singletons

context