Extension Functions in Kotlin allow adding new functionality to existing classes, including library classes, without inheritance or modifying their source code. This eliminates the need for traditional design patterns like the Decorator, which typically requires wrapping objects to extend behavior.
Features of Kotlin Extension Functions:
- Extension functions are called using dot-notation on instances of the extended type, making them appear as if they were native methods of the class.
- They can be added to user-defined classes and library classes (e.g.,
String,List). - Kotlin also supports extension properties (similar to extension functions).
- Extension functions can have nullable receiver types, allowing them to be called on nullable objects.
- To declare an extension, prefix the function name with the receiver type (e.g.,
fun Int.customFunction()). - Extension functions cannot access
privateorprotectedmembers of the class. - If an extension function is added to a base class, derived classes inherit it and can override it.
When to use Inheritance not Extension Functions
To access or modify the private or protected members of a class, extension functions are not the solution. Use inheritance to create a subclass that has the necessary access privileges.
How extension functions are built below the hood
- Compiled as Static Functions: When a Kotlin extension function is written, the compiler translates it into a static function in the generated code.
- Receiver as First Parameter: This static function takes the object on which the extension function is called (the "receiver") as its first parameter.
Kotlin Extension Function Examples
Example 1: Extending Primitive Type (Int)
// Extends Int with a new multiplication operation
fun Int.eightTimes(): Int = this * 8 // 'this' refers to the Int receiver
fun main() {
val number = 5
println(number.eightTimes()) // Output: 40
println(10.eightTimes()) // Output: 80
}
Note: Extension functions work on both variables and literals.
Example 2: Extending a Custom Class
class Sphere(val radius: Double) {
fun volume(): Double = (4.0 / 3) * Math.PI * radius.pow(3)
}
// Extension adds diameter calculation to Sphere class
fun Sphere.diameter(): Double = 2 * radius // Implicit 'this' access
// Another extension demonstrating receiver access
fun Sphere.surfaceArea(): Double = 4 * Math.PI * radius.pow(2)
fun main() {
val unitSphere = Sphere(1.0)
println("Volume: ${unitSphere.volume()}") // 4.188...
println("Diameter: ${unitSphere.diameter()}") // 2.0
println("Surface Area: ${unitSphere.surfaceArea()}") // 12.566...
}
Key Notes:
- The compiler decides which extension function to use based on its acceptance criteria when declared, not on what the variable actually holds at the moment the program runs.
- Extension functions don't change the class itself at all. They just provide a more convenient way to write code that looks like it's part of the class, but is actually a separate helper function.
- If a class already has a function with the same name and setup as an extension function, the class's original function is always used instead of the extension function.