2-2-14 快速掌握Kotlin-定义扩展函数

26 阅读4分钟

Kotlin 自定义扩展函数

扩展函数是 Kotlin 的一大特色,它允许你为现有的类添加新的函数,而无需继承该类或使用装饰器模式。

1. 基本语法

最简单的扩展函数

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

// 使用
val greeting = "Hello"
println(greeting.addExclamation())  // 输出: Hello!

带参数的扩展函数

// 为 Int 添加扩展函数
fun Int.multiplyBy(factor: Int): Int {
    return this * factor
}

// 使用
val result = 5.multiplyBy(3)  // 15

2. 扩展函数的原理

扩展函数本质上是静态函数,接收一个接收者对象作为第一个参数:

// 编译后的 Java 代码类似:
// public static String addExclamation(String $this$addExclamation) {
//     return $this$addExclamation + "!";
// }

3. 为常用类添加扩展函数

String 扩展

// 检查字符串是否是电子邮件格式
fun String.isEmail(): Boolean {
    val emailRegex = Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$")
    return emailRegex.matches(this)
}

// 获取字符串的首字母缩写
fun String.getInitials(): String {
    return this.split(" ")
        .filter { it.isNotEmpty() }
        .map { it.first().uppercaseChar() }
        .joinToString("")
}

// 使用
val email = "test@example.com"
println(email.isEmail())  // true

val name = "John Doe"
println(name.getInitials())  // JD

List 扩展

// 查找列表中满足条件的第一个元素,找不到返回 null
fun <T> List<T>.findFirstOrNull(predicate: (T) -> Boolean): T? {
    return this.firstOrNull(predicate)
}

// 对列表进行去重,但保留顺序
fun <T> List<T>.distinctByOrder(): List<T> {
    val seen = mutableSetOf<T>()
    return this.filter { seen.add(it) }
}

// 使用
val numbers = listOf(1, 2, 3, 2, 1, 4)
val unique = numbers.distinctByOrder()  // [1, 2, 3, 4]

4. 扩展属性

除了扩展函数,还可以定义扩展属性:

// 为 String 添加扩展属性
val String.wordCount: Int
    get() = this.split("\\s+".toRegex()).size

// 为 List 添加扩展属性
val <T> List<T>.midElement: T?
    get() = if (isEmpty()) null else this[size / 2]

// 使用
val text = "Kotlin is awesome"
println(text.wordCount)  // 3

val list = listOf(1, 2, 3, 4, 5)
println(list.midElement)  // 3

5. 可空接收者的扩展

可以为可空类型定义扩展函数:

// 安全地执行操作
fun String?.safeToUpperCase(): String {
    return this?.uppercase() ?: "DEFAULT"
}

// 为空时提供默认值
fun <T> Collection<T>?.orEmpty(): Collection<T> {
    return this ?: emptyList()
}

// 使用
val nullableString: String? = null
println(nullableString.safeToUpperCase())  // DEFAULT

6. 作用域与导入

在不同文件中组织扩展函数

// StringExtensions.kt
package com.example.extensions

fun String.removeWhitespace(): String {
    return this.replace("\\s".toRegex(), "")
}

fun String.containsOnlyDigits(): Boolean {
    return this.matches(Regex("\\d+"))
}

// 使用前需要导入
import com.example.extensions.removeWhitespace
import com.example.extensions.containsOnlyDigits

使用扩展函数的最佳实践

// 创建一个扩展文件,专门存放某个类的扩展
// ViewExtensions.kt
import android.view.View

fun View.show() {
    this.visibility = View.VISIBLE
}

fun View.hide() {
    this.visibility = View.GONE
}

fun View.toggleVisibility() {
    this.visibility = if (this.visibility == View.VISIBLE) {
        View.GONE
    } else {
        View.VISIBLE
    }
}

7. 泛型扩展函数

创建适用于多种类型的扩展:

// 为所有集合类型添加扩展
fun <T> Collection<T>.printElements(separator: String = ", ") {
    println(this.joinToString(separator))
}

// 为所有可比较类型添加扩展
fun <T : Comparable<T>> T.isBetween(lower: T, upper: T): Boolean {
    return this >= lower && this <= upper
}

// 使用
val list = listOf(1, 2, 3)
list.printElements()  // 输出: 1, 2, 3

val number = 5
println(number.isBetween(1, 10))  // true

8. 扩展函数与成员函数的优先级

当扩展函数与成员函数签名相同时,成员函数优先

