跳到主要内容

Kotlin 2.5 新特性:伴生块与伴生扩展实战指南

· 阅读需 5 分钟
重庆 Kotlin User Group

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 objectcompanion 块伴生扩展
需要声明在类内部是是否(顶层声明)
生成额外类是否否
JVM 静态成员需 @JvmStatic自动自动
可扩展 Java 类型否否是
可实现接口是否否
有实例是(单例)否否
属性支持 backing field是是是

迁移建议​

不需要急着把所有 companion object 替换掉。以下场景建议优先迁移:

  1. 频繁被 Java 调用的代码:去掉所有 @JvmStatic,减少出错可能
  2. 纯工具类/常量类:不需要实现接口、不需要单例实例的场景
  3. 需要为 Java 类型添加工具方法:用伴生扩展替代顶层函数,API 更直观

以下场景暂时保留 companion object:

  • companion object 实现了接口
  • 代码中依赖 companion object 的单例特性
  • 需要保持库的二进制兼容性

总结​

伴生块和伴生扩展是 Kotlin 语言在"简洁"和"实用"方向上的又一次进化。它解决了 companion object 长期存在的 JVM 互操作痛点,同时通过伴生扩展打开了为任意类型(包括 Java 类型)添加类级别成员的大门。

虽然目前还是实验性特性,但设计已经相当成熟(KEEP-0449),预计在未来版本中会逐步稳定。建议大家现在就开始在项目中试用,提前感受 Kotlin 语言演进的节奏。

// 一句话总结
companion { /* 更简洁的类级别成员 */ }
companion fun Type.newMember() { /* 给任意类型加成员 */ }

参考资料: