Kotlin has a companion object feature that lets us associate a single object with a class. Companion objects can serve a number of purposes. For example, we may wish to associate methods such as factory methods with a companion object rather than an instance of a class. Likewise, we can use companion object to store constants or count the number of times an instance of a class was created.
Here is an example Burger class that uses a companion object.
class Burger(val name : String) { //Create and define a companion object. companion object SpecialBurgers { //Here are some constants const val TOP_BUN_BURGER = "Top Bun Burger" const val NATIONAL_PASS_THYME_BURGER = "National Pass Thyme Burger" const val KALE_MARY_BURGER = "Kale Mary Burger" const val SNIPWRECKED_BURGER = "Snipwrecked Burger" //Count the number of burgers that were created private var _instances = 0 val burgers : Int get() = _instances fun burgerList() = listOf(TOP_BUN_BURGER, NATIONAL_PASS_THYME_BURGER, KALE_MARY_BURGER, SNIPWRECKED_BURGER) } init { check(name in burgerList(), { "$name not in Burger List"}) //Increment the number of burgers created _instances++ } }
The Burger class has a companion object called SpecialBurgers. Readers will notice that creating a companion object is almost the same as writing a class. The difference is that the companion object is instantiated with the JVM loads the Burger class into memory. Companion objects can have init blocks, but they are not allowed to have constructors. Finally, we are not allowed to create instances of companion objects.
The SpecialBurgers object has four constants, a variable called _instances, and some methods. The init block in Burger increments _instances. It also calls burgerList() found in the companion object to check if the name variable is found in burgerList(). If the name isn’t in burgerList(), the program will throw an exception.
We can use the companion object later on by using the class name followed by the dot (.) syntax. Here is the later half of the program that uses the companion object.
fun main(args : Array<String>){ val badBurger = "Barley Davidson Burger" try { //This will throw an exception because of the //check in Burger's init block val barleyBurger = Burger(badBurger) } catch (e : Exception){ println("$badBurger is not in ${Burger.burgerList()}") } //We can reference the KALE_MARY_BURGER constant found in //Burger's companion object val firstBurger = Burger.KALE_MARY_BURGER val kaleBurger = Burger(firstBurger) //This next line uses the burgers property found in SpecialBurgers to //print how many burgers we have created println("Created ${kaleBurger.name} and we have served ${Burger.burgers} burgers") //We can also reference our burger constants directly val snipsBurger = Burger(Burger.SNIPWRECKED_BURGER) println("Created ${snipsBurger.name} and we have served ${Burger.burgers} burgers") //Finally, the SpecialBurgers is an actual object, so we //can store it in a variable when needed val burgerCompanion = Burger.SpecialBurgers val topBunBurger = Burger(burgerCompanion.TOP_BUN_BURGER) println("Created ${topBunBurger.name} and we have served ${Burger.burgers} burgers") }
We use the SpecialBurgers object in a few different ways in main. First, we try and make a barleyBurger. Creating a barleyBurger throws an exception because it’s burger name, “Barley Davidson Burger” is fails the name check found in Burger’s init block.
The next burger we try making is the kaleBurger. First we show how to store a value found in Burger.KALE_MARY_BURGER in a variable. As you can see, it acts just like any other variable. The next line creates an instance of Burger by passing firstBurger to the constructor of Burger. Creating the kaleBurger succeeds and the init block in Burger increments the _instances variable by one. The next line prints out the name of the burger and how many burgers we have served. Notice that it’s Burger.burgers because we are using the burger property found on SpecialBurgers.
The snipsBurger is created by passing Burger.SNIPWRECKED_BURGER direclty into the Burger’s constructor. Once against _instances is incremented and we now have served two specialty burgers. The final burger is created by first assigning SpecialBurgers to a variable first. Since SpecialBurgers is an object, it can be assigned to other references when needed. We create our topBunBurger by using burgerCompanion.TOP_BUN_BURGER.
Putting it Together
Here is the entire program, followed by the output.
package ch4.companionobjects class Burger(val name : String) { companion object SpecialBurgers { //Heere are some constants const val TOP_BUN_BURGER = "Top Bun Burger" const val NATIONAL_PASS_THYME_BURGER = "National Pass Thyme Burger" const val KALE_MARY_BURGER = "Kale Mary Burger" const val SNIPWRECKED_BURGER = "Snipwrecked Burger" //Count the number of burgers that were created private var _instances = 0 val burgers : Int get() = _instances fun burgerList() = listOf(TOP_BUN_BURGER, NATIONAL_PASS_THYME_BURGER, KALE_MARY_BURGER, SNIPWRECKED_BURGER) } init { check(name in burgerList(), { "$name not in Burger List"}) //Increment the number of burgers created _instances++ } } fun main(args : Array<String>){ val badBurger = "Barley Davidson Burger" try { //This will throw an exception because of the //check in Burger's init block val barleyBurger = Burger(badBurger) } catch (e : Exception){ println("$badBurger is not in ${Burger.burgerList()}") } //We can reference the KALE_MARY_BURGER constant found in //Burger's companion object val firstBurger = Burger.KALE_MARY_BURGER val kaleBurger = Burger(firstBurger) //This next line uses the burgers property found in SpecialBurgers to //print how many burgers we have created println("Created ${kaleBurger.name} and we have served ${Burger.burgers} burgers") //We can also reference our burger constants directly val snipsBurger = Burger(Burger.SNIPWRECKED_BURGER) println("Created ${snipsBurger.name} and we have served ${Burger.burgers} burgers") //Finally, the SpecialBurgers is an actual object, so we //can store it in a variable when needed val burgerCompanion = Burger.SpecialBurgers val topBunBurger = Burger(burgerCompanion.TOP_BUN_BURGER) println("Created ${topBunBurger.name} and we have served ${Burger.burgers} burgers") }
This is the output when run.
Barley Davidson Burger is not in [Top Bun Burger, National Pass Thyme Burger, Kale Mary Burger, Snipwrecked Burger] Created Kale Mary Burger and we have served 1 burgers Created Snipwrecked Burger and we have served 2 burgers Created Top Bun Burger and we have served 3 burgers