Kotlin 2.5 新特性:伴生块与伴生扩展实战指南
Kotlin 2.5.0 引入了一个令人兴奋的实验性特性:伴生块(companion blocks) 与 伴生扩展(companion extensions)。它们从根本上改变了我们定义类级别成员的方式,让代码更简洁,互操作性更强。
为什么需要新特性?
在 Kotlin 中,companion object 一直是定义类级别常量、工厂方法和工具函数的标准方式。但它有几个痛点:
// 旧写法:需要 @JvmStatic 才能让 Java 方便调用
class Vector(val x: Double, val y: Double) {
companion object {
@JvmStatic
val ZERO = Vector(0.0, 0.0)
@JvmStatic
fun fromPolar(r: Double, theta: Double): Vector =
Vector(r * kotlin.math.cos(theta), r * kotlin.math.sin(theta))
}
}
每个 @JvmStatic 注解都是一份"税"——忘记写一个,Java 端就要多写一层 Companion 访问。而且 companion object 本质上是一个对象实例,JVM 上会额外生成一个类。
伴生块:更轻量的类级别成员
Kotlin 2.5.0 引入了 companion {} 块语法,直接生成 静态成员,不再创建对象实例:
// 新写法:companion 块,编译为 JVM 静态成员
class Vector(val x: Double, val y: Double) {
companion {
val ZERO = Vector(0.0, 0.0)
fun fromPolar(r: Double, theta: Double): Vector =
Vector(r * kotlin.math.cos(theta), r * kotlin.math.sin(theta))
}
}
关键区别:
- 不需要
@JvmStatic:在 JVM 平台上,companion块中的成员自动编译为静态方法和字段 - 不生成额外类:
companion object会生成一个单例类,而companion块不会 - 语法更简洁:少了
object关键字,语义更明确
Java 端可以直接调用:
// Java 代码中直接调用,无需 .Companion
Vector v = Vector.fromPolar(1.0, Math.PI / 4);
Vector zero = Vector.ZERO;
伴生扩展:给任意类型添加类级别成员
更强大的能力是 伴生扩展。即使一个类没有定义 companion object,你也能通过伴生扩展为它添加类级别成员。这对 Java 类型尤其有用:
// 为 Java 的 LocalDate 添加伴生扩展
package com.example.dates
import java.time.LocalDate
companion fun LocalDate.fromCompact(text: String): LocalDate {
require(text.length == 8) { "格式应为 YYYYMMDD,共 8 位" }
return LocalDate.of(
text.substring(0, 4).toInt(),
text.substring(4, 6).toInt(),
text.substring(6, 8).toInt(),
)
}
使用时只需导入:
import com.example.dates.fromCompact
import java.time.LocalDate
val date = LocalDate.fromCompact("20261013") // 2026-10-13
LocalDate 是 Java 类,根本没有 Kotlin 的 companion object,但伴生扩展让我们像使用原生 Kotlin API 一样调用它。
伴生扩展属性也同样好用:
// 伴生扩展属性,支持 backing field
companion val Vector.UnitX get() = Vector(1.0, 0.0)
companion val Vector.UnitY get() = Vector(0.0, 1.0)
如何启用
这个特性目前还是实验性的,需要手动开启。
Gradle Kotlin DSL:
kotlin {
compilerOptions {
// 只启用伴生块
freeCompilerArgs.add("-Xcompanion-blocks")
// 同时启用伴生扩展(会生成 pre-release 编译产物)
freeCompilerArgs.add("-Xcompanion-blocks-and-extensions")
}
}
注意事项:
- 只启用伴生块(
-Xcompanion-blocks)不影响编译产物的稳定性 - 启用伴生扩展(
-Xcompanion-blocks-and-extensions)会生成 pre-release binaries,作为库发布时需注意下游兼容性 - 在 IntelliJ IDEA 2026.1+ 中,直接写
companion {}会提示你启用该特性
与 companion object 的对比
| 特性 | companion object | companion 块 | 伴生扩展 |
|---|---|---|---|
| 需要声明在类内部 | 是 | 是 | 否(顶层声明) |
| 生成额外类 | 是 | 否 | 否 |
| JVM 静态成员 | 需 @JvmStatic | 自动 | 自动 |
| 可扩展 Java 类型 | 否 | 否 | 是 |
| 可实现接口 | 是 | 否 | 否 |
| 有实例 | 是(单例) | 否 | 否 |
| 属性支持 backing field | 是 | 是 | 是 |
迁移建议
不需要急着把所有 companion object 替换掉。以下场景建议优先迁移:
- 频繁被 Java 调用的代码:去掉所有
@JvmStatic,减少出错可能 - 纯工具类/常量类:不需要实现接口、不需要单例实例的场景
- 需要为 Java 类型添加工具方法:用伴生扩展替代顶层函数,API 更直观
以下场景暂时保留 companion object:
- companion object 实现了接口
- 代码中依赖 companion object 的单例特性
- 需要保持库的二进制兼容性
总结
伴生块和伴生扩展是 Kotlin 语言在"简洁"和"实用"方向上的又一次进化。它解决了 companion object 长期存在的 JVM 互操作痛点,同时通过伴生扩展打开了为任意类型(包括 Java 类型)添加类级别成员的大门。
虽然目前还是实验性特性,但设计已经相当成熟(KEEP-0449),预计在未来版本中会逐步稳定。建议大家现在就开始在项目中试用,提前感受 Kotlin 语言演进的节奏。
// 一句话总结
companion { /* 更简洁的类级别成员 */ }
companion fun Type.newMember() { /* 给任意类型加成员 */ }
参考资料: