Kotlin 与 Java 互操作:混合工程里的边界收口
文章目录
第 1 章 为什么互操作是 Android 日常
即便新模块用 Kotlin 写,你仍会碰到:
- 老 Java 库 / SDK:返回类型在 Java 里没标
@Nullable。 - 团队遗留 Java 模块:Kotlin 调
Utils,Java Activity 调 Kotlin 扩展。 - Android 框架 API:
Bundle、Intent在 Java 侧声明常不带精确可空。
工程原则:在边界收口,不在 UI 深处赌运气。从 Java 进来的值,在映射层立刻变成 T 或 T?;向外暴露给 Java 的 Kotlin API,用注解把契约写进字节码。
第 2 章 平台类型:T! 是什么
Kotlin 引用 Java(或无 null 注解)声明时,得到平台类型,写作 T!——编译器不强制 null 检查,运行时仍可能 NPE。
// Java: String getTitle() // 无 @Nullable
val title: String = javaBean.title // 平台类型,可当非空用——有风险
val safe: String? = javaBean.title // 更稳妥:主动当可空
纯 Kotlin 互操作示例(模拟 Java 边界):
// 模拟 Java 返回未标注可空的 String
fun fakeJavaGetName(): String! = "阿 Kotlin"
fun greet(): String {
val name = fakeJavaGetName() // 平台类型
return name.uppercase() // 若 Java 实际返回 null → NPE
}
Android 对照:intent.getStringExtra("city") 在 Kotlin 里是 String?,这是已标注的 API;但若调自家 Java 工具类无注解,就会遇到平台类型。
踩坑
- 把平台类型一路传到 Composable / Adapter,最深处的
!!崩溃最难查。
第 3 章 可空注解与 Kotlin 识别
在 Java 侧使用 JetBrains / AndroidX 注解,Kotlin 能识别:
// Java
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
public class UserParser {
@Nullable
public String parseName(@NotNull String raw) { ... }
}
val name: String? = UserParser().parseName("x") // 正确推断为 String?
Kotlin 调 Java 集合时,注意可变性与只读映射:java.util.List 在 Kotlin 里是 MutableList 平台视图。
第 4 章 从 Kotlin 暴露给 Java:@Jvm*
Java 看不到 Kotlin 的默认参数、顶层函数、object 单例,需要注解:
class OrderFormatter {
@JvmOverloads
fun format(price: Double, currency: String = "CNY"): String =
"$price $currency"
}
object AppConfig {
@JvmStatic
fun apiBaseUrl(): String = "https://api.example.com"
}
@JvmOverloads:为默认参数生成 Java 重载。@JvmStatic:在object/companion里生成真正静态方法。@JvmName:解决泛型擦除导致的签名冲突(进阶)。
顶层函数编译为 FileNameKt 的静态方法,Java 调用:DemoKt.greet()。
踩坑
- Kotlin
data class的componentN()给 Java 用较别扭;给 Java 消费的 DTO 有时保留 Java Bean 或加@JvmField。
第 5 章 SAM 与 lambda:Java 接口怎么接
Java 单抽象方法接口(SAM)在 Kotlin 可用 lambda:
button.setOnClickListener { view ->
view.isEnabled = false
}
Kotlin 函数类型与 Java Function 互转时,注意 Unit 与 void:() -> Unit 在 Java 侧有时要 Function0<Unit> 或显式 return。
第 6 章 Android 边界:Bundle、Intent 与收窄
标准模式:读参数 → 立刻收窄 → 再往下传非空类型(片段示例):
fun readCity(intent: Intent): String =
intent.getStringExtra("city")?.trim().takeUnless { it.isNullOrEmpty() }
?: "未知城市"
fun bindGreeting(textView: android.widget.TextView, intent: Intent) {
textView.text = "你好,${readCity(intent)}"
}
从 Java HashMap 或 JSON 库进来的值,在进入领域模型前完成 ?.let / ?: / requireNotNull(仅确定非空时)处理。
踩坑
- 在 UI 层对平台类型用
!!「反正不会 null」——混合工程里这是 NPE 温床。
动手练
练习 1:平台类型(A · 基础)
目标:理解 T!。
要求:说明平台类型为何「编译器不强制检查 null」;写一行把平台类型赋给 String? 的代码。
练习 2:@JvmOverloads(A · 基础)
目标:Java 可调默认参数。
要求:为 fun connect(host: String, port: Int = 443) 加注解,并说明 Java 能怎么调。
练习 3:@JvmStatic(D · Android 实践)
目标:object 给 Java 用。
要求:object Logger { @JvmStatic fun d(msg: String) },写出 Java 调用语句 Logger.d("ok");。
练习 4:坏代码收窄(B · 进阶)
起点:
fun showTitle(javaApi: JavaTitleApi, view: android.widget.TextView) {
view.text = javaApi.getTitle()!!.uppercase()
}
要求:去掉 !!,null 时显示 "无标题"(JavaTitleApi.getTitle() 为平台类型 String!)。
练习 5:SAM(A · 基础)
目标:lambda 接 Java 接口。
要求:用 lambda 写 list.sortWith { a, b -> a.compareTo(b) } 的等价比较器(纯 Kotlin List<String> 即可)。
练习 6:边界收口(C · 综合)
目标:Intent 参数链。
要求:fun parseCount(intent: Intent): Int,从 getStringExtra("count") 解析,非法或缺失返回 0。
进阶阅读(可选)
追问链 #1:平台类型在防什么? 🔥
参考回答:Java 无统一可空注解时,Kotlin 无法知道能否为 null,故给 T!。应在边界收窄为 T/T?,不要穿透 UI。
追问链 #2:@JvmOverloads 生成几个重载? 🔥
参考回答:为每一组「从右向左省略默认参数」的组合生成 Java 重载(受 JVM 限制,极多默认参数时可能不全)。
追问链 #3:Kotlin object 在 Java 里长什么样? ⭐
参考回答:单例实例 INSTANCE;@JvmStatic 成员才是静态方法。
追问链 #4:为什么 getStringExtra 是 String? 而不是平台类型? 💡
参考回答:AndroidX/Kotlin 增强声明或已标注可空;自家 Java 代码若无注解仍是平台类型。
完整链路一句通
Java 无注解 → 平台类型 T! → 边界立刻收窄为 T/T? → 向外暴露用 @JvmStatic/@JvmOverloads → Bundle/Intent 用 ?./?: 再进 UI。

相关推荐
更多推荐




所有评论(0)