How it works
The explanation, with examples you can run.
What gets compiled
fun String.slugify(): String = lowercase().replace(Regex("[^a-z0-9]+"), "-").trim('-') val String.wordCount: Int // extension property: a getter, no field get() = split(Regex("\\s+")).count { it.isNotEmpty() } fun main() { println("Hello, Kotlin World!".slugify()) // hello-kotlin-world println("one two three".wordCount) // 3}On the JVM, slugify becomes public static String slugify(String $this) in the file's ...Kt class. Inside the body, this is the receiver and can be left out. Extensions can be declared on nullable types too (fun String?.orDash() = this ?: "-"), which is how isNullOrEmpty() works on a null reference.
Extension properties are only computed getters (and setters): there is nowhere to store a field, because the class wasn't changed.
Static dispatch
open class Shapeclass Circle : Shape() fun Shape.name() = "shape"fun Circle.name() = "circle" fun describe(s: Shape) = s.name() // decided here, at compile time fun main() { println(describe(Circle())) // shape println(Circle().name()) // circle}describe is compiled against Shape, so it calls Shape.name() whatever object is passed in. A member function declared open and overridden in Circle would print circle. If you want polymorphism, use members (or when over a sealed type); if you want to add helpers to a type you don't own, use extensions.
Two related rules:
- Members win. An extension with the same name and parameters as a member is never called.
- Extensions are imported. An extension declared in another package has to be imported by name, which keeps them from leaking everywhere. Two libraries with the same extension name are resolved by which one you import.
Where extensions shine
Kotlin's standard library is mostly extensions: map, filter and joinToString are extensions on Iterable, so they work on any Java collection too. Android KTX and many server libraries do the same for platform APIs. A good extension is small, has no surprising side effects, and is about the receiver: Instant.toLocalDate(zone) yes, String.sendEmail() probably not.
Extensions with a receiver are also the building block for lambdas with receivers (buildString { append("x") }), which the next topics use. Kotlin 2.4 made context parameters stable: context(logger: Logger) fun save() asks for a value that must be available in the caller's context, a more explicit replacement for the old experimental context receivers.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.