Kotlin Extensions
π
2026-06-20 | π Kotlin
Kotlin Extensions
Kotlin can extend properties and methods of a class without inheriting from the class or using Decorator patterns.
Extensions are a static behavior and do not affect the code of the extended class itself.
* * *
## Extension Functions
Extension functions allow adding new methods to existing classes without modifying the original class. The definition form of an extension function is:
fun receiverType.functionName(params){ body }
* receiverType: Represents the receiver of the function, i.e., the object being extended.
* functionName: The name of the extension function.
* params: Parameters of the extension function, which can be NULL.
The following example extends the User class:
class User(var name:String)/**Extension function**/ fun User.Print(){ print("Username $name")} fun main(arg:Array){ var user = User("") user.Print()}
The output of the example execution is:
Username
The following code adds a swap function to MutableList:
// Extension function swap, swaps values at different positions fun MutableList.swap(index1: Int, index2: Int) { val tmp = this // 'this' corresponds to the list this = this this = tmp } fun main(args: Array) { val l = mutableListOf(1, 2, 3) // Swaps values at positions 0 and 2 l.swap(0, 2) // 'this' inside the 'swap()' function will refer to the value of 'l' println(l.toString())}
The output of the example execution is:
[3, 2, 1]
The `this` keyword refers to the receiver object (i.e., the object instance specified before the dot when calling the extension function).
* * *
## Extension Functions are Statically Resolved
Extension functions are statically resolved and are not virtual members of the receiver type. When calling an extension function, which specific function is called is determined by the object expression of the calling function, not by the dynamic type:
open class C class D: C() fun C.foo() = "c" // Extension function foo fun D.foo() = "d" // Extension function foo fun printFoo(c: C) { println(c.foo()) // Type is C} fun main(arg:Array){ printFoo(D())}
The output of the example execution is:
c
If an extension function and a member function have the same name, the member function will be used first when the function is called.
class C { fun foo() { println("Member function") }} fun C.foo() { println("Extension function") } fun main(arg:Array){ var c = C() c.foo()}
The output of the example execution is:
Member function
### Extending a Nullable Object
Inside an extension function, you can use `this` to check if the receiver is NULL. This way, even if the receiver is NULL, you can call the extension function. For example:
fun Any?.toString(): String { if (this == null) return "null" // After the null check, "this" is automatically cast to a non-null type, so the following toString() // resolves to the member function of the Any class return toString()} fun main(arg:Array){ var t = null println(t.toString())}
The output of the example execution is:
null
* * *
## Extension Properties
In addition to functions, Kotlin also supports extending properties:
val List.lastIndex: Int get() = size - 1
Extension properties can be defined in a class or a Kotlin file, but not inside a function. Initialization of properties is not allowed because properties do not have a backing field. They can only be defined by explicitly provided getter/setter.
val Foo.bar = 1 // Error: Extension property cannot have initializer
Extension properties can only be declared as `val`.
* * *
## Extensions on Companion Objects
If a class has a companion object, you can also define extension functions and properties for the companion object.
Companion objects are called via "ClassName.", and extension functions declared for the companion object are called using the class name qualifier:
class MyClass { companion object { } // Will be referred to as "Companion"} fun MyClass.Companion.foo() { println("Extension function on companion object")} val MyClass.Companion.no: Int get() = 10 fun main(args: Array) { println("no:${MyClass.no}") MyClass.foo()}
The output of the example execution is:
no:10Extension function on companion object
* * *
## Scope of Extensions
Typically, extension functions or properties are defined at the top-level package:
package foo.bar fun Baz.goo() { β¦β¦ }
To use an extension defined outside the current package, import the extension function name:
package com.example.usage import foo.bar.goo // Import all extensions named goo // or import foo.bar.* // Import everything from foo.bar fun usage(baz: Baz) { baz.goo()}
* * *
## Declaring Extensions as Members
Inside a class, you can declare an extension for another class.
In this extension, there are multiple implicit receivers. The instance of the class where the extension method is defined is called the dispatch receiver, and the instance of the target type of the extension method is called the extension receiver.
class D { fun bar() { println("D bar") }}class C { fun baz() { println("C baz") } fun D.foo() { bar() // Calls D.bar baz() // Calls C.baz } fun caller(d: D) { d.foo() // Calls the extension function }} fun main(args: Array) { val c: C = C() val d: D = D() c.caller(d)}
The output of the example execution is:
D bar C baz
Inside class C, an extension for class D is created. Here, C is called the dispatch receiver, and D is the extension receiver. From the example above, it is clear that within the extension function, you can call member functions of the dispatch receiver.
If a function exists in both the dispatch receiver and the extension receiver, the extension receiver takes precedence. To reference a member of the dispatch receiver, you can use the qualified `this` syntax.
class D { fun bar() { println("D bar") }}class C { fun bar() { println("C bar") } // Same name as bar in class D fun D.foo() { bar() // Calls D.bar(), extension receiver takes precedence this@C.bar() // Calls C.bar() } fun caller(d: D) { d.foo() // Calls the extension function }} fun main(args: Array) { val c: C = C() val d: D = D() c.caller(d)}
The output of the example execution is:
D bar C bar
Extension functions defined as members can be declared as `open` and overridden in subclasses. This means that in the dispatch process of such extension functions, they are virtual for the dispatch receiver but still static for the extension receiver.
open class D {}class D1 : D() {} open class C { open fun D.foo() { println("D.foo in C") } open fun D1.foo() { println("D1.foo in C") } fun caller(d: D) { d.foo() // Calls the extension function }}class C1 : C() { override fun D.foo() { println("D.foo in C1") } override fun D1.foo() { println("D1.foo in C1") }} fun main(args: Array) { C().caller(D()) // Outputs "D.foo in C" C1().caller(D()) // Outputs "D.foo in C1" β Virtual dispatch for the dispatch receiver C().caller(D1()) // Outputs "D.foo in C" β Static dispatch for the extension receiver}
The output of the example execution is:
D.foo in C D.foo in C1 D.foo in C