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
}

Leave a Comment

Your email address will not be published. Required fields are marked *