Kotlin: Kotlin扩展函数详解
最后更新:2026-08-26
扩展函数让 Charlie 在不修改 源码的情况下添加 方法——这不是魔法,而是编译期静态分发的语法糖,但它的实用价值是实实在在的。
1. 你将学到
- 扩展函数:
- 扩展属性:
- 静态分发:扩展不破坏封装,不参与多态
- 作用域控制:顶层 vs 类成员内扩展
- Charlie 实战: /
<Order>
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) 静态分发时序图
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 很容易内联优化。零额外开销。
📖 小节
- 扩展函数 在不修改源码的情况下添加方法
- 扩展属性 添加计算属性,不能有 backing field
- 扩展是静态分发的:由声明类型决定调用,不参与多态
- 成员方法优先于同名扩展函数——扩展永远不会破坏已有行为
- 顶层扩展需 import 使用,类内扩展只在类内可见
- 扩展函数让 API 从 进化为
📝 作业
- 基础题(难度⭐):为 添加扩展函数 ,检查是否包含 。提示:
- 进阶题(难度⭐⭐):为 添加 和 扩展函数。提示: /
- 挑战题(难度⭐⭐⭐):验证扩展函数的静态分发:定义 和 ,各定义同名扩展函数,通过 类型引用调用,观察结果。提示:声明类型决定调用