Constant declaration and the use of companion objects are important Kotlin concepts that replace the familiar static members from Java and the associated difficulties when crossing OOP and functional programming paradigms.
Background: In Java, static final fields are typically used for constants, and static methods for utility or factory functions. In Kotlin, instead of static, the concepts of object and companion object were introduced, and for compile-time constants — the keyword const.
Problem: There is a need to declare values that are independent of class instances, as well as to organize factory methods and static state without violating OOP integrity.
Solution: Companion objects are declared within a class and allow placing members common to all instances:
Code example:
class MyClass { companion object { const val DEFAULT_LIMIT = 10 fun create(): MyClass = MyClass() } } val limit = MyClass.DEFAULT_LIMIT val instance = MyClass.create()
Key features:
Can a companion object have multiple instances in a class?
No, a class can have only one companion object. An attempt to declare a second one will lead to a compilation error. However, any number of methods/properties are allowed inside the companion object.
Can lateinit variables be initialized inside a companion object?
No, because properties with const or variables inside a companion object must be initialized immediately or be val/var with explicit initialization. lateinit is not allowed for properties inside the companion object.
Can a companion object have its own name and when is this required?
Yes, the name of the companion object can be specified explicitly if you need to refer to it by name or, for example, implement interfaces. In other cases, it is optional. Example:
class Foo { companion object Factory { fun create(): Foo = Foo() } } val instance = Foo.Factory.create()
All auxiliary functionality and global variables of the program are placed in a companion object, using var instead of val/const:
Pros:
Cons:
Only compile-time constants (const val) and pure functions are used inside the companion object, all mutable items are either localized or passed through DI:
Pros:
Cons: