Extension functions

Kotlin · Practical · 10 min

Extensions are how Kotlin's standard library and Android KTX make Java APIs pleasant, and how you write readable helpers.

An extension function adds a function to an existing type, even one you don't own, such as String or a Java class:

fun String.isPalindrome(): Boolean = this == reversed()
"level".isPalindrome()   // true

Inside, this refers to the receiver object (and can be omitted). Extension properties work similarly but can't have backing fields: val String.wordCount get() = split(" ").size.

How they work: an extension compiles to a static function taking the receiver as its first parameter. Consequences:

  • Extensions can't access private or protected members.
  • They're resolved statically, by the declared type of the expression, not the runtime type. They don't participate in polymorphism.
  • A member function with the same signature always wins over an extension.
  • They must be imported where used if declared in another package.

Extensions on nullable receivers can be called on null: fun String?.orDash() = this ?: "-". That's how the standard library's isNullOrEmpty() works.

Generic extensions apply to many types: fun <T> List<T>.second() = this[1].

Good uses: small helpers that make call sites read naturally, adapters for Java APIs, and grouping utility code instead of StringUtils classes. Avoid putting core domain behavior in scattered extensions where readers won't find it.

Example

fun String.isPalindrome(): Boolean = this == reversed()

val String.wordCount: Int
    get() = split(" ").count { it.isNotBlank() }

fun String?.orDash(): String = if (isNullOrBlank()) "-" else this

fun <T> List<T>.secondOrNull(): T? = getOrNull(1)

open class Animal
class Dog : Animal()
fun Animal.kind() = "animal"
fun Dog.kind() = "dog"

fun main() {
    println("level".isPalindrome())         // true
    println("hello  big world".wordCount)   // 3
    val missing: String? = null
    println(missing.orDash())               // -
    println(listOf(1, 2, 3).secondOrNull()) // 2

    val pet: Animal = Dog()
    println(pet.kind())                     // animal (static resolution!)
}

Remember

  • fun Type.name() adds functions callable with dot syntax
  • this is the receiver; private members are not accessible
  • Extensions resolve by static type, not runtime type
  • Nullable receivers let you call extensions on null

Watch out for

  • Expecting an extension on a subclass to override one on the base class.

Check yourself

  1. val a: Animal = Dog(); a.kind() with extensions on both Animal and Dog calls which?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: kotlinlang.org/docs/extensions.html