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,否则返回 null
  • takeUnless:不满足条件才返回 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("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
}

💡 函数体内可通过 this 直接访问接收者实例,就像在原类内部编写方法一样。

调用方式也如前所述:

val input = "<a>hello</a>"
val output = input.escapeForXml() // "&lt;a&gt;hello&lt;/a&gt;"

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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
}

在 Java 中调用时,Kotlin 编译器会生成一个名为 StringUtilKt 的工具类(自动加 Kt 后缀):

String xml = "<a>hi</a>";
String escapedXml = StringUtilKt.escapeForXml(xml);

assertEquals("&lt;a&gt;hi&lt;/a&gt;", 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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
}

之后在 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


原始标题:Extension Functions in Kotlin