8 min read

Kotlin is a rapidly rising programming language. It offers developers the simplicity and effectiveness to develop robust and lightweight applications. Kotlin offers great functional programming support, and one of the best features of Kotlin in this respect are extension functions, hands down! Extension functions are great, because they let you modify existing types with new functions. This is especially useful when you’re working with Android and you want to add extra functions to the framework classes. In this article, we’ll see what Extension functions are and how the’re a blessing in disguise!

This article has been extracted from the book, Functional Kotlin, by Mario Arias and Rivu Chakraborty. The book bridges the language gap for Kotlin developers by showing you how to create and consume functional constructs in Kotlin.
fun String.sendToConsole() = println(this)
fun main(args: Array) {
"Hello world! (from an extension function)".sendToConsole()
}

To add an extension function to an existing type, you must write the function’s name next to the type’s name, joined by a dot (.).

In our example, we add an extension function (sendToConsole()) to the String type. Inside the function’s body, this refers the instance of String type (in this extension function, string is the receiver type).

Apart from the dot (.) and this, extension functions have the same syntax rules and features as a normal function. Indeed, behind the scenes, an extension function is a normal function whose first parameter is a value of the receiver type. So, our sendToConsole() extension function is equivalent to the next code:

fun sendToConsole(string: String) = println(string)
sendToConsole("Hello world! (from a normal function)")

So, in reality, we aren’t modifying a type with new functions. Extension functions are a very elegant way to write utility functions, easy to write, very fun to use, and nice to read—a win-win. This also means that extension functions have one restriction—they can’t access private members of this, in contrast with a proper member function that can access everything inside the instance:

class Human(private val name: String)
fun Human.speak(): String = "${this.name} makes a noise" //Cannot access 'name': it is private in 'Human'

Invoking an extension function is the same as a normal function—with an instance of the receiver type (that will be referenced as this inside the extension), invoke the function by name.

Extension functions and inheritance

There is a big difference between member functions and extension functions when we talk about inheritance.

The open class Canine has a subclass, Dog. A standalone function, printSpeak, receives a parameter of type Canine and prints the content of the result of the function speak(): String:

open class Canine {
   open fun speak() = ""
}
class Dog : Canine() {
override fun speak() = "woof!!"
}

fun printSpeak(canine: Canine) {
println(canine.speak())
}

Open classes with open methods (member functions) can be extended and alter their behavior. Invoking the speak function will act differently depending on which type is your instance.

The printSpeak function can be invoked with any instance of a class that is-a Canine, either Canine itself or any subclass:

printSpeak(Canine())
printSpeak(Dog())

If we execute this code, we can see this on the console:

console

Although both are Canine, the behavior of speak is different in both cases, as the subclass overrides the parent implementation.

But with extension functions, many things are different.

As with the previous example, Feline is an open class extended by the Cat class. But speak is now an extension function:

open class Feline
fun Feline.speak() = ""

class Cat : Feline()

fun Cat.speak() = "meow!!"

fun printSpeak(feline: Feline) {
println(feline.speak())
}

Extension functions don’t need to be marked as override, because we aren’t overriding anything:

