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() // trueInside, 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
privateorprotectedmembers. - 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.