Kotlin Training Program

DOWNLOAD APP

FEEDBACK

Companion Object

Need

Some properties of a class might be common for all of its objects. For example, consider a class Employee to save data of employees that belong to one organization only. So organization property will have only one value which won’t change. Then defining it as a data member leads to inefficient memory usage because it has to be saved in memory separately for each and every object.

 data class Employee(
    val id: Int,
    val name: String,
    val organization: String = "ABC Enterprises"
)

fun main() {
    val a = Employee(1, "A")
    val b = Employee(2, "B")
    
    println(a) // Prints "Employee(id=1, name=A, organization=ABC Enterprises)"
    println(b) // Prints "Employee(id=2, name=B, organization=ABC Enterprises)"
}
 

Here each object has its own copy of organization field although its value is same for each object. Hence inefficient memory usage due to duplicate data.

A minor problem

Notice that organization is defined as constructor argument. So, while creating an object we can define a custom value for it :

 val a = Employee(1, "A", "DEF Consultancy")
 

This is bad design. We can fix this problem, by defining organization inside the class body :

 data class Employee(
    val id: Int,
    val name: String
) {
    val organization: String = "ABC Enterprises"
}
 

Since it is defined as val, we can’t even modify it (precisely what we want!).

Basics

To remove this data duplicacy & inefficient memory usage, we can create a Companion object.

Companion object is an object which contains class members that are common across all objects.

Companion object is defined inside class body using the companion object keyword :

 class /* className */ (...) {
		companion object {
				// Common members
		}
		...
}
 

We can now define the Employee#organization field inside companion object :

 data class Employee(
    val id: Int,
    val name: String
) {
    companion object {
        val organization = "ABC Enterprises"
    }
}
 

Note :

Factory functions

To create various types of objects of a single class, we use constructor overloading. For example, consider a class - Polygon with single field - sides: List<Int>. We can overload constructors to create various types of Polygons like Quadrilateral, Rectangle, Square etc. :

 class Polygon {
    val sides: List<Int>

    // Creates a Quadrilateral
    constructor(s1: Int, s2: Int, s3: Int, s4: Int) {
        this.sides = listOf(s1, s2, s3, s4)
    }

    // Creates a Square
    constructor(side: Int): this(side, side, side, side)

    // Creates a Rectangle
    constructor(l: Int, b: Int): this(l, b, l, b)
}

fun main() {
    val quadrilateral = Polygon(4, 5, 6, 7)
    val square = Polygon(5)
    val rectangle = Polygon(3, 4)
}
 

Now how do we define a new constructor for creating a regular pentagon (equilateral & equiangular polygon of 5 sides)? We already have a similar constructor for Square with single argument - side. One solution is to combine all regular polygon constructor into one with two fields - noOfSides & side :

 // Creates a Regular Polygon
constructor(noOfSides: Int, side: Int) {
    this.sides = List(noOfSides) { side }
}
 

But this function conflicts with Rectangle constructor which has the exact same signature (also accepts two Ints) :

 constructor(l: Int, b: Int): this(l, b, l, b)
 

This leads to ambiguity (confusion) for compiler as to which constructor to invoke.

So constructor overloading has its own limtations including the poor readability of code. For example, Polygon(5) doesn’t express that it creates a Square.

We can solve such Constructor overloading problems by defining Factory functions inside Companion object.

Factory function is a function that invokes class constructor and constructs particular type of objects.

Following is how we can rewrite the above program in a much more readable way :

 class Polygon(
    val sides: List<Int> 
) {
    companion object {
        fun createQuadrilateral(s1: Int, s2: Int, s3: Int, s4: Int): Polygon {
            return Polygon(sides = listOf(s1, s2, s3, s4))
        }
        
        fun createRegularPolygon(noOfSides: Int, side: Int): Polygon {
            return Polygon(sides = List(noOfSides) { side })
        }
        
        fun createSquare(side: Int) = createRegularPolygon(4, side)
        
        fun createRectangle(l: Int, b: Int) = createQuadrilateral(l, b, l, b)
    }
}

fun main() {
    val quadrilateral = Polygon.createQuadrilateral(4, 5, 6, 7)
    val square = Polygon.createSquare(5)
    val rectangle = Polygon.createRectangle(3, 4)
    val regularPentagon = Polygon.createRegularPolygon(5, 10)
}