在 Kotlin 中调用 Java

Kotlin 设计时就把 Java 互操作作为核心目标。绝大多数 Java 代码可以直接从 Kotlin 调用,但“能调用”不代表“语义完全一样”。真正需要理解的是属性映射、平台类型、集合可变性、关键字转义和泛型空性。

直接调用 Java 类

Java:

public class UserService {
    public String findName(long id) {
        return "Ada";
    }
}

Kotlin:

val service = UserService()
val name = service.findName(1)
println(name)

Kotlin 会自然识别 Java 类、构造函数、方法和字段。

getter/setter 映射为属性

Java:

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Kotlin 调用:

val user = User()
user.name = "Ada"
println(user.name)

user.name 实际调用 getName() / setName()。这类属性称为 synthetic property。

布尔 getter 也会映射:

public boolean isEnabled() { return true; }
public void setEnabled(boolean enabled) {}

Kotlin:

if (config.isEnabled) {
    config.isEnabled = false
}

如果 Java 类只有 setter,没有 getter,Kotlin 不会把它暴露成属性,因为 Kotlin 不支持“只写属性”。

Java void 与 Kotlin Unit

Java 返回 void 的方法,在 Kotlin 中返回 Unit

public void save() {}

Kotlin:

val result = service.save() // result 类型是 Unit

通常你不会使用这个返回值。它存在是为了让 Kotlin 的表达式和函数类型系统保持统一。

Java 标识符与 Kotlin 关键字冲突

如果 Java 方法名刚好是 Kotlin 关键字,可以用反引号调用:

foo.`is`(bar)
foo.`object`()

这类情况不常见,但在调用老 Java API、代码生成 API 或特殊 DSL 时可能遇到。

平台类型

Java 引用可能为 null,但 Java 类型本身通常不表达空性。Kotlin 调用未标注空性的 Java API 时,会得到平台类型。

Java:

public String findName(long id) {
    return null;
}

Kotlin:

val name = api.findName(1)
println(name.length) // 编译器可能允许,但运行时可能 NPE

IDE 可能把它显示为 String!。你不能在 Kotlin 源码中显式写 String!,它只是编译器和 IDE 表示“不知道是 String 还是 String?”的方式。

建议在边界处立刻收敛类型:

val name: String? = api.findName(1)
val displayName = name ?: "匿名"

或如果业务上必须非空:

val name: String = api.findName(1)
    ?: error("Java API 返回了空 name")

空性注解

Kotlin 可以识别多种 Java 空性注解,例如:

  • JetBrains @Nullable / @NotNull
  • JSpecify @Nullable / @NonNull / @NullMarked
  • Android 注解
  • JSR-305 注解
  • Eclipse、FindBugs、Lombok、RxJava 相关注解

Java:

import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;

public class UserApi {
    public @Nullable String findNickname(long id) {
        return null;
    }

    public @NotNull String findName(long id) {
        return "Ada";
    }
}

Kotlin:

val nickname: String? = api.findNickname(1)
val name: String = api.findName(1)

对 Java 库维护者来说,补充空性注解是改善 Kotlin 体验最有效的方式之一。

Java 集合映射

Java 集合在 Kotlin 中会映射到 Kotlin 集合接口,但因为 Java 集合本身可变性不明确,经常显示为类似 (Mutable)List<T>! 的平台形式。

val list = javaApi.loadNames()

你需要根据 API 语义决定:

val readOnly: List<String> = javaApi.loadNames()
val mutable: MutableList<String> = javaApi.loadNames()

如果 Java 方法没有明确承诺可变性,不要随意把返回值当作可变集合修改。必要时复制:

val mutableNames = javaApi.loadNames().toMutableList()

Java 数组

Java 数组可以从 Kotlin 调用:

val array: Array<String> = javaApi.loadArray()
println(array[0])

基本类型数组有专门类型:

  • IntArray
  • LongArray
  • DoubleArray
  • BooleanArray

它们避免装箱,类似 Java 的 int[]long[]

SAM 转换

Java 函数式接口可以用 Kotlin Lambda 调用:

Java:

public interface Callback {
    void onDone(String value);
}

public void load(Callback callback) {}

Kotlin:

api.load { value ->
    println(value)
}

这是 Kotlin 调 Java API 时非常常见的写法,尤其在旧 Java 回调接口、Swing、Android、Executor 等场景中。

checked exception

Java 方法声明的 checked exception 不会强制 Kotlin 捕获:

public String read() throws IOException {}

Kotlin:

val text = reader.read() // 编译器不强制 try-catch

这让调用更简洁,但也意味着你要根据业务场景主动处理异常。不要因为 Kotlin 不强制,就忽略真实的失败路径。

实践建议

  • 调 Java API 的返回值时,优先显式收敛平台类型。
  • 能给 Java 代码补空性注解就补,尤其是公共库和核心模块。
  • Java 集合返回值不明确时,按需要 toList()toMutableList()
  • checked exception 不强制捕获,但设计上仍要处理 IO、网络、数据库等失败。
  • 关键业务边界不要让平台类型深入 Kotlin 核心领域模型。

参考

  • 官方文档:Calling Java from Kotlin:https://kotlinlang.org/docs/java-interop.html