1. 概述
Kotlin 的扩展函数(Extension Functions)是一项非常实用的特性,它允许我们在不修改原始类源码、不使用继承或装饰器模式的前提下,为已有类添加新功能。一旦定义了扩展函数,调用时就像它是该类原生方法一样自然。
这种能力极大提升了代码的可读性和可维护性。尤其当我们面对第三方库或 JDK 类时,无法直接修改其源码,扩展函数就成了“补丁式增强”的首选方案。
举个实际场景:我们需要对字符串进行 XML 转义处理。在 Java 中通常会这样写:
String escaped = escapeStringForXml(input);
而在 Kotlin 中,借助扩展函数可以优雅地改写为:
val escaped = input.escapeForXml()
✅ 优势明显:
- 更符合链式调用习惯,语义清晰
- IDE 能像识别普通成员方法一样提供自动补全提示
- 让工具类方法“看起来”像是目标类的一部分
2. 标准库中的常用扩展函数
Kotlin 标准库内置了大量开箱即用的扩展函数,掌握它们能显著提升编码效率。
2.1 上下文操作类扩展函数
这类函数适用于所有类型,常用于上下文切换、空值安全处理等场景。你可能已经在用了却没意识到它们是扩展函数。
✅ let
最典型的例子就是 let
,它可以作用于任意类型,并将当前对象作为参数传入 Lambda:
val name = "Baeldung"
val uppercase = name.let { n -> n.toUpperCase() }
🔁 类比 Java:类似于
Optional.map()
或Stream.map()
的行为。
其中 name
就是这个扩展函数的 receiver(接收者) —— 即被扩展的对象本身。
结合安全调用操作符 ?.
使用时威力更强:
val name = maybeGetName()
val uppercase = name?.let { n -> n.toUpperCase() }
⚠️ 注意:只有当 name
非 null 时,let
块才会执行,且块内变量 n
可保证非空。这是 Kotlin 空安全机制的重要体现。更多细节参考 Kotlin 空安全指南。
✅ run
与 let
类似,但 Lambda 内部通过 this
访问 receiver:
val name = "Baeldung"
val uppercase = name.run { toUpperCase() } // this 指向 name
✅ apply
和 run
行为一致,但返回的是 receiver 自身而非 Lambda 的结果。非常适合构建对象配置链:
val languages = mutableListOf<String>()
languages.apply {
add("Java")
add("Kotlin")
add("Groovy")
add("Python")
}.apply {
remove("Python")
}
✅ 亮点:无需显式写 this
或命名变量,代码更简洁流畅。
✅ also
类似 let
,但返回 receiver 本身(不是 Lambda 结果),适合插入副作用逻辑:
val languages = mutableListOf<String>()
languages.also { list ->
list.add("Java")
list.add("Kotlin")
list.add("Groovy")
}
// 返回 languages 本身
✅ takeIf
/ takeUnless
条件化取值神器:
takeIf
:满足条件则返回 receiver,否则返回 nulltakeUnless
:不满足条件才返回 receiver
val language = getLanguageUsed()
val coolLanguage = language.takeIf { it == "Kotlin" } // 是 Kotlin 才返回
val oldLanguage = language.takeUnless { it == "Kotlin" } // 不是 Kotlin 才返回
🔄 功能上接近 Java Stream 的
filter().findFirst().orElse(null)
组合。
2.2 集合与字符串扩展函数
Kotlin 为 Java 集合框架和 String 类型提供了海量扩展函数,极大简化了日常开发。
集合相关
这些函数定义在以下文件中:
_Collections.kt
_Ranges.kt
_Sequences.kt
_Arrays.kt
(数组专用)
覆盖了常见的 map
, filter
, reduce
, distinct
, groupBy
等操作,基本替代了 Java Stream 的大部分使用场景。
字符串相关
_Strings.kt
提供了丰富的字符串处理扩展,比如:
"hello".reverse() // "olleh"
"abc".repeat(3) // "abcabcabc"
" text ".trimIndent() // "text"
"foo.bar".substringAfter(".") // "bar"
✅ 实践建议:把 String 当作字符序列来操作已成为 Kotlin 编程的标配习惯。
3. 自定义扩展函数
当你需要为某个现有类增加专属功能时,就可以动手写自己的扩展函数了。
基本语法
扩展函数的声明方式与普通函数类似,关键区别在于:函数名前加上 receiver 类型 + 点号。
例如为 String
添加 XML 转义功能:
fun String.escapeForXml(): String {
return this
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
}
💡 函数体内可通过
this
直接访问接收者实例,就像在原类内部编写方法一样。
调用方式也如前所述:
val input = "<a>hello</a>"
val output = input.escapeForXml() // "<a>hello</a>"
3.1 泛型扩展函数
若希望扩展函数支持多种类型,可使用泛型 receiver。
fun <T> T.concatAsString(b: T): String {
return this.toString() + b.toString()
}
调用示例:
5.concatAsString(10) // "510" ✅
"5".concatAsString("10") // "510" ✅
5.concatAsString("10") // ❌ 编译报错:类型不匹配
⚠️ 踩坑提醒:泛型必须严格匹配,Kotlin 不会自动推断跨类型操作。
3.2 中缀扩展函数(Infix)
中缀函数可用于构建 DSL 风格代码,调用时省略点号和括号。
infix fun Number.toPowerOf(exponent: Number): Double {
return Math.pow(this.toDouble(), exponent.toDouble())
}
调用方式如下:
3 toPowerOf 2 // 9.0
9 toPowerOf 0.5 // 3.0
✅ 适用场景:数学运算、映射配置、条件判断等追求表达力的地方。
3.3 操作符扩展函数(Operator)
通过 operator
关键字,可以让扩展函数支持操作符重载。
例如实现列表乘法:
operator fun List<Int>.times(by: Int): List<Int> {
return this.map { it * by }
}
即可使用 *
操作符调用:
listOf(1, 2, 3) * 4 // [4, 8, 12]
✅ 支持的操作符包括:+
, -
, *
, /
, %
, []
, ==
, !=
等(需遵循 Kotlin 规范)。
4. 在 Java 中调用 Kotlin 扩展函数
虽然扩展函数是 Kotlin 特性,但在 Java 中仍可通过静态方法形式调用。
4.1 自定义扩展函数的可见性
假设我们有一个文件 StringUtil.kt
定义了:
package com.baeldung.kotlin
fun String.escapeForXml(): String {
return this.replace("&", "&").replace("<", "<").replace(">", ">")
}
在 Java 中调用时,Kotlin 编译器会生成一个名为 StringUtilKt
的工具类(自动加 Kt
后缀):
String xml = "<a>hi</a>";
String escapedXml = StringUtilKt.escapeForXml(xml);
assertEquals("<a>hi</a>", escapedXml);
📌 关键点:
- 方法第一个参数就是 receiver(即原始字符串)
- 本质是静态方法:
public static final String escapeForXml(String $this)
当然也可以静态导入简化代码:
import static com.baeldung.kotlin.StringUtilKt.*;
4.2 调用标准库扩展函数
部分 Kotlin 内建扩展函数也可从 Java 调用,例如:
String name = "john";
String capitalizedName = StringsKt.capitalize(name);
assertEquals("John", capitalizedName);
❌ 但注意:带有 @InlineOnly
注解的函数无法从 Java 调用,例如:
inline fun <T, R> T.let(block: (T) -> R): R
因为这类函数在编译期会被内联展开,不会生成对应的静态方法。
4.3 重命名生成的 Java 类名
默认生成的 XXXKt
类名不够美观?可以用 @file:JvmName
控制输出类名。
@file:JvmName("Strings")
package com.baeldung.kotlin
fun String.escapeForXml(): String {
return this.replace("&", "&").replace("<", "<").replace(">", ">")
}
之后在 Java 中就能用更简洁的类名调用:
Strings.escapeForXml(xml);
配合静态导入后几乎无感知差异:
import static com.baeldung.kotlin.Strings.*;
5. 总结
扩展函数是 Kotlin 提升代码表现力的核心利器之一,具备以下价值:
✅ 无侵入式增强:无需继承或包装即可为任意类添加方法
✅ 提升可读性:让工具方法融入业务语境,形成领域友好的 API
✅ 促进复用:通用逻辑可集中管理,避免散落在各处的 Utils 类
但也需警惕滥用风险:
- ❌ 不要过度扩展基础类型导致语义混乱
- ❌ 避免与原有方法命名冲突造成误解
- ✅ 建议按功能模块组织扩展函数文件(如
StringExtensions.kt
,DateUtils.kt
)
文中所有示例代码均可在 GitHub 获取:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-lang-oop