Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A companion object is a singleton instance declared within a class or interface using the companion modifier. It is tied to the enclosing type’s definition rather than any specific instance. This allows its members to be accessed directly via the enclosing type name, semantically mirroring static members in languages like Java or C#, while remaining a fully-fledged object at runtime.

Syntax and Declaration

A companion object is declared inside a class or interface block. A fundamental restriction in Kotlin is that a class or interface can declare a maximum of one companion object. This is a critical distinction between a companion object and standard nested object declarations, which can be declared multiple times within the same enclosing type. By default, a companion object is implicitly named Companion, but it can be assigned an explicit identifier.
class OuterClass {
    // Only one companion object is permitted per class
    companion object {
        val defaultNamedProperty: String = "Implicitly named 'Companion'"
        fun defaultNamedFunction() {}
    }
}

interface OuterInterface {
    companion object {
        val interfaceProperty: String = "Companion inside interface"
    }
}

class NamedCompanionClass {
    companion object Core {
        val explicitlyNamedProperty: String = "Explicitly named 'Core'"
    }
}

fun main() {
    // Accessed via the enclosing type name
    OuterClass.defaultNamedFunction()
    OuterClass.Companion.defaultNamedFunction() // Equivalent

    val prop = NamedCompanionClass.explicitlyNamedProperty
    val explicitProp = NamedCompanionClass.Core.explicitlyNamedProperty // Equivalent
}

Object-Oriented Capabilities

Unlike the static keyword in Java, a companion object is a real instance at runtime. It participates in standard object-oriented paradigms, meaning it can extend classes and implement interfaces.
interface Factory<T> {
    fun create(): T
}

class Component {
    // The companion object implements the Factory interface
    companion object : Factory<Component> {
        override fun create(): Component = Component()
    }
}

// The companion object can be passed as an instance of the interface
fun registerFactory(factory: Factory<Component>) { 
    // Registration logic
}

fun main() {
    registerFactory(Component) // Passes Component.Companion implicitly
}

JVM Representation and Interoperability

At the JVM bytecode level, a companion object is compiled as a static nested class (e.g., OuterClass$Companion). The enclosing class holds a standard public static final field named Companion (or the explicit name) that points to the singleton instance of this nested class. This field is not marked with the ACC_SYNTHETIC flag; it is explicitly generated by the compiler without synthetic masking because it is designed to be fully visible and accessible from Java code. By default, members of a companion object are instance methods and properties on the companion object itself, not true static members of the enclosing class. To force the Kotlin compiler to generate true JVM static members on the enclosing class, you can use the @JvmStatic annotation, the @JvmField annotation, or the const modifier. The const modifier is the most idiomatic Kotlin approach for exposing compile-time constants (primitives and Strings) as public static final fields to Java.
class InteropClass {
    companion object {
        // Compiled as an instance method on the Companion object
        fun standardFunction() {} 

        // Compiled as an instance method AND a true static method on InteropClass
        @JvmStatic
        fun staticFunction() {} 

        // Compiled as a true static field on InteropClass, bypassing getter/setter generation
        @JvmField
        val staticField: Int = 42 

        // Compiled as a true public static final field on InteropClass
        const val COMPILE_TIME_CONST: String = "Constant value"
    }
}

Initialization Semantics

A companion object is initialized lazily when the corresponding enclosing class is loaded and resolved by the JVM for the first time. This initialization matches the semantics of Java static initializers, meaning the JVM guarantees thread safety during the creation of the companion object instance without requiring explicit synchronization blocks.

Companion Object Extensions

If a class or interface declares a companion object (even an empty one), you can define extension functions and properties for it. This allows you to attach type-level functions to a type from outside its original definition.
class ExtensibleClass {
    // An empty companion object is required as an anchor
    companion object 
}

// Defining an extension function on the companion object
fun ExtensibleClass.Companion.externalClassLevelFunction() {
    println("Extension resolved statically")
}

fun main() {
    // Invocation
    ExtensibleClass.externalClassLevelFunction()
}
Master Kotlin with Deep Grasping Methodology!Learn More