跳到主要内容

Kotlin 扩展

Kotlin 提供了扩展(Extension)功能,允许为现有类添加新函数和属性,而无需继承或修改原始类。这是 Kotlin 最实用的特性之一,可以显著提高代码的可读性和复用性。

扩展函数

基本语法

扩展函数是在类外部定义的、为该类添加的新函数:

// 为 String 添加扩展函数
fun String.addExclamation(): String {
return this + "!"
}

// this 可以省略
fun String.addExclamationSimple(): String = "$this!"

fun main() {
val str = "Hello"
println(str.addExclamation()) // Hello!
println(str.addExclamationSimple()) // Hello!
println("World".addExclamation()) // World!
}

扩展函数原理

扩展函数实际上是静态方法的语法糖,它不会真正修改类:

// Kotlin 扩展
fun String.addExclamation(): String = this + "!"

// 编译后的 Java 等价代码
public static String addExclamation(String receiver) {
return receiver + "!";
}

// 调用方式
String str = "Hello";
String result = ExtensionsKt.addExclamation(str);

理解要点

  • 扩展函数是静态解析的,在编译时确定调用哪个函数
  • 不支持多态,不能被子类覆盖
  • 扩展函数不能访问类的私有成员

实用示例

// String 扩展
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}

fun String.toTitleCase(): String {
return this.split(" ").joinToString(" ") {
it.replaceFirstChar { c -> c.uppercase() }
}
}

// Int 扩展
fun Int.isEven(): Boolean = this % 2 == 0

fun Int.isPrime(): Boolean {
if (this < 2) return false
for (i in 2 until this) {
if (this % i == 0) return false
}
return true
}

// List 扩展
fun <T> List<T>.secondOrNull(): T? {
return if (this.size >= 2) this[1] else null
}

fun <T> List<T>.randomOrNull(): T? {
return if (this.isEmpty()) null else this.random()
}

fun main() {
println("[email protected]".isEmail()) // true
println("hello world".toTitleCase()) // Hello World

println(4.isEven()) // true
println(7.isPrime()) // true

val list = listOf("a", "b", "c")
println(list.secondOrNull()) // b
println(list.randomOrNull()) // 随机元素
}

泛型扩展函数

为所有类型添加扩展

// 为所有类型 T 添加通用扩展
fun <T> T.print(): T {
println(this)
return this
}

fun <T> T.describe(): String {
return "Value: $this, Type: ${this!!::class.simpleName}"
}

fun main() {
42.print() // 42
"Hello".print() // Hello

println(42.describe()) // Value: 42, Type: Int
println("Kotlin".describe()) // Value: Kotlin, Type: String
}

带约束的泛型扩展

// 为 Number 及其子类添加扩展
fun <T : Number> T.toDoubleValue(): Double {
return this.toDouble()
}

// 为 Comparable 类型添加扩展
fun <T : Comparable<T>> T.isBetween(min: T, max: T): Boolean {
return this >= min && this <= max}

fun main() {
println(42.toDoubleValue()) // 42.0
println(3.14.toDoubleValue()) // 3.14

println(5.isBetween(1, 10)) // true
println('c'.isBetween('a', 'z')) // true
}

可空接收者扩展

// 为可空类型添加扩展
fun String?.orEmpty(): String {
return this ?: ""
}

fun String?.isNotNullOrEmpty(): Boolean {
return !this.isNullOrEmpty()
}

fun main() {
val nullStr: String? = null
val str: String? = "Hello"

println(nullStr.orEmpty()) // (空字符串)
println(str.orEmpty()) // Hello

println(nullStr.isNotNullOrEmpty()) // false
println(str.isNotNullOrEmpty()) // true
}

扩展属性

可以为现有类添加扩展属性,但扩展属性不能有幕后字段(backing field),只能通过已有的成员计算得出:

// 只读扩展属性
val String.lastChar: Char
get() = this[this.length - 1]

val String.halfLength: Int
get() = this.length / 2

// 可变扩展属性(仅适用于可变类型)
var StringBuilder.firstChar: Char
get() = this[0]
set(value) {
this.setCharAt(0, value)
}

fun main() {
println("Kotlin".lastChar) // n
println("Hello".halfLength) // 2

val sb = StringBuilder("Hello")
println(sb.firstChar) // H
sb.firstChar = 'J'
println(sb) // Jello
}

扩展属性的局限性

// 错误:扩展属性不能有初始化器
// val String.extra: String = "extra" // 编译错误

// 正确:通过 getter 计算
val String.extra: String
get() = "extra: $this"

fun main() {
println("test".extra) // extra: test
}

扩展作用域

顶层扩展

定义在文件顶层的扩展可以在整个项目中使用:

// File: StringUtils.kt
package com.example.utils

fun String.isEmail(): Boolean = this.contains("@")

// 使用
import com.example.utils.isEmail

fun main() {
println("[email protected]".isEmail()) // true
}

类内扩展

定义在类内部的扩展,只能在该类或其实例中访问:

class Processor {
// 内部扩展函数
fun String.process(): String {
return this.uppercase()
}

fun handle(input: String) {
// 在类内部调用
println(input.process())
}
}

fun main() {
val processor = Processor()
processor.handle("hello") // HELLO

// 外部无法调用
// "hello".process() // 编译错误
}

扩展与成员函数冲突

如果扩展函数与成员函数签名相同,成员函数优先:

class Example {
fun printMe() {
println("Member function")
}
}

fun Example.printMe() {
println("Extension function")
}

fun main() {
val example = Example()
example.printMe() // Member function(成员函数优先)
}

伴生对象扩展

可以为类的伴生对象添加扩展函数:

class MyClass {
companion object {
const val TAG = "MyClass"
}
}

