Where Java and TypeScript write static, Kotlin nests an object in the class and marks it companion. You call its members through the class name, and because it lives inside the class it can call a private constructor, which makes it the natural home for factories.
class Book private constructor(val id: Int, val title: String) {
companion object {
const val CURRENCY = "USD"
private var nextId = 1
fun create(title: String) = Book(nextId++, title)
@JvmStatic fun sample() = create("The Quiet Harbor")
}
override fun toString() = "#$id $title"
}
fun main() {
println(Book.sample())
println("${Book.Companion.create("Salt and Saffron")}, prices in ${Book.CURRENCY}")
}#1 The Quiet Harbor #2 Salt and Saffron, prices in USD
Book.create(...) is shorthand for Book.Companion.create(...). The companion is a real object that can implement interfaces, which is why Android declares a ViewModel factory as companion object { val Factory = viewModelFactory { ... } } (ViewModel and UDF). Under the hood, javap on Book.class shows a static field Companion of type Book$Companion, which alone holds create; @JvmStatic adds a real public static final Book sample() to Book for Java callers, and const val becomes a static final field inlined at each use.