class Example {
    fun print() {
        println("成员函数")
    }
}

fun Example.print() {
    println("扩展函数")
}

fun main() {
    Example().print()  // 输出: 成员函数
}

9. 实际项目中的应用

Android 开发示例

// SharedPreferences 扩展
fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor.() -> Unit
) {
    val editor = edit()
    action(editor)
    if (commit) {
        editor.commit()
    } else {
        editor.apply()
    }
}

// 使用
sharedPreferences.edit {
    putString("username", "john")
    putInt("age", 25)
}

网络请求处理

// Retrofit Response 扩展
fun <T> Response<T>.getOrThrow(): T {
    if (isSuccessful) {
        return body() ?: throw NullPointerException("Response body is null")
    }
    throw HttpException(this)
}

fun <T> Response<T>.getOrNull(): T? {
    return if (isSuccessful) body() else null
}

// 使用
try {
    val user = apiService.getUser().getOrThrow()
} catch (e: HttpException) {
    // 处理错误
}

日期时间处理

// LocalDate 扩展
fun LocalDate.format(pattern: String = "yyyy-MM-dd"): String {
    return this.format(DateTimeFormatter.ofPattern(pattern))
}

fun LocalDate.isToday(): Boolean {
    return this == LocalDate.now()
}

fun LocalDate.isWeekend(): Boolean {
    return this.dayOfWeek == DayOfWeek.SATURDAY || 
           this.dayOfWeek == DayOfWeek.SUNDAY
}

// 使用
val today = LocalDate.now()
println(today.format())  // 2024-01-15
println(today.isWeekend())  // 根据具体日期判断

10. 运算符重载扩展

通过扩展函数实现运算符重载:

// 为 Pair 添加加号运算符
operator fun Pair<Int, Int>.plus(other: Pair<Int, Int>): Pair<Int, Int> {
    return Pair(first + other.first, second + other.second)
}

// 使用
val point1 = Pair(1, 2)
val point2 = Pair(3, 4)
val result = point1 + point2  // Pair(4, 6)

11. DSL(领域特定语言)构建

使用扩展函数构建 DSL:

class Html {
    private val children = mutableListOf<String>()
    
    fun body(init: Body.() -> Unit) {
        val body = Body().apply(init)
        children.add(body.toString())
    }
    
    override fun toString(): String {
        return "<html>${children.joinToString("")}</html>"
    }
}

class Body {
    private val children = mutableListOf<String>()
    
    fun p(text: String) {
        children.add("<p>$text</p>")
    }
    
    fun h1(text: String) {
        children.add("<h1>$text</h1>")
    }
    
    override fun toString(): String {
        return "<body>${children.joinToString("")}</body>"
    }
}

// 创建 HTML DSL
fun html(init: Html.() -> Unit): Html {
    return Html().apply(init)
}

// 使用
val myHtml = html {
    body {
        h1("Welcome")
        p("Hello, World!")
    }
}
println(myHtml)

12. 扩展函数的限制

// 1. 不能访问私有成员
class PrivateClass {
    private val secret = "hidden"
}

// 错误:无法访问私有成员
// fun PrivateClass.revealSecret() = this.secret

// 2. 扩展函数是静态解析的
open class Animal
class Dog : Animal()

fun Animal.speak() = "Animal sound"
fun Dog.speak() = "Woof!"

fun main() {
    val animal: Animal = Dog()
    println(animal.speak())  // 输出: Animal sound(静态解析,不是动态绑定)
}

13. 最佳实践

  1. 保持扩展函数小而专注
  2. 为扩展函数提供清晰的名称
  3. 将相关扩展放在同一个文件中
  4. 避免与现有成员函数冲突
  5. 考虑使用扩展函数代替工具类
// 不好的做法:工具类
object StringUtils {
    fun capitalize(str: String): String {
        return str.replaceFirstChar { it.uppercase() }
    }
}

// 好的做法:扩展函数
fun String.capitalizeWords(): String {
    return this.split(" ").joinToString(" ") { word ->
        word.replaceFirstChar { it.uppercase() }
    }
}

// 使用更自然
val name = "john doe"
println(name.capitalizeWords())  // John Doe

总结

Kotlin 扩展函数是一种强大的特性,它允许你:

  • 为现有类添加新功能而不修改源代码
  • 创建更自然、流畅的 API
  • 减少工具类的使用
  • 构建领域特定语言(DSL)

合理使用扩展函数可以使代码更加简洁、易读,但也要注意不要过度使用,避免造成代码混乱。