Extension Functions and Properties in Kotlin

dimitrilc 2 Tallied Votes 275 Views Share

Introduction

In Kotlin, we can extend classes without subclassing via extension functions and extension properties. This feature is useful whenever we want to extend 3rd party libraries or final classes.

In this tutorial, we will learn what extension functions and extension properties are, and how to create our own.

Goals

At the end of the tutorial, you would have learned:

  1. What extension functions are.
  2. What extension properties are.
  3. How to create extension functions.
  4. How to create extension properties.

Prerequisite Knowledge

  1. Basic Kotlin.

Tools Required

  1. An IDE that supports Kotlin such as IntelliJ Community Edition.

Project Setup

To follow along with this tutorial, perform the steps below:

  1. Create a new Kotlin project using Gradle as the build system.

  2. For the Project JDK, use JDK 16.

  3. After your Gradle project is loaded, copy and paste the content for the build.gradle.kts file below.

     import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
    
     plugins {
        kotlin("jvm") version "1.5.31"
     }
    
     group = "com.example"
     version = "1.0-SNAPSHOT"
    
     repositories {
        mavenCentral()
     }
    
     dependencies {
        testImplementation(kotlin("test"))
     }
    
     tasks.test {
        useJUnitPlatform()
     }
    
     tasks.withType<KotlinCompile>() {
        kotlinOptions.jvmTarget = "11"
     }
  4. Under src/main/kotlin, create a new package called com.example.

  5. Under com.example, create an Entry.kt file.

  6. Create the main() function with zero argument in this file.

Extension Functions Concept Overview

To understand how extension functions work, let us start with a scenario where extending a class is not allowed. In the Entry.kt file, below the main() function, create a class called PizzaMaker using the code below.

    class PizzaMaker {

       fun makePepperoniPizza(){
           println("Pepperoni Pizza")
       }

       fun makeHawaiianPizza(){
           println("Hawaiian Pizza")
       }
    }

The class above only contains two simple functions, which are to make Pepperoni and Hawaiian pizzas.

If you are coming from a Java world, you might have thought that this class is extensible because it is not marked as final. But Kotlin classes are final by default, and classes must be marked with the keyword open for it to be extensible. The PizzaMaker class above is implicitly final. If we try to subclass it using the code below,

    class PizzaMakerChild: PizzaMaker() {}

then the compiler will complain that,

    This type is final, so it cannot be inherited from

Extension Function

Even if the class is final, we can still use extension functions to extend it. An extension function syntax is similar to a regular function. The only extra requirement is that we have to provide a Receiver type, which goes right before the function name and a period(.).

    fun PizzaMaker.makeSomeOtherPizza() {}

In the above function, PizzaMaker is the Receiver type.

Using the syntax above, let us create two extension functions of PizzaMaker using the code below. They are top-level functions, so make sure you put them outside of the PizzaMaker class.

    fun PizzaMaker.makeVeganPizza() {
       println("Vegan Pizza")
    }

    fun PizzaMaker.makeMeatLoverPizza(){
       println("Meat Lover")
    }

Now, we can also call makeVeganPizza() and makeMeatLoverPizza() directly from any PizzaMaker reference. In main(), we call the code below.

    fun main(){
       val maker = PizzaMaker()

       maker.makePepperoniPizza()
       maker.makeHawaiianPizza()

       maker.makeVeganPizza() //extensions
       maker.makeMeatLoverPizza() //extensions
    }

And the output is:

    Pepperoni Pizza
    Hawaiian Pizza
    Vegan Pizza
    Meat Lover

Extension Properties

Besides being to add extension functions, we can also add extension properties. When we add an extension property, we do not actually add a new field into the class, so this property does not have a backing field(it cannot store its own state by itself). So for this extension property, we must define a getter and an optional setter.

Suppose we add a field to count the number of pizzas made each time a make*() function is called (the count goes up for extension functions, too).

    class PizzaMaker() {

       var count: Int = 0

       fun makePepperoniPizza(){
           println("Pepperoni Pizza")
           count++
       }

       fun makeHawaiianPizza(){
           println("Hawaiian Pizza")
           count++
       }
    }

    //class PizzaMakerChild: PizzaMaker() {} //illegal

    fun PizzaMaker.makeVeganPizza() {
       println("Vegan Pizza")
       count++
    }

    fun PizzaMaker.makeMeatLoverPizza(){
       println("Meat Lover")
       count++
    }

And then we can add an extension property to store the amount of revenue made ($8 for each pizza) using the code below.

    val PizzaMaker.revenue: Int //$8 for each pizza
       get() = count * 8

With the extension property above, we can also access (get) it directly via a PizzaMaker reference.

If we call this code in main(),

    fun main(){
       val maker = PizzaMaker()

       maker.makePepperoniPizza()
       maker.makeHawaiianPizza()

       maker.makeVeganPizza() //extensions
       maker.makeMeatLoverPizza() //extensions

       println(maker.count) //internal property
       println(maker.revenue) //extension property
    }

We now can access the revenue property directly from the PizzaMaker reference. The code output:

    Pepperoni Pizza
    Hawaiian Pizza
    Vegan Pizza
    Meat Lover
    4
    32

We made a total of 4 pizzas, so the revenue is $32.

Solution Code

    package com.example

    fun main(){
       val maker = PizzaMaker()

       maker.makePepperoniPizza()
       maker.makeHawaiianPizza()

       maker.makeVeganPizza() //extensions
       maker.makeMeatLoverPizza() //extensions

       println(maker.count) //internal property
       println(maker.revenue) //extension property
    }

    class PizzaMaker() {

       var count: Int = 0

       fun makePepperoniPizza(){
           println("Pepperoni Pizza")
           count++
       }

       fun makeHawaiianPizza(){
           println("Hawaiian Pizza")
           count++
       }
    }

    //class PizzaMakerChild: PizzaMaker() {} //illegal

    fun PizzaMaker.makeVeganPizza() {
       println("Vegan Pizza")
       count++
    }

    fun PizzaMaker.makeMeatLoverPizza(){
       println("Meat Lover")
       count++
    }

    val PizzaMaker.revenue: Int //$8 for each pizza
       get() = count * 8

Summary

We have learned how to create our own extension functions and extension properties. The fill project code can be found here https://github.com/dmitrilc/DaniwebKotlinExtensionFunctions/tree/master