欢迎光临
我们一直在努力

9-Kotlin高阶语法-扩展函数

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 扩展函数/属性(官方英文): https://kotlinlang.org/docs/extensions.html
  • Kotlin 扩展函数/属性(中文翻译): https://www.kotlincn.net/docs/reference/extensions.html
  • Kotlin 扩展函数最佳实践(官方博客): https://blog.jetbrains.com/kotlin/2015/02/kotlin-1-0-beta-2-is-out/(扩展函数部分)
  • Kotlin 扩展函数常见陷阱: https://kotlinlang.org/docs/extensions.html#extensions-are-resolved-statically
  • Kotlin 作用域函数: https://kotlinlang.org/docs/scope-functions.html

  • 总结

  • 核心特性:扩展函数是静态解析的语法糖,可在不修改原类的前提下为其添加方法,支持可空接收者、泛型扩展、扩展属性等。
  • 关键注意点:静态解析导致运行时多态失效,成员函数优先级高于扩展函数,扩展属性没有幕后字段。
  • 核心价值:替代传统工具类,让代码更简洁、更符合面向对象风格,是 Kotlin 提升开发效率的核心特性之一。
  • 广泛应用:从 Android 开发到后端服务,从集合处理到 DSL 构建,扩展函数无处不在。
  • 掌握扩展函数是写出地道 Kotlin 代码的关键一步,它让代码更加自然、可读,同时保持了良好的封装性。

    赞(0)
    未经允许不得转载:171主机测评 » 9-Kotlin高阶语法-扩展函数
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址