Kotlin 扩展函数全面解析
扩展函数(Extension Functions)是 Kotlin 最具特色且实用的特性之一,它允许你在不继承、不修改原有类源码、不创建装饰器的前提下,为任意类(包括系统类、第三方库类)添加新的方法。这一特性大幅提升了代码的可读性和复用性,避免了 Java 中工具类(如 StringUtils、CollectionUtils)的冗余写法。
一、核心概念
1. 什么是扩展函数?
扩展函数可以在不修改原有类的情况下,为现有类添加新的功能。这对于你无法修改源码的类(如 String、List 或第三方库中的类)尤其有用。
基本语法:
fun 接收者类型.函数名(参数列表): 返回值类型 {
// 函数体,使用 this 访问接收者对象
}
- 接收者类型:要扩展的类(如 String、List<Int>);
- this:在扩展函数内部,this 指向调用该函数的接收者实例;
- 作用域:默认在声明的文件内可见,可通过 public/private 控制访问。
2. 本质与核心价值
- 本质:扩展函数是一种静态解析的语法糖。编译器会将扩展函数转换为静态方法,并不会真正修改目标类的字节码(目标类无感知),因此它没有运行时开销。
- 核心价值:
- 避免工具类泛滥(如 Java 的 StringUtil.isEmpty() → Kotlin 的 String.isEmptyExt());
- 让代码调用更符合「面向对象」的链式风格;
- 可扩展系统类/第三方库类(如 String、List、Context 等)。
二、扩展函数基础用法
1. 为普通类添加扩展(以 String 为例)
// 扩展 String:判断是否为空白(包含空格/制表符/换行符)
fun String.isBlankExt(): Boolean {
return this.trim().isEmpty() // this 指代调用的 String 实例
}
// 扩展 String:统计字符串中的中文字符数量
fun String.countChineseChars(): Int {
var count = 0
for (char in this) {
// 中文 Unicode 范围:\\u4e00-\\u9fa5
if (char.toString().matches(Regex("[\\\\u4e00-\\\\u9fa5]"))) {
count++
}
}
return count
}
// 扩展 String:重复多次
fun String.repeat(times: Int): String {
val result = StringBuilder()
for (i in 0 until times) {
result.append(this)
}
return result.toString()
}
fun main() {
println(" ".isBlankExt()) // true
println("Kotlin 编程".countChineseChars()) // 2
println("ha".repeat(3)) // hahaha
}
2. 为集合类添加扩展(以 List 为例)
// 扩展 List<Int>:计算平均值(处理空列表)
fun List<Int>.average(): Double {
if (this.isEmpty()) return 0.0
return this.sum().toDouble() / this.size
}
// 泛型扩展 List<T>:过滤并转换元素
fun <T, R> List<T>.filterAndMap(
filter: (T) -> Boolean, // 过滤条件
transform: (T) -> R // 转换逻辑
): List<R> {
val result = mutableListOf<R>()
for (item in this) {
if (filter(item)) {
result.add(transform(item))
}
}
return result
}
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.average()) // 3.0
// 泛型扩展:过滤偶数并转换为字符串
val evenStrList = numbers.filterAndMap(
filter = { it % 2 == 0 },
transform = { it.toString() }
)
println(evenStrList) // [2, 4]
}
3. 为自定义类添加扩展
data class User(val id: Long, val name: String, val age: Int)
// 扩展 User:判断是否为成年用户
fun User.isAdult(): Boolean = age >= 18
// 扩展 User:生成信息摘要
fun User.getSummary(): String = "ID: $id, 姓名: $name, 成年: ${isAdult()}"
fun main() {
val user1 = User(1, "张三", 20)
val user2 = User(2, "李四", 17)
println(user1.isAdult()) // true
println(user2.getSummary()) // ID: 2, 姓名: 李四, 成年: false
}
4. 泛型扩展函数
扩展函数也可以使用泛型,使其适用于多种类型。
fun <T> List<T>.middleOrNull(): T? {
if (isEmpty()) return null
return this[size / 2]
}
fun <T> List<T>.rotate(shift: Int): List<T> {
val size = this.size
if (size == 0) return this
val normalizedShift = (shift % size + size) % size
return this.drop(normalizedShift) + this.take(normalizedShift)
}
fun main() {
val tasks = listOf("编码", "测试", "部署")
println(tasks.rotate(1)) // [测试, 部署, 编码]
}
三、扩展函数进阶特性
1. 可空接收者
扩展函数的接收者类型可声明为可空(Type?),从而在函数内部处理 null 的情况,避免调用前判空。
// 扩展可空的 String:安全获取长度(null 时返回 0)
fun String?.safeLength(): Int {
// this 可能为 null,需先判断
return this?.length ?: 0
}
// 扩展可空的 String:安全转换为 Int(避免抛异常)
fun String?.toIntSafe(default: Int = 0): Int {
if (this == null) return default
return try {
this.toInt()
} catch (e: NumberFormatException) {
default
}
}
fun main() {
val str1: String? = "Kotlin"
val str2: String? = null
println(str1.safeLength()) // 6
println(str2.safeLength()) // 0
println("123".toIntSafe()) // 123
println(null.toIntSafe(-1)) // -1
}
2. 扩展属性
除了扩展函数,Kotlin 还支持扩展属性。注意:扩展属性没有幕后字段(backing field),因此不能有初始化器,必须自定义 getter/setter。
// 只读扩展属性:判断字符串是否为邮箱
val String.isEmail: Boolean
get() = this.matches(Regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$"))
// 读写扩展属性:为 MutableList 提供“最后一个元素”的读写操作
var MutableList<Int>.lastElement: Int
get() = if (this.isEmpty()) 0 else this[this.size – 1]
set(value) {
if (this.isEmpty()) {
this.add(value)
} else {
this[this.size – 1] = value
}
}
fun main() {
println("user@example.com".isEmail) // true
println("user.example.com".isEmail) // false
val list = mutableListOf(1, 2, 3)
println(list.lastElement) // 3
list.lastElement = 10
println(list) // [1, 2, 10]
}
3. 伴生对象扩展
可以为类的伴生对象定义扩展函数,调用时就像静态方法一样。
class User(val username: String) {
companion object { }
}
fun User.Companion.fromEmail(email: String): User {
val username = email.substringBefore("@")
return User(username)
}
fun main() {
val user = User.fromEmail("alice@example.com")
println(user.username) // alice
}
4. 作为成员声明的扩展
在一个类内部,可以为另一个类声明扩展函数。此时扩展函数内部可以访问外部类的成员(通过限定 this)。
class Host(val hostname: String)
class Connection(val host: Host, val port: Int) {
// 在 Connection 内部为 Host 声明扩展函数
fun Host.getConnectionString(): String {
return "${this.hostname}:${this@Connection.port}"
}
fun connect() {
val connectionString = host.getConnectionString()
println("Connecting to $connectionString")
}
}
fun main() {
val host = Host("localhost")
val connection = Connection(host, 8080)
connection.connect() // Connecting to localhost:8080
}
5. 扩展函数与高阶函数结合
扩展函数可以接收函数类型参数,实现更灵活的逻辑封装。
// 模拟 Android 的 View 类
class View {
fun setOnClickListener(listener: () -> Unit) {
println("设置点击监听")
listener()
}
}
// 扩展 View:简化点击事件
fun View.onClick(block: () -> Unit) {
this.setOnClickListener(block)
}
fun main() {
val button = View()
button.onClick {
println("按钮被点击")
}
}
6. 链式调用
扩展函数可以链式调用,实现流畅的 API。
fun String.mask(): String =
if (length <= 2) this
else replaceRange(1, length – 1, "*".repeat(length – 2))
fun String.shorten(max: Int): String =
if (length <= max) this else substring(0, max) + "…"
fun main() {
val email = "alice@example.com"
val result = email.mask().shorten(10)
println(result) // a********…
}
四、扩展函数的特性与注意事项
1. 静态解析(Static Resolution)
扩展函数是静态解析的,这意味着调用哪个扩展函数是由编译时的声明类型决定的,而不是运行时的实际类型。这是扩展函数与成员函数最重要的区别。
open class Shape
class Circle : Shape()
fun Shape.getName() = "Shape"
fun Circle.getName() = "Circle"
fun printName(shape: Shape) {
println(shape.getName()) // 总是调用 Shape 的扩展
}
fun main() {
val shape: Shape = Circle()
println(shape.getName()) // 输出: Shape(声明类型是 Shape)
printName(Circle()) // 输出: Shape
}
2. 成员函数优先
如果类中已经有了同名的成员函数,并且参数签名匹配,那么成员函数优先被调用。
class Example {
fun printInfo() = println("成员函数")
}
fun Example.printInfo() = println("扩展函数") // 永远不会被调用
fun main() {
Example().printInfo() // 输出: 成员函数
}
但扩展函数可以重载成员函数(即不同的参数签名):
class Example {
fun printInfo() = println("无参数成员函数")
}
fun Example.printInfo(times: Int) = println("带参数的扩展函数")
fun main() {
val ex = Example()
ex.printInfo() // 无参数成员函数
ex.printInfo(3) // 带参数的扩展函数
}
3. 访问权限
扩展函数遵循与其声明位置相同的可见性规则:
- 定义在顶层的扩展函数:默认 public,可以在任何地方导入使用;
- 使用 private 修饰:只在当前文件内可见;
- 使用 internal 修饰:只在当前模块内可见。
// File: StringExtensions.kt
package com.example.utils
public fun String.reverse(): String = this.reversed()
private fun String.isAllUpperCase(): Boolean = this.all { it.isUpperCase() }
internal fun String.removeWhitespace(): String = this.replace("\\\\s".toRegex(), "")
4. 导入与重命名
扩展函数需要导入才能在其他包中使用,也可以使用 as 关键字重命名。
// 导入单个扩展函数
import com.example.utils.reverse
// 导入所有扩展函数
import com.example.utils.*
// 重命名扩展函数避免冲突
import com.example.utils.reverse as customReverse
fun main() {
val text = "Kotlin"
println(text.customReverse())
}
5. 不能访问私有成员
扩展函数不能访问接收者类型的 private 或 protected 成员。这是设计使然,以保持类的封装性。
class MyClass {
private val secret = "私有数据"
fun publicInfo() = "公开数据"
}
fun MyClass.extension() {
// println(secret) // 编译错误:无法访问私有成员
println(publicInfo()) // 可以访问公开成员
}
五、实际应用场景
1. 简化 Android 开发
// 扩展 Context:简化 Toast 调用
fun Context.toast(message: String, duration: Int = android.widget.Toast.LENGTH_SHORT) {
android.widget.Toast.makeText(this, message, duration).show()
}
// 扩展 View:简化 findViewById(带泛型)
fun <T : android.view.View> android.view.View.findViewByIdExt(id: Int): T {
return this.findViewById(id) as T
}
// 在 Activity 中使用
// toast("操作成功")
// val button = findViewByIdExt<android.widget.Button>(R.id.btn_submit)
2. 工具函数封装
替代传统的静态工具类,让代码更自然。
fun String.isEmail(): Boolean = contains("@") && contains(".")
fun String.isPhoneNumber(): Boolean = matches(Regex("^1[3-9]\\\\d{9}$"))
fun String.isValidPassword(): Boolean = length >= 8 && any { it.isDigit() } && any { it.isLetter() }
fun <T> List<T>.distinctKeepOrder(): List<T> {
val seen = mutableSetOf<T>()
return this.filter { seen.add(it) }
}
fun String.capitalizeFirst(): String {
if (this.isEmpty()) return this
return this.substring(0, 1).uppercase() + this.substring(1)
}
3. 为第三方库添加功能
当使用无法修改的第三方库时,扩展函数特别有用。
// 假设使用某个 JSON 库的 JsonObject 类
fun JsonObject.getIntOrDefault(key: String, default: Int): Int {
return if (has(key)) getInt(key) else default
}
fun JsonObject.getStringOrNull(key: String): String? {
return if (has(key)) getString(key) else null
}
4. DSL 构建
扩展函数是构建类型安全 DSL 的基础。
class HTML {
private val elements = mutableListOf<String>()
fun body(init: BODY.() -> Unit) {
val body = BODY().apply(init)
elements.add(body.toString())
}
override fun toString() = elements.joinToString("")
}
class BODY {
private val content = mutableListOf<String>()
fun p(text: String) { content.add("<p>$text</p>") }
override fun toString() = "<body>${content.joinToString("")}</body>"
}
fun html(init: HTML.() -> Unit): HTML = HTML().apply(init)
fun main() {
val myHtml = html {
body {
p("第一段")
p("第二段")
}
}
println(myHtml) // <body><p>第一段</p><p>第二段</p></body>
}
六、官方资料链接
总结
掌握扩展函数是写出地道 Kotlin 代码的关键一步,它让代码更加自然、可读,同时保持了良好的封装性。