printSpeak(Feline())
printSpeak(Cat()

If we execute this code, we can see this on the console:

console

In this case, both invocations produce the same result. Although in the beginning, it seems confusing, once you analyze what is happening, it becomes clear. We’re invoking the Feline.speak() function twice; this is because each parameter that we pass is a Feline to the printSpeak(Feline) function:

open class Primate(val name: String)
fun Primate.speak() = "$name: "

open class GiantApe(name: String) : Primate(name)

fun GiantApe.speak() = "${this.name} :"

fun printSpeak(primate: Primate) {
println(primate.speak())
}

printSpeak(Primate("Koko"))
printSpeak(GiantApe("Kong"))

If we execute this code, we can see this on the console:

consoleIn this case, it is still the same behavior as with the previous examples, but using the right value for name. Speaking of which, we can reference name with name and this.name; both are valid.

Extension functions as members

Extension functions can be declared as members of a class. An instance of a class with extension functions declared is called the dispatch receiver.

The Caregiver open class internally defines, extension functions for two different classes, Feline and Primate:

open class Caregiver(val name: String) {
   open fun Feline.react() = "PURRR!!!"
   
   fun Primate.react() = "*$name plays with ${this@Caregiver.name}*"

   fun takeCare(feline: Feline) {
      println("Feline reacts: ${feline.react()}")
   }
fun takeCare(primate: Primate){
println("Primate reacts: ${primate.react()}")
}
}

Both extension functions are meant to be used inside an instance of Caregiver. Indeed, it is a good practice to mark member extension functions as private, if they aren’t open.

In the case of Primate.react(), we are using the name value from Primate and the name value from Caregiver. To access members with a name conflict, the extension receiver (this) takes precedence and to access members of the dispatcher receiver, the qualified this syntax must be used. Other members of the dispatcher receiver that don’t have a name conflict can be used without qualified this.

Don’t get confused by the various means of this that we have already covered:

  • Inside a class, this means the instance of that class
  • Inside an extension function, this means the instance of the receiver type like the first parameter in our utility function with a nice syntax:
class Dispatcher {
   val dispatcher: Dispatcher = this

   fun Int.extension(){
      val receiver: Int = this
      val dispatcher: Dispatcher = this@Dispatcher
   }
}

Going back to our Zoo example, we instantiate a Caregiver, a Cat, and a Primate, and we invoke the function Caregiver.takeCare with both animal instances:

val adam = Caregiver("Adam")
val fulgencio = Cat()

val koko = Primate("Koko")

adam.takeCare(fulgencio)
adam.takeCare(koko)

If we execute this code, we can see this on the console:

console

Any zoo needs a veterinary surgeon. The class Vet extends Caregiver:

open class Vet(name: String): Caregiver(name) {
   override fun Feline.react() = "*runs away from $name*"
}

We override the Feline.react() function with a different implementation. We are also using the Vet class’s name directly, as the Feline class doesn’t have a property name:

val brenda = Vet("Brenda")
listOf(adam, brenda).forEach { caregiver ->
println("${caregiver.javaClass.simpleName} ${caregiver.name}")
caregiver.takeCare(fulgencio)
caregiver.takeCare(koko)
}

After which, we get the following output:

extension functions in kotlin

Extension functions with conflicting names

What happens when an extension function has the same name as a member function?

The Worker class has a function work(): String and a private function rest(): String. We also have two extension functions with the same signature, work and rest:

class Worker {
   fun work() = "*working hard*"

   private fun rest() = "*resting*"
}
fun Worker.work() = "*not working so hard*"

fun  Worker.work(t:T) = "*working on $t*"

fun Worker.rest() = “*playing video games*”

Having extension functions with the same signature isn’t a compilation error, but a warning: Extension is shadowed by a member: public final fun work(): String

It is legal to declare a function with the same signature as a member function, but the member function always takes precedence, therefore, the extension function is never invoked. This behavior changes when the member function is private, in this case, the extension function takes precedence.

It is also possible to overload an existing member function with an extension function:

val worker = Worker()
println(worker.work())

println(worker.work("refactoring"))

println(worker.rest())

On execution, work() invokes the member function and work(String) and rest() are extension functions:

Extension functions for objects

In Kotlin, objects are a type, therefore they can have functions, including extension functions (among other things, such as extending interfaces and others).

We can add a buildBridge extension function to the object, Builder:

object Builder {

}

fun Builder.buildBridge() = "A shinny new bridge"

We can include companion objects. The class Designer has two inner objects, the companion object and Desk object:

class Designer {
   companion object {

   }

   object Desk {

   }
}
fun Designer.Companion.fastPrototype() = "Prototype"

fun Designer.Desk.portofolio() = listOf("Project1", "Project2")

Calling this functions works like any normal object member function:

Designer.fastPrototype()
Designer.Desk.portofolio().forEach(::println)

So there you have it! You now know how to take advantage of extension functions in Kotlin. If you found this tutorial helpful and would like to learn more, head on over to purchase the full book, Functional Kotlin, by Mario Arias and Rivu Chakraborty.

Read Next:

Forget C and Java. Learn Kotlin: the next universal programming language

5 reasons to choose Kotlin over Java

Building chat application with Kotlin using Node.js, the powerful Server-side JavaScript platform


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required