Kotlin: Kotlin扩展函数详解

最后更新:2026-08-26

扩展函数让 Charlie 在不修改 源码的情况下添加 方法——这不是魔法,而是编译期静态分发的语法糖,但它的实用价值是实实在在的。

1. 你将学到


2. 一个架构师的真实故事

(1) 痛点:Util 类泛滥

Charlie 的 Java 项目有 15 个 Util 类:、、……每个都是静态方法堆砌,调用时 冗长且不直观。

(2) 扩展函数的解法

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

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

扩展函数让 API 调用从 变为 ,代码可读性飞跃。


3. 扩展函数基础

(1) 基本语法

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) 扩展函数与泛型

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) 可空接收者扩展

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. 扩展属性

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) 扩展属性 vs 成员属性

维度 成员属性 扩展属性
定义位置 类内部 类外部
backing field
状态存储 存在对象中 无法存储
可变性 可 var 只能计算型 var
访问权限 受可见性限制 只能访问公有 API

5. 静态分发——扩展的核心本质

扩展函数是静态分发的:调用哪个实现由声明类型决定,而非运行时类型。

(1) 静态分发示例

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) 静态分发时序图

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) 静态分发 vs 虚方法分发

维度 成员方法(虚分发) 扩展函数(静态分发)
解析时机 运行时 编译期
依据 实际类型 声明类型
多态 支持 不支持
覆盖 子类可覆盖 无法覆盖
优点 动态灵活 安全可预测

6. 作用域控制

(1) 顶层扩展

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

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

(2) 类成员内扩展

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) 扩展作用域对比

定义位置 可见范围 适用场景
顶层(文件级) 全项目(import 后) 通用工具扩展
类成员内 类内部 与类状态相关的扩展
同一文件 同一文件 辅助扩展

7. 完整示例:OrderProcessor 扩展工具集

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")}")
}

输出:

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

❓ 常见问题

Q 扩展函数能访问私有成员吗?
A 不能。扩展函数在类外部定义,只能访问公有成员。这是扩展不破坏封装的关键。
Q 扩展函数和成员方法同名会怎样?
A 成员方法优先。如果类已有同名同参的方法,扩展函数会被忽略。这也是为什么扩展是安全的——不会意外覆盖已有行为。
Q 扩展函数能被继承和重写吗?
A 不能。扩展函数是静态分发的,不参与虚方法分发。子类无法 override 父类的扩展函数。
Q 扩展属性为什么不能有 backing field?
A 因为扩展属性不存储在对象中,它只是计算逻辑。如果需要存储状态,必须用其他方式(如 Map 关联)。
Q 顶层扩展会污染命名空间吗?
A 需要 import 才能使用,不会自动污染。建议将相关扩展放在同一文件中,按需 import。
Q 扩展函数的性能和普通方法一样吗?
A 几乎一样。扩展函数在编译后就是静态方法调用,JVM 很容易内联优化。零额外开销。

📖 小节


📝 作业

  1. 基础题(难度⭐):为 添加扩展函数 ,检查是否包含 。提示:
  2. 进阶题(难度⭐⭐):为 添加 和 扩展函数。提示: /
  3. 挑战题(难度⭐⭐⭐):验证扩展函数的静态分发:定义 和 ,各定义同名扩展函数,通过 类型引用调用,观察结果。提示:声明类型决定调用

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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