fun String.shout() = this.uppercase() + "!" defines an extension function, letting any String call .shout() as though it were a real member — without modifying String's own source, which for a built-in or third-party class isn't even possible. Inside the extension, this refers to the receiver (the String being called on). Critically, extension functions are resolved STATICALLY, based on the variable's declared compile-time type, not its actual runtime type — a real, if uncommon, source of confusion when an extension function and a real member function share a name, since the member function always wins.
A basic extension function
shout() is called exactly like a real method, but String's own source was never touched — this is precisely how it's possible to "add" a method to a class you don't own at all, like a class from a third-party library.
fun String.shout(): String {
return this.uppercase() + "!" // 'this' refers to the String being called on
}
println("hello".shout()) // "HELLO!"Extension properties
Extension properties work the same way, though they can't have backing fields — they must be computed from the receiver each time, since there's no place to actually store extra state on a class you don't own.
val String.lastChar: Char
get() = this[this.length - 1]
println("hello".lastChar) // 'o'The real gotcha: static resolution, not dynamic dispatch
printType is chosen by v's DECLARED type (Base) at compile time, even though v actually holds a Derived at runtime — genuinely different from how a real overridden member function would resolve, which would use Derived's version through dynamic dispatch.
open class Base
class Derived : Base()
fun Base.printType() = println("Base")
fun Derived.printType() = println("Derived")
val v: Base = Derived() // declared type: Base, actual type: Derived
v.printType() // prints "Base" — resolved by the DECLARED type, not the actual one!