Java 互操作
Kotlin 的杀手级特性之一是与 Java 生态的无缝、双向互操作。这不仅意味着可以从 Kotlin 调用 Java,更意味着我们可以通过注解控制 Kotlin 代码在 Java 视角下的表现形式,从而编写出对 Java 调用者友好的 API。
Kotlin 调用 Java:空安全与平台类型
Java 类型系统不包含空安全信息。当 Kotlin 读取 Java 方法的返回值或字段时,它无法确定该值是否可能为 null。因此,Kotlin 引入了平台类型 (Platform Types),表示为 T!。
平台类型陷阱 (T!)
T! 是一种“薛定谔的类型”,编译器放宽了对它的空检查。
// Java
public String getData() { return null; }// 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 类。
@file:JvmName("StringUtils") // 指定生成的 Java 类名为 StringUtils
package com.example
fun isEmpty(s: String) = s.isEmpty()Java 调用:StringUtils.isEmpty("s")
2. 静态成员:@JvmStatic
Kotlin 的 object 或 companion object 中的成员在 Java 中默认是通过 INSTANCE 字段访问的。@JvmStatic 会生成真正的静态方法/字段。
object Singleton {
@JvmStatic fun doWork() {}
}对比:
- 无注解:
Singleton.INSTANCE.doWork() - 有注解:
Singleton.doWork()
3. 重载生成:@JvmOverloads
Kotlin 的默认参数在 Java 中不可见(除非所有参数都填)。@JvmOverloads 会生成多个重载方法。
@JvmOverloads
fun search(query: String, limit: Int = 10) { ... }生成的 Java 方法:
search(String query, int limit)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。
@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)的要求,需要手动控制。
// 强制生成 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
public class User {
public String getName() { ... }
public void setName(String name) { ... }
public boolean isActive() { ... }
}// Kotlin
user.name = "Viro" // 调用 setName
println(user.isActive) // 调用 isActive()命名陷阱
如果 Java 方法名不是标准的 JavaBean 风格(例如 name() 而不是 getName()),Kotlin 无法将其识别为属性,只能作为函数调用。
SAM 转换 (Single Abstract Method)
Kotlin 对 Java 的单方法接口(如 Runnable)提供了 Lambda 支持。
// Java 方法签名: void execute(Runnable r)
executor.execute {
println("Running via Lambda") // 自动转换为 Runnable
}注意:对于 Kotlin 定义的接口,必须显式声明为 fun interface 才能享受这种 Lambda 转换,否则必须使用 object : Interface 匿名内部类写法。
核心开发准则
- 库开发者必修:如果你在开发一个会被 Java 调用的 Kotlin 库(如 Android SDK),必须熟练使用
@JvmOverloads,@JvmStatic和@JvmName来优化 Java 调用体验。 - 警惕 null:接收 Java 数据时,永远假设它是可空的,除非有明确的注解背书。
- 避免 Kotlin 特有类型:在公共 API 中尽量避免暴露
UInt、FunctionN等 Java 难以处理的类型。