Skip to content
Extension Functions
step 1/7

Reading — step 1 of 7

Learn

~3 min readFunctions and Lambdas

Extension Functions

You cannot edit String. It ships inside the standard library, and the method you wish it had is not there. Kotlin's answer is to let you write that method anyway, outside the class, and call it as though it had always belonged:

fun String.shout(): String = this.toUpperCase() + "!"

fun main() {
    println("ship".shout())

    //> SHIP!
}

String. before the name is the receiver type — the type you are extending. Inside the body, this is the value the function was called on. No subclassing, no wrapper, no edit to String.

Grader note: this course runs Kotlin 1.3.70. The modern spelling "ada".uppercase() does not exist yet and fails to compile with unresolved reference: uppercase. Use toUpperCase() here.

It is a function, not a method

That distinction is the whole mental model, and it explains everything surprising about extensions. The compiler rewrites "ship".shout() into an ordinary static call with "ship" as the first argument. Nothing is added to String at run time.

Two consequences follow, and both bite people.

Consequence one: resolution is static

The extension that runs is chosen from the declared type of the variable, not the object actually in it:

open class Animal
class Dog : Animal()

fun Animal.name(): String = "animal"
fun Dog.name(): String = "dog"

fun main() {
    val a: Animal = Dog()
    println(a.name())
    val d: Dog = Dog()
    println(d.name())

    //> animal
    //> dog
}

a holds a Dog, and still prints animal. A member function would have printed dog — overriding is a run-time lookup, extensions are not. If you need polymorphism, you need a real member.

Consequence two: a member always wins

If the class already has a matching function, the member is called and your extension is dead code:

class Box { fun hello(): String = "member" }
fun Box.hello(): String = "extension"

fun main() {
    println(Box().hello())

    //> member
}

The compiler warns you — extension is shadowed by a member: public final fun hello() — which is worth reading rather than scrolling past.

Nullable receivers

The receiver type may be nullable, and then the null check moves inside the function. This is the one extension shape that genuinely cannot be written any other way:

fun String?.orUnknown(): String = if (this == null || this.isBlank()) "unknown" else this

fun main() {
    val missing: String? = null
    println(missing.orUnknown())
    println("ada".orUnknown())

    //> unknown
    //> ada
}

Note what is not there: no ?., no !!, no if (x != null) at the call site. missing.orUnknown() is a plain dot on a nullable value, and it is safe, because this being null is a case the function itself handles.

Common mistakes

  • Expecting an extension to override a member — the member wins, silently apart from a warning.
  • Expecting polymorphism — the declared type picks the extension, so a Dog in an Animal variable gets the Animal one.
  • Reaching for private state — an extension is an outside function; it sees only the public surface of the type.
  • Forgetting the receiver can be nullablefun String?.f() handles null inside, which is usually cleaner than making every caller write ?..

Your exercise

Initials asks for both shapes at once: a plain extension and a nullable one.

Write fun String.initials(): String, turning "ada lovelace" into "A.L." — the uppercased first letter of each word, each followed by a dot. Split on Regex("\\s+"), not on " ", so a run of several spaces still counts as one gap.

Write fun String?.orUnknown(): String, returning "unknown" when the receiver is null or blank, and the receiver itself otherwise.

The driver is written for you: it reads a count, then that many lines, treating a line of - as null. For each it prints the name followed by -> and that name's initials — so - prints unknown -> U., because "unknown" is what initials() then receives. Use toUpperCase(), not uppercase().

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…