404 Not Found

404 Not Found


nginx

Kotlin Extension Functions Explained

Extension functions let Charlie add isHighValue() to Order without modifying its source code — it's not magic, just compile-time static dispatch syntactic sugar, but its practical value is undeniable.

1. What You'll Learn


2. A Real Architect's Story

(1) Pain Point: Util Class Proliferation

Charlie's Java project has 15 Util classes: OrderUtil, StringUtil, DateUtil... Each is a pile of static methods, making calls like OrderUtil.isHighValue(order) verbose and unintuitive.

(2) The Extension Function Solution

KOTLIN
// Instead of OrderUtil.isHighValue(order)
fun Order.isHighValue() = total > 10_000

// Now call it like a member method!
if (order.isHighValue()) {
    routeToVipPipeline()
}

Extension functions transform API calls from Util.method(obj) to obj.method(), a quantum leap in code readability.


3. Extension Function Basics

(1) Basic Syntax

KOTLIN
// Extend String with order ID formatting
fun String.toOrderId() = "ORD-$this"

// Extend Double with USD formatting
fun Double.toUSD() = "\$$this USD"

// Extend List with business logic
fun List<Order>.totalRevenue() = this.sumOf { it.total }

// Usage
println("001".toOrderId())          // ORD-001
println(299.99.toUSD())             // $299.99 USD
println(orders.totalRevenue())      // 12345.67

(2) Extension Functions with Generics

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

// Extension with type constraint
fun <T : Comparable<T>> List<T>.secondLargest(): T? {
    return this.sortedDescending().secondOrNull()
}

(3) Nullable Receiver Extensions

KOTLIN
// Extend nullable type - handle null gracefully
fun String?.orDefault(default: String = "N/A"): String = this ?: default

val name: String? = null
println(name.orDefault("Unknown"))  // Unknown
println("Alice".orDefault())        // Alice

4. Extension Properties

KOTLIN
// Read-only extension property
val BigDecimal.inMillions: Double
    get() = this.toDouble() / 1_000_000

val String.isOrderId: Boolean
    get() = startsWith("ORD-")

// Usage
val revenue = BigDecimal("2_500_000")
println("${revenue.inMillions}M")  // 2.5M

println("ORD-001".isOrderId)  // true
println("ABC-001".isOrderId)  // false

// Note: extension properties CANNOT have backing fields
// var extension properties are possible but require explicit setter

(1) Extension Properties vs Member Properties

Dimension Member Property Extension Property
Definition location Inside the class Outside the class
Backing field Has one None
State storage Stored in object Cannot store
Mutability Can be var Computed var only
Access level Subject to visibility Only accesses public API

5. Static Dispatch — The Core of Extensions

Extension functions are statically dispatched: which implementation is called is determined by the declared type, not the runtime type.

(1) Static Dispatch Example

KOTLIN
open class Order(val id: String, val total: Double)
class BulkOrder(id: String, total: Double, val minQty: Int) : Order(id, total)

// Extension on Order
fun Order.summary() = "Order $id: \$$total USD"

// Extension on BulkOrder
fun BulkOrder.summary() = "Bulk $id: \$$total USD (min: $minQty)"

fun printSummary(order: Order) {
    println(order.summary())  // Always calls Order.summary()!
}

val bulk = BulkOrder("ORD-001", 5_000.0, 100)
printSummary(bulk)       // "Order ORD-001: $5000.0 USD" - NOT BulkOrder version!
bulk.summary()           // "Bulk ORD-001: $5000.0 USD (min: 100)" - direct call OK

(2) Static Dispatch Sequence Diagram

100%
sequenceDiagram
    participant Caller
    participant Order
    participant BulkOrder

    Caller->>Order: order.summary() (declared type: Order)
    Note over Order: Resolved at COMPILE TIME
    Order-->>Caller: Order.summary() result

    Caller->>BulkOrder: bulk.summary() (declared type: BulkOrder)
    Note over BulkOrder: Resolved at COMPILE TIME
    BulkOrder-->>Caller: BulkOrder.summary() result

(3) Static Dispatch vs Virtual Method Dispatch

Dimension Member Method (Virtual Dispatch) Extension Function (Static Dispatch)
Resolution time Runtime Compile time
Basis Actual type Declared type
Polymorphism Supported Not supported
Override Subclass can override Cannot override
Advantage Dynamic flexibility Safe and predictable

6. Scope Control

(1) Top-Level Extensions

KOTLIN
// File: OrderExtensions.kt
package com.order.extensions

fun Order.isHighValue() = total > 10_000
fun List<Order>.totalRevenue() = sumOf { it.total }

(2) Class-Member Extensions

KOTLIN
class OrderService {
    // Extension defined inside a class - only visible within this class
    fun Order.needsReview(): Boolean = total > 5_000 && status == "PENDING"

    fun process(order: Order) {
        if (order.needsReview()) {  // Accessible here
            routeToReview(order)
        }
    }
}

// order.needsReview()  // ERROR: not accessible outside OrderService

(3) Extension Scope Comparison

