An object declaration within another class can be marked with the companion keyword to become a companion object. Companion objects connect the initialization of an object declaration to the instantiation of the enclosing class, and there can be only a single companion object for a class. This lets us scope whatever functionality the object declaration provides to the namespace of the enclosing class, cleaning up the global namespace and adding useful semantics in many cases.
In this snippet, we've defined a companion object for SomeClass:
class SomeClass {
companion object {
fun foo() {}
}
}
We can access the companion object in one of the following two ways:
fun main(args: Array<String>) {
SomeClass.Companion.foo()
SomeClass.foo()
}
You might notice that we didn't provide a name for the companion. By default, the name of a companion object will be Companion, but it's possible to provide your own name as well. We could rename our companion object by specifying a name after the object keyword. In this example, we've renamed the companion to Factory:
class SomeClass {
companion object Factory {
fun foo() {}
}
}
After renaming the companion to Factory, we can now access it using .Factory rather than .Companion:
fun main(args: Array<String>) {
SomeClass.Factory.foo()
SomeClass.foo()
}
This flexibility in companion object naming can be really useful when considering the Kotlin-to-Java interop experience and will be explored further in Chapter 8, Controlling the Story.
We've seen how to access companion objects, but how, and when, are they actually initialized and ready for use? Companion objects are initialized in two cases:
- When the enclosing class is initialized
- When a function or property on the companion object is referenced
In either of the preceding cases, a companion object is created:
fun main(args: Array<String>) {
SomeClass.foo()
SomeClass()
}
Once the companion object is initialized, it can be treated as any other class. Companion objects may define their own properties and methods and can implement interfaces or even extend other classes:
class SomeClass {
companion object Factory : SomeInterface {
const val someVal = "val"
fun foo() {}
override fun doSomething(foo: String) {
super.doSomething(foo)
// do something
}
}
}
Companion objects have access to private constructors of their enclosing class and can, therefore, be used to implement a factory method if the enclosing class has only private constructors:
class SomeClass private constructor() {
private val id = "id"
companion object Factory : SomeInterface {
const val someVal = "val"
fun foo() {}
fun createSomeClass() = SomeClass()
override fun doSomething(foo: String) {
super.doSomething(foo)
}
}
}
We'll explore the further use of companion objects and the Factory pattern in Chapter 17, Practical Design Patterns. Classes and companion objects are great for defining reusable, named classes. However, sometimes we only need a one-off class that won't be reused. For these cases, we can use object expressions.