Android 高级工程师面试:Kotlin 与 Java 互操作 近1年高频追问 22 题
文章目录
- 学习建议
- 基础层(8 题)
-
- #1 为什么 Android 混合工程必须理解 Kotlin-Java 互操作? 🔥
- #2 Kotlin 源码编译后在 JVM 上呈现什么? ⭐
- #3 什么是平台类型(platform type)?Java 调用 Kotlin 有什么风险? 🔥
- #4 Kotlin 属性与 Java 字段:getter/setter 还是 field? ⭐
- #5 @JvmStatic 和 @JvmField 分别解决什么? 🔥
- #6 @JvmOverloads 如何让 Java 调用 Kotlin 默认参数? 🔥
- #7 companion object 从 Java 如何调用? ⭐
- #8 Kotlin object 单例在 Java 里怎么用? ⭐
- 进阶层(7 题)
- 核心层(7 题)
-
- #16 Kotlin 与 Java 集合类型如何映射?可变性陷阱在哪? 🔥
- #17 从 Java 视角看 Kotlin out/in 与通配符? 🔥
- #18 @JvmWildcard 和 @JvmSuppressWildcards 有何用? 💡
- #19 data class 从 Java 侧如何构造、读取与 copy? ⭐
- #20 Java 如何调用 Kotlin suspend?Continuation 互操作要点 🔥
- #21 混合模块、AGP 与 R8 如何保留 Kotlin metadata? 🔥
- #22 大型 Kotlin+Java 混合项目互操作最佳实践? ⭐
- 面试策略速查
- 完整链路一句通
- 相关推荐
学习建议
| 目标层级 | 建议 |
|---|---|
| 初级 | 掌握基础层 8 题:@JvmStatic/@JvmOverloads、平台类型、companion/object 从 Java 怎么调;工程联想看追问第 1 条 |
| 中级 | 通读全文;进阶层能讲清 SAM 双向、@Throws、空安全注解互操作、顶层扩展的 Java 静态调用 |
| 高级 | 核心层加分项必答:集合可变性映射、out/in 通配符视角、suspend Continuation、R8 keep Kotlin metadata;能串联「注解→字节码→Java 调用面→混淆」链路 |
基础层(8 题)
#1 为什么 Android 混合工程必须理解 Kotlin-Java 互操作? 🔥
标准回答
现代 Android 工程几乎全是 Kotlin+Java 共存,互操作决定 API 能否被正确调用、空指针能否被编译期拦住。Kotlin 编译为 JVM 字节码后与 Java 共享同一套类型系统,但语法糖(默认参数、属性、companion)在 Java 侧有额外规则。
面试官可能继续追问
-
Android 混合工程里互操作常见场景?
存量 Java 模块、三方 SDK、注解处理器生成代码、R8 混淆后反射;新 Kotlin API 未加 @Jvm* 注解会导致 Java 编译失败或 NPE。 -
全 Kotlin 项目还需要学吗?
需要,读库源码、接 Java SDK、写对外 API 给 Java 模块时仍会遇到。
#2 Kotlin 源码编译后在 JVM 上呈现什么? ⭐
标准回答
.kt 经 kotlinc 编译为 .class,与 Java 字节码同跑在 ART/Dalvik 上,类名、方法签名遵循 JVM 规范。顶层函数编译为 文件名Kt 的静态方法;object 生成单例类 + INSTANCE;inline 在调用点展开,不单独生成可被 Java 直接调用的方法体。
面试官可能继续追问
-
Android 混合模块编译产物怎么合并?
GradlecompileKotlin与compileJava产物进同一 dex;Java 只看到编译后的.class签名,看不到 Kotlin 语法糖。 -
能看到 Kotlin 源码里的
val吗?
不能,Java 侧只见 getter 或@JvmField暴露的字段。
#3 什么是平台类型(platform type)?Java 调用 Kotlin 有什么风险? 🔥
标准回答
平台类型是 Kotlin 从 Java 继承/API 读入时,编译器无法确定可空性的类型,表现为 T!,可当作 T 或 T? 使用。Java 无 Kotlin 空安全信息时,签名里的 String 在 Kotlin 侧是 String!;若 Java 实际返回 null,Kotlin 当非空用会 NPE。
面试官可能继续追问
-
Android 里平台类型怎么防 NPE?
调用老旧 Java SDK、缺@Nullable的 Framework API 时主动?.;向 Java 暴露 API 应加@Nullable/@NonNull帮 Kotlin 侧消歧。 -
平台类型会写进字节码吗?
不会,只是 Kotlin 编译器前端概念;字节码仍是 Java 原始签名。
#4 Kotlin 属性与 Java 字段:getter/setter 还是 field? ⭐
标准回答
默认 val/var 编译为 private 字段 + getX()/setX(),Java 用 getter/setter 访问,不是直接字段访问。var name: String → getName()/setName();val 只有 getter。@JvmField 才暴露 public 字段,跳过 accessor。
面试官可能继续追问
-
Android 里属性互操作注意什么?
Data Binding、部分反射库、序列化框架若按字段名找属性,Kotlin 类需@JvmField或@get:JvmName对齐;否则 Java 里user.name实际是方法调用。 -
is前缀布尔属性 Java 怎么调?isActive→isActive(),不是getIsActive()。
#5 @JvmStatic 和 @JvmField 分别解决什么? 🔥
标准回答
@JvmStatic: companion / object 中的方法,生成真正静态方法,Java 可 Utils.doWork() 而非 Utils.INSTANCE.doWork()。@JvmField:属性编译为 public 字段,Java 可 obj.count++,无 getter/setter。
面试官可能继续追问
-
Android 里 @JvmStatic/@JvmField 用在哪?
companion object里给 Java 用的工厂方法、常量应加@JvmStatic/@JvmField;Firebase、Retrofit 等 Java 友好 API 常用此模式。 -
@JvmStatic 能用在顶层函数吗?
顶层函数本身已是静态,不需要;主要用于 companion/object 成员。
#6 @JvmOverloads 如何让 Java 调用 Kotlin 默认参数? 🔥
标准回答
Kotlin 默认参数在 Java 不可见,Java 必须传齐所有参数;@JvmOverloads 为每个默认参数生成重载方法。编译器按「从右向左省略默认值」生成 foo()、foo(a)、foo(a,b) 等重载,Java 调用最近匹配签名。
面试官可能继续追问
-
Android 里 @JvmOverloads 什么时候加?
给 Java 模块暴露的 Kotlin API(自定义 View 构造辅助、Builder)应在带默认值的构造/方法上加,否则 Java 无法省略参数。 -
所有默认参数函数都该加吗?
仅需要被 Java 调用的公开 API 加;纯 Kotlin 内部不必。
#7 companion object 从 Java 如何调用? ⭐
标准回答
companion 编译为带 Companion 内部类 + 外部类静态入口;Java 默认通过 OuterClass.Companion 访问。未标 @JvmStatic 的成员在 Java 里是 MyClass.Companion.doSomething();标了才是 MyClass.doSomething()。
面试官可能继续追问
-
Android 里 companion 从 Java 怎么调?
const val TAG的const val已是编译期常量,Java 可直接MyClass.TAG;普通 companion 方法需@JvmStatic。 -
companion 能实现 Java 接口吗?
可以,但 Java 侧仍要通过 Companion 实例或 @JvmStatic 转静态。
#8 Kotlin object 单例在 Java 里怎么用? ⭐
标准回答
object Foo 编译为单例类,Java 通过 Foo.INSTANCE 访问唯一实例,再调实例方法。Kotlin 保证线程安全懒初始化;Java 无 object 语法,只看到 public static final Foo INSTANCE。
面试官可能继续追问
-
Android 里 object 单例 Java 怎么调?
object NetworkHolder作全局持有很常见;给 Java 调用时文档要写清XXX.INSTANCE.method(),或包一层@JvmStatic门面。 -
object 和 companion 单例区别?
object 是独立单例类;companion 附属于外部类,命名空间不同。
进阶层(7 题)
#9 SAM 转换双向:Java 接口与 Kotlin lambda 如何互调? 🔥
标准回答
Java → Kotlin:Java SAM 接口在 Kotlin 调用处可写 lambda,如 view.setOnClickListener { }。Kotlin → Java:Kotlin fun interface 或单抽象方法接口,Java 8+ 可用 lambda 实现;多抽象方法接口不行。
面试官可能继续追问
-
Android 里 SAM 互操作例子?
Runnable、OnClickListener、Predicate等大量 SAM;Kotlin 写 lambda,Java 传 lambda 给 Kotlin 高阶函数时需函数式接口。 -
接口有默认方法还算 SAM 吗?
仅一个抽象方法仍可 SAM 转换。
#10 Kotlin 顶层/扩展函数在 Java 里如何静态调用? 💡
标准回答
顶层函数与扩展函数编译到 文件名Kt(或 @file:JvmName 指定名)的 static 方法;扩展函数第一个参数是接收者。fun String.lastSegment() → StringExtKt.lastSegment(str);Java 无扩展语法,必须显式传接收者。
面试官可能继续追问
-
Android 里扩展函数 Java 怎么调?
Context.kt里fun Context.dp()在 Java 为ContextExtKt.dp(context, 16);对 Java 暴露扩展 API 时用@file:JvmName("ContextUtils")稳定类名。 -
扩展能在 Java 里像实例方法吗?
不能,永远是静态 + 接收者参数。
#11 @Throws 如何处理 Kotlin 与 Java 受检异常差异? ⭐
标准回答
Kotlin 无受检异常,但 Java 调用 Kotlin 抛出的异常时,编译器不把 Kotlin 异常当 checked;@Throws 让 Kotlin 函数在 Java 签名上 声明 异常,逼 Java 处理或 throws。@Throws(IOException::class) 写入字节码 throws IOException;多异常可列多个 class。
面试官可能继续追问
-
Android 里 @Throws 什么时候加?
封装 IO、解析逻辑给 Java 模块用时,若希望 Java 方 try-catch,应加@Throws;纯 Kotlin 调用方仍按运行时异常处理。 -
Kotlin 调 Java 受检异常呢?
Kotlin 不强制 catch,但异常仍会抛出,建议显式处理。
#12 空安全注解互操作:@Nullable、@NonNull 与平台类型 ⭐
标准回答
Kotlin 读 Java 时,JSR-305 / JetBrains @Nullable/@NonNull / Android @RecentlyNullable 等会影响推断为 T? 或 T;缺失则变平台类型。@Nonnull 参数在 Kotlin 传 null 可编译警告;Kotlin 向 Java 暴露 API 应用 @Nullable/@NotNull 标注,避免 Java 误用。
面试官可能继续追问
-
Android 里空安全注解互操作注意什么?
androidx 与 JetBrains 注解在 AGP 默认开启增强;对接 Gson/Retrofit 模型时,Java 字段注解决定 Kotlin 可空性,混用不当是混合工程 NPE 主因之一。 -
@NonNull和@NotNull一样吗?
工具链大多等价识别,项目应统一一套。
#13 internal 可见性能跨模块吗? 💡
标准回答
internal 在 同一 Gradle module 内可见,编译为 public 字节码(名称可能 mangling),其他 module 的 Java/Kotlin 不能直接访问源码级 internal API。跨模块靠模块边界隔离,不是 Java package-private;@PublishedApi internal 可特例暴露给公开 inline API。
面试官可能继续追问
-
Android 多 module 里 internal 怎么用?
Kotlininternal实现细节不会泄漏给:feature以外的模块;但反射或同一 dex 内仍可能看到类名,勿放敏感逻辑。 -
Java 能调 internal 吗?
同模块 Java 可以(编译后 public),异模块不行。
#14 @file:JvmName / @JvmName 解决哪些命名冲突? ⭐
标准回答
@file:JvmName(“FooUtil”):改顶层函数/属性生成的 Java 类名,默认 FileNameKt → FooUtil。@JvmName(“bar”):改单个函数/属性的 JVM 签名名,解决擦除后签名冲突或 Java 关键字问题。
面试官可能继续追问
-
Android 里 JvmName 典型场景?
utils.kt一堆扩展时,Java 侧用稳定DisplayUtils.dp()比DisplayUtilsKt.dp()更友好;与 Java 已有类名冲突时必须重命名。 -
改 JvmName 会影响反射吗?
会,反射要用 JVM 上的实际名。
#15 inline 函数对 Java 可见吗?reified 呢? 💡
标准回答
inline 函数在 Java 不能像 Kotlin 那样内联调用;若未 noinline 的参数,Java 甚至无法直接调用该 inline 函数(需非 inline 包装或拆非 inline API)。reified 依赖内联在调用点展开具体类型,Java 完全不可用。
面试官可能继续追问
-
Android 里 inline/reified 给 Java 暴露怎么办?
提供非 inline 重载或Class<T>入参版本;inline reified仅 Kotlin 内部使用,与 TypeToken 思路类似。 -
性能敏感 API 给 Java 怎么办?
提供普通函数 + 文档说明 Kotlin 侧用 inline 版本。
核心层(7 题)
#16 Kotlin 与 Java 集合类型如何映射?可变性陷阱在哪? 🔥
标准回答
List<T>(Kotlin)→ List<T>(Java 只读视图);MutableList<T> → java.util.List(可写)。Kotlin 只读 List 可能是 Java 可变 List 的包装,只读≠不可变。kotlin.collections 很多是 Java 集合的别名/包装;Arrays.asList 返回固定大小列表,Kotlin 当 List 用仍可能 UnsupportedOperationException。
面试官可能继续追问
-
Android API 边界集合怎么防踩坑?
Kotlin 模块返回List<T>给 Java,若内部是mutableListOf且引用外泄,Java 仍可add破坏不变性;对外应toList()防御性拷贝。 -
Set/Map同理吗?
是,MutableMap对应可写java.util.Map。
#17 从 Java 视角看 Kotlin out/in 与通配符? 🔥
标准回答
Kotlin out T(协变)编译为 Java ? extends T;in T(逆变)→ ? super T;声明处型变在 Java 签名上就是通配符。Java 调用 Kotlin List<out String> 只能读 String,不能 add String;Kotlin 调用 Java List<? extends Foo> 时类型是 List<out Foo> 或平台类型。
面试官可能继续追问
-
Android 里 out/in 互操作怎么用?
Repository 返回List<out UiModel>给 Java 展示层安全;若 Java 侧需要add,应用MutableList或 Kotlinin消费者类型,与 PECS 原则一致。 -
星投影
List<*>Java 签名?
类似List<?>或原始 List,读作未知元素。
#18 @JvmWildcard 和 @JvmSuppressWildcards 有何用? 💡
标准回答
@JvmWildcard:强制 Kotlin 类型参数在 Java 签名带通配符,如 List<String> → List<? extends String>,避免 Java RAW 赋值破坏协变。@JvmSuppressWildcards:抑制通配符,生成 Java 友好的具体类型签名,便于 Java 调用方少写 ? extends。
面试官可能继续追问
-
Android 里泛型 API 给 Java 暴露怎么调?
暴露给 Java 的泛型 API(缓存Map<K,V>、回调List<T>)若 Java 编译报错或警告过多,用二者微调签名;Kotlin 调用方仍按原泛型规则检查。 -
能标在类上还是函数上?
可用于类、函数、类型参数位置,粒度不同。
#19 data class 从 Java 侧如何构造、读取与 copy? ⭐
标准回答
data class 仍生成 getter(getName())、componentN()、copy 实例方法、equals/hashCode/toString;无 Java 可用的全参构造若属性有默认值且未 @JvmOverloads。Java new User(id, name) 需主构造所有参数在 Java 可见;copy 在 Java 要按参数顺序调用。
面试官可能继续追问
-
Android 里 data class 给 Java 用注意什么?
网络/DTO 模型给 Java 用时应@JvmOverloads构造、或提供 Builder;Java 用 getter 即可,少用component1()。 -
data class 能继承 Java 类吗?
可以继承类,但不能同时继承另一个 Kotlin 类(除 Any)。
#20 Java 如何调用 Kotlin suspend?Continuation 互操作要点 🔥
标准回答
suspend 函数编译为带 Continuation<? super T> 末参 的普通方法;Java 调用需传入 Continuation 实现,在 resumeWith 里拿结果或异常。kotlinc 状态机 + Continuation 回调;Java 不享受协程语法糖,应优先用库提供的 Callback 包装 或让 Kotlin 侧提供非 suspend 入口。
面试官可能继续追问
-
Android 里 Java 怎么调 Kotlin suspend?
Room、Retrofit suspend API 给 Java 用时常在 Kotlin 层包Callback/ListenableFuture;Java 直接调 suspend 仅适合框架集成,业务代码避免手写 Continuation。 -
Continuation 最后一个参数 Java 要手写吗?
是,且需正确CoroutineContext,工程上极少手写。
#21 混合模块、AGP 与 R8 如何保留 Kotlin metadata? 🔥
标准回答
R8 混淆会剥离 Kotlin Metadata(@kotlin.Metadata),导致反射、部分序列化、Moshi/Gson Kotlin 适配器、ViewBinding 泛型推断失效;需 -keep class kotlin.Metadata { *; } 及 @Keep 相关规则。Kotlin 可见性、默认参数、内联、属性名依赖 metadata;纯 Java 反射只看 .class 够用,混合库不够。
面试官可能继续追问
-
Android release 混淆 Kotlin 类挂了怎么办?
AGP 8 + R8 full mode 默认开启;release 崩溃若涉及反射/Parcelize,先查proguard-rules是否 keep 模型;库作者应发布consumer-proguard-rules.pro。 -
多 module 只 keep app 够吗?
各 library 应自带 consumer rules,避免 feature module 被错剥。
#22 大型 Kotlin+Java 混合项目互操作最佳实践? ⭐
标准回答
边界清晰:新代码 Kotlin 优先;Java 存量包门面化,对外 API 用 @JvmStatic/@JvmOverloads/@Nullable 标注。API 设计:给 Java 的 public API 避免 inline/reified、suspend 直出、internal 泄漏;集合返回防御性只读拷贝。构建与混淆:统一注解、各 module 发布 consumer ProGuard。
面试官可能继续追问
-
Android 大型混合项目怎么分层?
:coreKotlin 实现 +:apiJava 接口模块、Retrofit 接口放 Java、实现放 Kotlin 是常见分层;团队规范写清「谁调谁、哪些注解必选」。 -
Compose 项目还有 Java 互操作吗?
有,View 互操作、AndroidView、存量 Java Activity 调 Kotlin Composable 桥接。
面试策略速查
| 面试等级 | 建议掌握 |
|---|---|
| 初级 | ★★★☆☆ |
| 中级 | ★★★★☆ |
| 高级 | ★★★★★ |
必背知识(🔥)
平台类型、@JvmStatic/@JvmField/@JvmOverloads、Java 调 Kotlin 默认参数、SAM 双向、集合可变性映射、out/in 与 Java 通配符、suspend Continuation 桥接、R8 Kotlin metadata
高频追问
「Java 怎么调 companion」「默认参数为什么编译不过」「平台类型 NPE 怎么防」「MutableList 和 List 边界」「release 混淆后 Kotlin 反射挂了」
加分项(💡)
顶层扩展 Java 静态调用、internal 模块边界、inline/reified Java 不可用、@JvmWildcard/@JvmSuppressWildcards
容易踩坑
Kotlin API 未加 @JvmOverloads 导致 Java 编译失败;把 Kotlin List 当不可变;Java 调 suspend 手写 Continuation;R8 未 keep metadata;@JvmField 滥用破坏封装
完整链路一句通
互操作一条线:Kotlin 编译为 JVM 字节码后与 Java 共享类型系统,语法糖靠 @Jvm* 与注解暴露正确调用面;平台类型与集合可变性是 NPE 主因;suspend 以 Continuation 末参桥接、业务应 Kotlin 包装;R8 剥离 metadata 需 consumer rules,混合工程用模块边界 + 注解统一 + API 门面化保长期可维护。
相关推荐
更多推荐
所有评论(0)