Extension Functions
Prerequisites: Functions, Classes and objects
An extension function adds a new function to an existing type —
even one you didn't write, such as String or Int — without changing
its source code or inheriting from it.
Syntax
fun ReceiverType.functionName(params): ReturnType { ... }
Example
fun String.initials(): String =
split(" ").filter { it.isNotEmpty() }.joinToString("") { it.first().uppercase() }
fun Int.isEven() = this % 2 == 0
val String.wordCount: Int
get() = trim().split(Regex("\\s+")).count { it.isNotEmpty() }
fun main() {
println("grace brewster hopper".initials())
println(4.isEven())
println(" the quick brown fox ".wordCount)
}
Expected output
GBH
true
4
- Inside an extension,
thisis the value it was called on (the receiver), and its members can be used directly —split(...)meansthis.split(...). - Extension properties (
wordCount) work the same way, but they can't store data — they must have a getter.
Much of Kotlin's standard library is built this way: map, filter,
and trim are extension functions.
Nullable receivers
An extension can be called on a nullable value:
fun String?.orPlaceholder() = if (this.isNullOrBlank()) "—" else this
fun main() {
val missing: String? = null
println(missing.orPlaceholder())
println("Ada".orPlaceholder())
}
—
Ada
What extensions can't do
- They can't access a class's
privatemembers. - They're resolved at compile time from the variable's declared type — they don't override anything, and they don't change the class.
Common mistakes
- Adding very general names to common types (
fun String.process()). Keep extension names specific, and keep widely-used extensions in a clearly named file, so readers can find them.