Definition Location Visibility Use Case
Top-level (file-level) Project-wide (after import) General utility extensions
Inside a class member Inside the class only Extensions tied to class state
Same file Same file only Helper extensions

7. Complete Example: OrderProcessor Extension Toolkit

KOTLIN
// ============================================
// OrderProcessor - Extension Toolkit
// Feature: Business logic as extension functions
// ============================================

import java.math.BigDecimal
import java.math.RoundingMode

data class Order(val id: String, val total: Double, val status: String, val customer: String)

// String extensions
fun String.toOrderId() = if (startsWith("ORD-")) this else "ORD-$this"
fun String.isOrderId() = matches(Regex("ORD-\\d{3,}"))

// Double extensions
fun Double.toUSD(): String = "\$${"%.2f".format(this)} USD"
fun Double.inMillions(): Double = this / 1_000_000

// BigDecimal extensions
fun BigDecimal.toUSD(): String = "\$${setScale(2, RoundingMode.HALF_UP)} USD"

// Order extensions
fun Order.isHighValue() = total > 10_000
fun Order.isPending() = status == "PENDING"
fun Order.summary() = "$id | ${total.toUSD()} | $status | $customer"

// List<Order> extensions
fun List<Order>.totalRevenue() = sumOf { it.total }
fun List<Order>.highValueOrders() = filter { it.isHighValue() }
fun List<Order>.byCustomer() = groupBy { it.customer }
fun List<Order>.revenueByCustomer() = byCustomer().mapValues { (_, orders) -> orders.totalRevenue() }

// Nullable extension
fun String?.orDefault(default: String = "UNKNOWN") = this ?: default

fun main() {
    val orders = listOf(
        Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
        Order("ORD-002", 15_000.00, "PENDING", "Bob"),
        Order("ORD-003", 2_500.00, "SHIPPED", "Charlie"),
        Order("ORD-004", 8_900.00, "CONFIRMED", "Bob"),
        Order("ORD-005", 45.50, "CANCELLED", "Alice")
    )

    // String extension
    println("001".toOrderId())       // ORD-001
    println("ORD-001".toOrderId())   // ORD-001
    println("ORD-001".isOrderId())   // true

    // Order extensions
    println("\n=== Order Summaries ===")
    orders.forEach { println(it.summary()) }

    println("\n=== High Value Orders ===")
    orders.highValueOrders().forEach { println(it.summary()) }

    // List extensions
    println("\n=== Revenue ===")
    println("Total: ${orders.totalRevenue().toUSD()}")
    println("In millions: ${orders.totalRevenue().inMillions()}M")

    println("\n=== Revenue by Customer ===")
    orders.revenueByCustomer().forEach { (customer, revenue) ->
        println("  $customer: ${revenue.toUSD()}")
    }

    // Nullable extension
    val name: String? = null
    println("\nDefault name: ${name.orDefault("Guest")}")
}

Output:

TEXT
ORD-001
ORD-001
true

=== Order Summaries ===
ORD-001 | $299.99 USD | CONFIRMED | Alice
ORD-002 | $15000.00 USD | PENDING | Bob
ORD-003 | $2500.00 USD | SHIPPED | Charlie
ORD-004 | $8900.00 USD | CONFIRMED | Bob
ORD-005 | $45.50 USD | CANCELLED | Alice

=== High Value Orders ===
ORD-002 | $15000.00 USD | PENDING | Bob

=== Revenue ===
Total: $26745.49 USD
In millions: 0.02674549M

=== Revenue by Customer ===
  Alice: $345.49 USD
  Bob: $23900.00 USD
  Charlie: $2500.00 USD

Default name: Guest

❓ FAQ

Q Can extension functions access private members?
A No. Extension functions are defined outside the class and can only access public members. This is key to extensions not breaking encapsulation.
Q What happens if an extension function has the same name as a member method?
A Member methods take priority. If the class already has a method with the same name and signature, the extension function is ignored. This is why extensions are safe — they never accidentally override existing behavior.
Q Can extension functions be inherited and overridden?
A No. Extension functions are statically dispatched and don't participate in virtual method dispatch. Subclasses cannot override parent class extension functions.
Q Why can't extension properties have backing fields?
A Because extension properties are not stored in objects — they're just computation logic. If you need to store state, you must use other mechanisms (e.g., Map associations).
Q Do top-level extensions pollute the namespace?
A They require import to use, so they don't auto-pollute. It's recommended to group related extensions in the same file and import as needed.
Q Is extension function performance the same as regular methods?
A Nearly identical. Extension functions compile to static method calls, which the JVM easily inlines and optimizes. Zero additional overhead.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Add an extension function containsPattern to String that checks if it contains a given regex pattern. Hint: use Regex.containsMatchIn
  2. Intermediate (⭐⭐): Add toOrderId() and isOrderId() extension functions to String. Hint: prepend "ORD-" / validate with regex
  3. Advanced (⭐⭐⭐): Verify extension function static dispatch: define Order and BulkOrder, each with a same-named extension function, call through an Order-typed reference and observe the result. Hint: the declared type determines the call

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