Skip to content

Java 互操作

源:Java interoperability

Kotlin 的杀手级特性之一是与 Java 生态的无缝、双向互操作。这不仅意味着可以从 Kotlin 调用 Java,更意味着我们可以通过注解控制 Kotlin 代码在 Java 视角下的表现形式,从而编写出对 Java 调用者友好的 API。

Kotlin 调用 Java:空安全与平台类型

Java 类型系统不包含空安全信息。当 Kotlin 读取 Java 方法的返回值或字段时,它无法确定该值是否可能为 null。因此,Kotlin 引入了平台类型 (Platform Types),表示为 T!

平台类型陷阱 (T!)

T! 是一种“薛定谔的类型”,编译器放宽了对它的空检查。

kotlin
// Java
public String getData() { return null; }
kotlin
// Kotlin
val data = javaObj.data // 类型是 String!
// 1. 可以赋值给非空类型(运行时可能崩)
val s: String = data // 抛出 NPE
// 2. 可以赋值给可空类型(安全)
val sNullable: String? = data // 安全
// 3. 直接调用方法(不报错,但危险)
println(data.length) // 抛出 NPE

解决方案:JSR-305 注解

为了让 Kotlin 正确识别空状态,应在 Java 代码中广泛使用 @Nullable / @NonNull 注解(如 AndroidX Annotations)。

Java 调用 Kotlin:注解控制

为了让 Kotlin 生成的字节码更符合 Java 开发者的习惯,Kotlin 提供了一系列 @Jvm 注解。

1. 文件名优化:@file:JvmName

默认情况下,Util.kt 中的顶层函数会编译为 UtilKt 类。

kotlin
@file:JvmName("StringUtils") // 指定生成的 Java 类名为 StringUtils
package com.example

fun isEmpty(s: String) = s.isEmpty()

Java 调用StringUtils.isEmpty("s")

2. 静态成员:@JvmStatic

Kotlin 的 objectcompanion object 中的成员在 Java 中默认是通过 INSTANCE 字段访问的。@JvmStatic 会生成真正的静态方法/字段。

kotlin
object Singleton {
    @JvmStatic fun doWork() {}
}

对比

  • 无注解:Singleton.INSTANCE.doWork()
  • 有注解:Singleton.doWork()

3. 重载生成:@JvmOverloads

Kotlin 的默认参数在 Java 中不可见(除非所有参数都填)。@JvmOverloads 会生成多个重载方法。

kotlin
@JvmOverloads
fun search(query: String, limit: Int = 10) { ... }

生成的 Java 方法

  1. search(String query, int limit)
  2. search(String query) // 默认 limit=10

4. 字段暴露:@JvmField

默认情况下,Kotlin 属性会生成 private field + public getter/setter@JvmField 会直接生成 public field,不生成访问器。

  • 场景:Android Parcelable.CREATOR,或纯数据结构。

5. 受检异常:@Throws

Kotlin 没有受检异常(Checked Exceptions),这会导致 Java 编译器不知道某个方法会抛出异常,从而无法强制 catch。

kotlin
@Throws(IOException::class)
fun readFile() {
    throw IOException()
}

泛型通配符控制

在 Java 中,泛型往往通过通配符(Wildcards)来实现灵活性。

  • List<out String> -> List<? extends String>
  • List<in String> -> List<? super String>

@JvmWildcard 与 @JvmSuppressWildcards

有时编译器的默认映射不符合某些 Java 框架(如 Dagger, Retrofit)的要求,需要手动控制。

kotlin
// 强制生成 List<? extends View>
fun processList(list: List<@JvmWildcard View>)

// 强制生成 List<View> (无通配符)
fun processList(list: List<@JvmSuppressWildcards View>)

属性访问与 Getter/Setter 约定

Kotlin 编译器会自动将 Java 的 getFoo() / setFoo(v) / isBar() 映射为 Kotlin 属性。

java
// Java
public class User {
    public String getName() { ... }
    public void setName(String name) { ... }
    public boolean isActive() { ... }
}
kotlin
// Kotlin
user.name = "Viro" // 调用 setName
println(user.isActive) // 调用 isActive()

命名陷阱

如果 Java 方法名不是标准的 JavaBean 风格(例如 name() 而不是 getName()),Kotlin 无法将其识别为属性,只能作为函数调用。

SAM 转换 (Single Abstract Method)

Kotlin 对 Java 的单方法接口(如 Runnable)提供了 Lambda 支持。

kotlin
// Java 方法签名: void execute(Runnable r)
executor.execute { 
    println("Running via Lambda") // 自动转换为 Runnable
}

注意:对于 Kotlin 定义的接口,必须显式声明为 fun interface 才能享受这种 Lambda 转换,否则必须使用 object : Interface 匿名内部类写法。

核心开发准则

  1. 库开发者必修:如果你在开发一个会被 Java 调用的 Kotlin 库(如 Android SDK),必须熟练使用 @JvmOverloads, @JvmStatic@JvmName 来优化 Java 调用体验。
  2. 警惕 null:接收 Java 数据时,永远假设它是可空的,除非有明确的注解背书。
  3. 避免 Kotlin 特有类型:在公共 API 中尽量避免暴露 UIntFunctionN 等 Java 难以处理的类型。