kotlinbeginner

Extension Functions and Properties

Add methods to existing classes without inheritance: extension functions, properties, and generic extensions.

kotlin
// String extensions
fun String.isEmail(): Boolean =
    matches(Regex("^[\\w.+-]+@[\\w-]+\\.[\\w.-]+$"))

fun String.truncate(maxLength: Int, suffix: String = "..."): String =
    if (length <= maxLength) this
    else take(maxLength - suffix.length) + suffix

fun String.toSlug(): String =
    lowercase()
        .replace(Regex("[^a-z0-9\\s-]"), "")
        .replace(Regex("\\s+"), "-")
        .trim('-')

// List extensions
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null

fun <T> List<T>.chunkedWithOverlap(size: Int, overlap: Int): List<List<T>> {
    require(overlap < size) { "Overlap must be less than chunk size" }
    val step = size - overlap
    return (0..this.size - size step step).map { i ->
        subList(i, (i + size).coerceAtMost(this.size))
    }
}

// Extension property
val String.wordCount: Int
    get() = trim().split(Regex("\\s+")).filter { it.isNotBlank() }.size

// Generic extension
fun <T : Comparable<T>> T.clamp(min: T, max: T): T = when {
    this < min -> min
    this > max -> max
    else -> this
}

// Nullable extensions
fun String?.orEmpty(): String = this ?: ""
fun <T> List<T>?.orEmpty(): List<T> = this ?: emptyList()

// Extension on companion (requires companion)
data class User(val name: String, val email: String) {
    companion object
}

fun User.Companion.fromCsv(csv: String): User {
    val (name, email) = csv.split(",").map { it.trim() }
    return User(name, email)
}

// Scope function patterns with extensions
fun <T> T.applyIf(condition: Boolean, block: T.() -> T): T =
    if (condition) block() else this

fun main() {
    // String extensions
    println("test@email.com".isEmail())           // true
    println("Hello World!".truncate(8))            // Hello...
    println("My Blog Post Title".toSlug())         // my-blog-post-title
    println("Hello world foo".wordCount)            // 3

    // List extensions
    println(listOf(1, 2, 3).secondOrNull())        // 2
    println(listOf(1, 2, 3, 4, 5).chunkedWithOverlap(3, 1))
    // [[1, 2, 3], [3, 4, 5]]

    // Clamp
    println(150.clamp(0, 100))  // 100
    println(3.14.clamp(0.0, 1.0)) // 1.0

    // Companion extension
    val user = User.fromCsv("Alice, alice@test.com")
    println(user)

    // Conditional apply
    val query = "SELECT *"
        .applyIf(true) { "$this WHERE active = true" }
        .applyIf(false) { "$this ORDER BY name" }
    println(query)
}

Use Cases

  • Adding utility methods to third-party types
  • Domain-specific language construction
  • Clean API design without utility classes

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.