// 为伴生对象添加扩展函数
fun MyClass.Companion.create(): MyClass {
println("Creating instance")
return MyClass()
}

fun MyClass.Companion.printTag() {
println(TAG)
}

fun main() {
MyClass.printTag() // MyClass
val instance = MyClass.create()
}

工厂方法示例

data class User(val id: Int, val name: String, val email: String) {
companion object
}

// 工厂方法
fun User.Companion.fromJson(json: String): User {
// 解析 JSON 创建 User
return User(0, "Unknown", "[email protected]")
}

fun User.Companion.guest(): User {
return User(-1, "Guest", "")
}

fun main() {
val guest = User.guest()
println(guest) // User(id=-1, name=Guest, email=)

val user = User.fromJson("{\"name\":\"张三\"}")
println(user)
}

标准库扩展函数

Kotlin 标准库大量使用扩展函数:

fun main() {
// let - 在对象上执行代码块
val length = "Hello".let {
println("Processing: $it")
it.length
}
println(length) // 5

// also - 执行额外操作,返回原对象
val list = mutableListOf(1, 2, 3)
.also { println("初始: $it") }
.also { it.add(4) }
println(list) // [1, 2, 3, 4]

// run - 使用 this 执行代码块
val result = "Hello".run {
println("处理: $this")
uppercase()
}
println(result) // HELLO

// apply - 配置对象,返回原对象
val sb = StringBuilder().apply {
append("Hello")
append(" ")
append("World")
}
println(sb) // Hello World
}

扩展函数最佳实践

1. 命名清晰

// 好的命名:清晰表达意图
fun String.isEmail(): Boolean
fun Int.isPrime(): Boolean
fun List<Int>.sumEven(): Int

// 不好的命名:含义不明
fun String.check(): Boolean
fun Int.calc(): Boolean

2. 合理使用扩展

// 推荐:与类相关的操作
fun String.words(): List<String> = this.split(" ")
fun Int.seconds(): Duration = Duration.seconds(this)

// 不推荐:与类无关的操作
// fun String.saveToDatabase() // 不应该作为 String 的扩展

3. 避免过度使用

// 不需要扩展:可以直接使用普通函数
fun formatString(str: String): String {
return str.trim().uppercase()
}

// 扩展更适合链式调用
" hello "
.trim()
.uppercase()
.replace("HELLO", "WORLD")

4. 使用扩展函数组织代码

// 将相关扩展放在同一文件
// File: StringExtensions.kt
package com.example.extensions

fun String.isEmail(): Boolean = contains("@") && contains(".")
fun String.isPhone(): Boolean = matches(Regex("\\d{11}"))
fun String.isUrl(): Boolean = startsWith("http")

// 使用时导入
import com.example.extensions.*

实战示例

时间单位扩展

val Int.seconds: Duration get() = Duration.seconds(this)
val Int.minutes: Duration get() = Duration.minutes(this)
val Int.hours: Duration get() = Duration.hours(this)

data class Duration(val seconds: Long) {
companion object {
fun seconds(value: Number) = Duration(value.toLong())
fun minutes(value: Number) = Duration(value.toLong() * 60)
fun hours(value: Number) = Duration(value.toLong() * 3600)
}

fun toMinutes() = seconds / 60
fun toHours() = seconds / 3600
override fun toString(): String = "${seconds}s"
}

fun main() {
val duration = 5.minutes
println(duration) // 300s
println(duration.toMinutes()) // 5

val total = 2.hours + 30.minutes
println(total) // 9000s
}

operator fun Duration.plus(other: Duration): Duration {
return Duration(this.seconds + other.seconds)
}

集合扩展

// 分块
fun <T> List<T>.chunked(size: Int): List<List<T>> {
return (0..this.lastIndex step size).map {
subList(it, minOf(it + size, this.size))
}
}

// 滑动窗口
fun <T> List<T>.windowed(size: Int): List<List<T>> {
return (0..this.lastIndex - size + 1).map {
subList(it, it + size)
}
}

fun main() {
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8)

println(list.chunked(3))
// [[1, 2, 3], [4, 5, 6], [7, 8]]

println(list.windowed(3))
// [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8]]
}

验证扩展

fun String.isValidEmail(): Boolean {
val emailRegex = Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
return matches(emailRegex)
}

fun String.isValidPhone(): Boolean {
return length == 11 && all { it.isDigit() }
}

fun String.isValidPassword(): Boolean {
return length >= 8 &&
any { it.isUpperCase() } &&
any { it.isLowerCase() } &&
any { it.isDigit() }
}

fun main() {
println("[email protected]".isValidEmail()) // true
println("13812345678".isValidPhone()) // true
println("Password123".isValidPassword()) // true
}

小结

本章我们学习了 Kotlin 扩展的核心内容:

  1. 扩展函数:为现有类添加新函数
  2. 泛型扩展:为所有类型或特定约束类型添加扩展
  3. 扩展属性:为现有类添加计算属性
  4. 扩展作用域:顶层扩展和类内扩展的区别
  5. 伴生对象扩展:为伴生对象添加扩展
  6. 最佳实践:合理使用扩展,避免滥用

扩展是 Kotlin 最实用的特性之一,它让我们能够以更自然的方式组织代码,提高代码的可读性和复用性。标准库中的许多函数都是通过扩展实现的。

练习

  1. Int 添加 factorial 扩展函数,计算阶乘
  2. List<T> 添加 swap 扩展函数,交换两个位置的元素
  3. String 添加判断邮箱格式的扩展属性 isEmail
  4. 创建一个工具类文件,包含常用的 String 扩展函数
  5. 使用扩展函数实现一个简单的 DSL