1. Overview

In this tutorial, we’ll show how to define a companion object in an interface. Additionally, we’ll explain how a companion object affects an interface implementation. Finally, we’ll show how to implement an interface by a companion object.

2. Companion Object in Interface

Let’s have a look at how to define a companion object in an interface. Above all, there is no difference between the companion object declaration in a class and in an interface. Let’s show it with a Vehicle interface:

interface Vehicle {
    fun getNumberOfWheels(): Int

    companion object {
        const val DOUBLE_TRACK_AMOUNT_OF_WHEELS = 3
        fun isDoubleTrack(vehicle: Vehicle) = vehicle.getNumberOfWheels() > DOUBLE_TRACK_AMOUNT_OF_WHEELS
    }
}

The companion object provides a common constant. It defines the minimum amount of wheels in a double-track vehicle. Additionally, we provide a helper method isDoubleTrack(). Common constants and helper methods are common use cases for using companion objects in interfaces.

3. Implementing Interface With Companion Object

Let’s now have a look at how to implement an interface with a companion object. That is to say, the companion object does not have any influence on the interface implementation. It does not change the contract of the interface. In addition, the companion object defines an associate object to the interface with a singleton instance. Exactly the same way as it happens in a class.

Now, let’s implement the Vehicle interface:

class Car(val brand: String, val model: String, val age: Int) : Vehicle {

    companion object {
        const val ANTIQUE_CAR_MINIMAL_AGE = 30
    }
    override fun getNumberOfWheels() = 4
    fun isAntique() = age >= ANTIQUE_CAR_MINIMAL_AGE
}

The Car class provides the implementation of the Vehicle interface. Additionally, it may create its own companion object, but it does not have any effect on the companion object created in the interface.

4. Use Interface in Companion Object

After that, let’s have a look if we can implement an interface by a companion object. For that purpose, we’ll use the Vehicle interface in a named companion object:

class VehicleImplementedInCompanionObject {

    companion object Bicycle: Vehicle {
        override fun getNumberOfWheels(): Int {
            return 2
        }
    }
}

As shown above, there is no difference between an interface implementation in the class and in the companion object.

Now, let’s check if there is a difference in using Car and Bicycle objects. For that purpose, let’s check the invocation of the getNumberOfWheels method.
First, let’s check it for the Car object:

@Test
fun `given type implementing Vehicle, when use should work`(){
    val car = Car("Ford", "Mustang", 12)
    assertThat(car.getNumberOfWheels()).isEqualTo(4)
    assertThat(Vehicle.isDoubleTrack(car)).isTrue
}

Next, let’s do the same for the Bicycle:

@Test
fun `given companion object implementing Vehicle, when use should work`(){
    val bicycle = VehicleImplementedInCompanionObject.Bicycle
    assertThat(bicycle.getNumberOfWheels()).isEqualTo(2)
    assertThat(Vehicle.isDoubleTrack(bicycle)).isFalse
}

We created a Car object in the first test. In the second test, we accessed the bicycle object. After that, we checked the number of wheels for both. In short, there is no difference in accessing the getNumberOfWheels method in both objects.

Additionally, we can have many instances of the Car class. But VehicleImplementedInCompanionObject.Bicycle always refers to the same singleton instance.

5. Conclusion

In this short article, we showed how to define a companion object in an interface. After that, we saw how to implement it in a class and in the companion object. Finally, we learned that there is no difference in accessing an interface’s methods implemented in the class and in the companion object.

As always, the source code of the examples is available over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.