Kotlin Extension Functions
Extension functions let you add new functions to existing classes without modifying the class itself. You can extend classes from the standard library, third-party libraries, or your own codebase. The new function appears as if it always belonged to the class.
Defining an Extension Function
// Add a function to String class
fun String.isPalindrome(): Boolean {
val clean = this.lowercase().filter { it.isLetter() }
return clean == clean.reversed()
}
fun main() {
println("racecar".isPalindrome()) // true
println("hello".isPalindrome()) // false
println("A man".isPalindrome()) // false (need full phrase)
}Anatomy of an Extension Function
fun String . isPalindrome (): Boolean {
│ │ │ │
│ │ │ └── Return type
│ │ └── Function name
│ └── Receiver type (the class being extended)
└── fun keyword
Inside the function:
this → refers to the String instance being called on
Extensions on Standard Types
// Extend Int
fun Int.isEven(): Boolean = this % 2 == 0
fun Int.squared(): Int = this * this
fun Int.times(action: () -> Unit) { repeat(this) { action() } }
// Extend Double
fun Double.roundTo(places: Int): Double {
val factor = Math.pow(10.0, places.toDouble())
return Math.round(this * factor) / factor
}
// Extend List
fun List.secondOrNull(): T? = if (size >= 2) this[1] else null
fun main() {
println(7.isEven()) // false
println(6.isEven()) // true
println(5.squared()) // 25
println(3.14159.roundTo(2)) // 3.14
val names = listOf("Alice", "Bob", "Carol")
println(names.secondOrNull()) // Bob
println(listOf("X").secondOrNull()) // null
3.times { print("Go! ") } // Go! Go! Go!
} Extension vs Member Function
class Dog(val name: String) {
fun bark() = println("$name barks!") // member function
}
fun Dog.fetch() = println("$name fetches the ball!") // extension
val dog = Dog("Rex")
dog.bark() // Rex barks! (member)
dog.fetch() // Rex fetches! (extension)
If an extension function has the same name and signature as a member function, the member function always wins.
Extension Properties
val String.wordCount: Int
get() = this.trim().split("\\s+".toRegex()).size
val Int.isPositive: Boolean
get() = this > 0
fun main() {
println("Hello World Kotlin".wordCount) // 3
println(42.isPositive) // true
println(-5.isPositive) // false
}Nullable Receiver Extension
fun String?.orEmpty(): String = this ?: ""
fun String?.printSafe() {
if (this == null) println("(null)") else println(this)
}
fun main() {
val name: String? = null
name.printSafe() // (null)
println(name.orEmpty()) // (empty line)
"Alice".printSafe() // Alice
}Practical Example: Date Formatting Utilities
fun Long.toReadableDate(): String {
val date = java.util.Date(this)
val fmt = java.text.SimpleDateFormat("dd MMM yyyy, HH:mm")
return fmt.format(date)
}
fun Long.daysAgo(): Long = (System.currentTimeMillis() - this) / 86_400_000L
fun String.capitalizeWords(): String =
split(" ").joinToString(" ") { word ->
word.replaceFirstChar { it.uppercase() }
}
fun main() {
val timestamp = System.currentTimeMillis() - 3 * 86_400_000L
println(timestamp.toReadableDate())
println("${timestamp.daysAgo()} days ago")
println("hello world kotlin".capitalizeWords()) // Hello World Kotlin
}