在 Java 中调用 Kotlin¶
Kotlin 调 Java 通常很自然;Java 调 Kotlin 也可行,但需要理解 Kotlin 编译到 JVM 后的形态。公共 API 如果要被 Java 使用,应主动设计 Java 调用体验,而不是只看 Kotlin 代码是否优雅。
Kotlin 属性在 Java 中的形态¶
Kotlin:
class User(var firstName: String, val id: Long)
Java 大致看到:
User user = new User("Ada", 1L);
String name = user.getFirstName();
user.setFirstName("Grace");
long id = user.getId();
val 只有 getter,var 有 getter 和 setter。
如果属性名以 is 开头:
class Door(var isOpen: Boolean)
Java:
door.isOpen();
door.setOpen(false);
顶层函数¶
Kotlin:
// File: Strings.kt
package demo
fun normalize(value: String): String = value.trim()
Java 调用:
String result = demo.StringsKt.normalize(" Ada ");
默认生成的 Java 类名是文件名加 Kt。
可以用 @file:JvmName 改名:
@file:JvmName("StringUtils")
package demo
fun normalize(value: String): String = value.trim()
Java:
String result = demo.StringUtils.normalize(" Ada ");
多个文件也可以通过 @JvmMultifileClass 合并成同一个 Java facade,但公共库中要谨慎使用,避免二进制兼容和命名混乱。
@JvmField¶
默认情况下,Kotlin 属性对 Java 暴露为 getter/setter。如果希望 Java 直接访问字段:
class User(id: String) {
@JvmField
val ID = id
}
Java:
String id = user.ID;
@JvmField 适合常量式、互操作要求明确的字段。普通业务属性仍推荐通过访问器暴露。
const val¶
const val MAX_RETRY = 3
Java 中会看到静态字段:
int retry = ConstantsKt.MAX_RETRY;
const val 会被编译期内联。公共库变更常量值时,Java/Kotlin 调用方可能需要重新编译才能看到新值。
伴生对象与 @JvmStatic¶
Kotlin:
class User private constructor(val name: String) {
companion object {
fun create(name: String): User = User(name)
}
}
Java 默认调用:
User user = User.Companion.create("Ada");
如果希望 Java 像调用静态方法一样调用:
class User private constructor(val name: String) {
companion object {
@JvmStatic
fun create(name: String): User = User(name)
}
}
Java:
User user = User.create("Ada");
object 中的方法同理。@JvmStatic 会额外生成静态方法,同时保留对象实例方法。
默认参数与 @JvmOverloads¶
Kotlin:
class Circle(
val x: Int,
val y: Int,
val radius: Double = 1.0
)
Kotlin 调用可以省略默认参数:
Circle(0, 0)
Java 默认看不到这种省略能力。需要生成重载时:
class Circle @JvmOverloads constructor(
val x: Int,
val y: Int,
val radius: Double = 1.0
)
方法也可以:
@JvmOverloads
fun draw(label: String, lineWidth: Int = 1, color: String = "red") {
}
注意:@JvmOverloads 会为默认参数生成多个重载,公共 API 中要控制参数数量,避免生成过多方法。
@Throws¶
Kotlin 没有 checked exception,所以默认不会在 JVM 方法签名上声明 throws:
fun writeToFile() {
throw IOException()
}
Java 无法按 checked exception 方式捕获:
try {
ExampleKt.writeToFile();
} catch (IOException e) { // 可能编译报错
}
需要时使用:
@Throws(IOException::class)
fun writeToFile() {
throw IOException()
}
这样 Java 调用方能看到 throws IOException。
internal 在 Java 中的可见性¶
Kotlin 的 internal 在 JVM 字节码层面通常仍是 public,只是会做名称修饰,降低 Java 误用概率。
这意味着:
internal是 Kotlin 编译器层面的模块边界。- Java 代码仍可能看到或调用某些 internal 成员。
- 不要把
internal当作安全边界。
公共库如果需要真正隐藏实现,要结合模块化、包组织、构建发布和 Java 可见性一起考虑。
泛型通配符¶
Kotlin 的 out / in 会映射到 Java 通配符:
class Box<out T>(val value: T)
Java 可能看到 Box<? extends Base>。如果生成的通配符影响 Java 调用体验,可以使用:
@JvmWildcard@JvmSuppressWildcards
这属于公共 API 设计细节。一般业务模块不需要主动使用,库代码才需要重点关注。
空安全¶
Java 可以向 Kotlin 非空参数传 null:
fun greet(name: String) {
println(name.length)
}
Java:
ExampleKt.greet(null);
Kotlin 会为公共非空参数生成运行时检查,让问题尽早以异常形式暴露。不要误以为 Java 调用方也能享受 Kotlin 编译期空安全。
实践建议¶
- 要被 Java 调用的 Kotlin API,应专门从 Java 写一段调用代码检查体验。
- 顶层函数公共暴露时考虑
@file:JvmName。 - 伴生对象工厂方法给 Java 用时考虑
@JvmStatic。 - 默认参数给 Java 用时考虑
@JvmOverloads,但不要滥用。 - checked exception 面向 Java 调用方时使用
@Throws。 internal不是 Java 层面的强封装。
参考¶
- 官方文档:Calling Kotlin from Java:https://kotlinlang.org/docs/java-to-kotlin-interop.html