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. 最佳实践
- 保持扩展函数小而专注
- 为扩展函数提供清晰的名称
- 将相关扩展放在同一个文件中
- 避免与现有成员函数冲突
- 考虑使用扩展函数代替工具类
// 不好的做法:工具类
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)
合理使用扩展函数可以使代码更加简洁、易读,但也要注意不要过度使用,避免造成代码混乱。