Kotlin/JVM 与 Java Records¶
Kotlin/JVM 运行在 JVM 上,可以自然调用 Java,也可以生成给 Java 使用的字节码。本章聚焦几个容易影响 Java 互操作体验的 JVM 主题:Java records、Kotlin 数据类、@JvmRecord、接口默认方法和 JVM API 设计。
Java records 是什么¶
Java record 是用于保存不可变数据的类:
public record Person(String name, int age) {}
Java 编译器会生成:
- 私有 final 字段。
- 全参数构造函数。
- record component 访问方法,例如
name()、age()。 equals()、hashCode()、toString()。
它和 Kotlin data class 很像,但不是同一个概念。
在 Kotlin 中使用 Java record¶
Java:
public record Person(String name, int age) {}
Kotlin:
val person = Person("Ada", 36)
println(person.name)
println(person.age)
Kotlin 可以像访问属性一样访问 record component。
Kotlin data class 与 Java record 对比¶
Kotlin:
data class Person(val name: String, val age: Int)
Java record:
public record Person(String name, int age) {}
相似点:
- 都适合表达数据载体。
- 都自动生成结构相等相关方法。
- 都减少样板代码。
差异:
- Kotlin 数据类有
copy()和解构。 - Java record 访问器是
name(),不是getName()。 - Java record 隐式继承
java.lang.Record。 - Kotlin data class 默认不是 JVM record。
@JvmRecord会改变生成字节码和 Java API 形态。
在 Kotlin 中声明 JVM record¶
@JvmRecord
data class Person(val name: String, val age: Int)
这会让 Kotlin data class 按 JVM record 形式生成 class 文件。
注意:给已有类添加 @JvmRecord 不是二进制兼容变更,因为属性访问器命名等字节码形态会改变。
@JvmRecord 要求¶
使用 @JvmRecord 的 data class 必须满足一些要求:
- 模块目标 JVM bytecode 版本至少是 16。
- 类不能显式继承其他类,因为 JVM record 隐式继承
java.lang.Record。 - 可以实现接口。
- 不能声明带 backing field 的额外属性,除非属性来自主构造函数参数。
- 不能有带 backing field 的可变属性。
- 不能是局部类。
- 主构造函数可见性必须和类本身一致。
这意味着 @JvmRecord 适合很纯粹的数据载体,不适合复杂业务对象。
注解与 record component¶
Java record 的注解可能传播到 record component、字段、访问器和构造参数。Kotlin 可以通过 @all: 接近这种行为:
@JvmRecord
data class Person(
val name: String,
@all:Positive val age: Int
)
如果注解需要同时适配 Kotlin property 和 Java record component,自定义注解时要设置合适的 Kotlin @Target 和 Java @java.lang.annotation.Target。
什么时候用 @JvmRecord¶
适合:
- 你的 Kotlin 类型主要服务 Java 16+ 调用方。
- API 明确需要 Java record 语义。
- 数据模型非常简单、不可变、无额外状态。
- 与 Java record 生态工具互操作。
不适合:
- 纯 Kotlin 内部模型。
- Android 或低 JVM target 项目。
- 需要
var、额外 backing field、复杂初始化状态。 - 不想破坏已有 Java/Kotlin 二进制兼容。
如果只是 Kotlin 内部代码,普通 data class 通常更自然。
JVM 接口默认方法¶
Kotlin 接口可以有默认实现:
interface Robot {
fun move() {
println("walking")
}
fun speak()
}
JVM 上,Kotlin 可以把接口函数编译成 Java default methods。编译行为受 -jvm-default 相关配置影响,例如是否生成兼容桥和 DefaultImpls。
对新项目来说,默认配置通常足够。公共库需要关注二进制兼容,尤其是库升级时 Java 调用方和旧 Kotlin 调用方是否仍能链接。
Kotlin API 给 Java 用时的设计清单¶
如果 Kotlin/JVM 代码是公共库或被大量 Java 代码调用,至少检查:
- 顶层函数生成类名是否需要
@file:JvmName。 - 伴生对象方法是否需要
@JvmStatic。 - 默认参数是否需要
@JvmOverloads。 - 属性是否应该暴露 getter/setter,还是
@JvmField。 - checked exception 是否需要
@Throws。 - 泛型通配符是否需要
@JvmWildcard或@JvmSuppressWildcards。 - inline value class 是否需要 Java 友好的替代 API。
- data class 是否真的需要
@JvmRecord。
Java 调用体验示例¶
Kotlin:
@file:JvmName("Users")
package demo
data class User(val id: Long, val name: String)
@JvmOverloads
fun createUser(id: Long, name: String = "unknown"): User =
User(id, name)
Java:
User a = Users.createUser(1L);
User b = Users.createUser(2L, "Ada");
如果没有 @file:JvmName,Java 会看到文件名加 Kt 的类名;如果没有 @JvmOverloads,Java 只能调用完整参数版本。
实践建议¶
- Kotlin 内部模型优先使用普通
data class,不要为了“像 Java”主动@JvmRecord。 - 面向 Java 16+ record 生态时再使用
@JvmRecord。 - 添加
@JvmRecord前评估二进制兼容影响。 - 公共 Kotlin/JVM API 必须从 Java 侧写调用样例验证。
- 接口默认方法和泛型通配符属于库作者必须关注的 ABI 细节。
参考¶
- 官方 Java records 文档:https://kotlinlang.org/docs/jvm-records.html
- 官方 Java 调 Kotlin 文档:https://kotlinlang.org/docs/java-to-kotlin-interop.html