文章目录
- 学习建议
- [基础层(8 题)](#基础层(8 题))
-
- [#1 Kotlin 空安全体系:`?`、`!!`、`?:` 怎么用? 🔥](#1 Kotlin 空安全体系:
?、!!、?:怎么用? 🔥) - [#2 `val` 和 `var` 有什么区别? 🔥](#2
val和var有什么区别? 🔥) - [#3 `data class` 解决了什么问题? 🔥](#3
data class解决了什么问题? 🔥) - [#4 `sealed class` 是什么?和 `enum` 怎么选? 🔥](#4
sealed class是什么?和enum怎么选? 🔥) - [#5 `when` 表达式和 Java `switch` 有什么不同? 🔥](#5
when表达式和 Javaswitch有什么不同? 🔥) - [#6 什么是智能转换(smart cast)? 🔥](#6 什么是智能转换(smart cast)? 🔥)
- [#7 扩展函数是什么?为什么不能改原类? 🔥](#7 扩展函数是什么?为什么不能改原类? 🔥)
- [#8 `lateinit` 和 `lazy` 的区别? ⭐](#8
lateinit和lazy的区别? ⭐)
- [#1 Kotlin 空安全体系:`?`、`!!`、`?:` 怎么用? 🔥](#1 Kotlin 空安全体系:
- [进阶层(7 题)](#进阶层(7 题))
-
- [#9 作用域函数 `let`/`run`/`apply`/`also`/`with` 怎么选? 🔥](#9 作用域函数
let/run/apply/also/with怎么选? 🔥) - [#10 `object` 和 `companion object` 的区别? ⭐](#10
object和companion object的区别? ⭐) - [#11 Kotlin 集合 `List`/`Map`/`Set` 与 Java 有何不同? ⭐](#11 Kotlin 集合
List/Map/Set与 Java 有何不同? ⭐) - [#12 Lambda 与高阶函数在 Android 中怎么用? ⭐](#12 Lambda 与高阶函数在 Android 中怎么用? ⭐)
- [#13 SAM 转换是什么? ⭐](#13 SAM 转换是什么? ⭐)
- [#14 解构声明怎么用? ⭐](#14 解构声明怎么用? ⭐)
- [#15 默认参数与命名参数怎么用? ⭐](#15 默认参数与命名参数怎么用? ⭐)
- [#9 作用域函数 `let`/`run`/`apply`/`also`/`with` 怎么选? 🔥](#9 作用域函数
- [核心层(7 题)](#核心层(7 题))
-
- [#16 属性委托 `by lazy` 的原理与用法? ⭐](#16 属性委托
by lazy的原理与用法? ⭐) - [#17 `by viewModels` 委托解决了什么? 🔥](#17
by viewModels委托解决了什么? 🔥) - [#18 Kotlin 与 Java 语法概览:现代 Android 为何首选 Kotlin? ⭐](#18 Kotlin 与 Java 语法概览:现代 Android 为何首选 Kotlin? ⭐)
- [#19 `typealias` 有什么用? 💡](#19
typealias有什么用? 💡) - [#20 可空类型常见 NPE 场景与常见变通写法? 💡](#20 可空类型常见 NPE 场景与常见变通写法? 💡)
- [#21 只读集合与可变集合怎么选? 💡](#21 只读集合与可变集合怎么选? 💡)
- [#22 `inline` 与 `reified` 一句话是什么? 💡](#22
inline与reified一句话是什么? 💡) - 面试策略速查
- 完整链路一句通
- 相关推荐
- [#16 属性委托 `by lazy` 的原理与用法? ⭐](#16 属性委托
学习建议
| 目标层级 | 建议 |
|---|---|
| 初级 | 掌握基础层 8 题:空安全、val/var、data class、when;工程联想看每题追问第 1 条 |
| 中级 | 通读全文;进阶层能讲清作用域函数选型、object/companion、Lambda/SAM、解构与默认参数 |
| 高级 | 核心层 by lazy/by viewModels、Kotlin vs Java 概览必答;能串联「空安全→类型建模→委托→Jetpack」工程链路 |
基础层(8 题)
#1 Kotlin 空安全体系:?、!!、?: 怎么用? 🔥
标准回答
Kotlin 默认非空,可空类型显式标 ?,编译期拦截大部分 NPE;?. 遇 null 短路,?: Elvis 给默认值,!! 强制非空但 null 仍抛 NPE。
面试官可能继续追问
-
Android 里空安全常见踩坑?
解析 Intent extra、网络 JSON 用
?:兜底;UI 层滥用!!是 NPE 高发区。 -
String?能直接赋给String吗?不能,需
?:、安全调用或let收窄后再用。

#2 val 和 var 有什么区别? 🔥
标准回答
val 只读引用不可重新赋值,var 可变引用可改指向;val 不保证对象内容不可变,只锁引用,编译期常量用 const val。
面试官可能继续追问
-
Android 里
val/var怎么选?ViewModel 对外状态、Repository 返回值用
val;Composeremember管理可变 UI 状态时常配var。 -
val list还能add吗?能,
val只禁止换引用,不禁止改集合内容。
#3 data class 解决了什么问题? 🔥
标准回答
自动生成 equals/hashCode/toString/copy/解构,替代 Java Bean 样板;主构造 val/var 参与生成,copy 支持不可变更新。
面试官可能继续追问
-
Android 里
data class用在哪?Room 实体、网络 DTO、
UiState建模;DiffUtil/LazyColumn依赖正确equals。 -
data class能继承类吗?不能继承普通类,可实现接口;继承用
sealed class。
#4 sealed class 是什么?和 enum 怎么选? 🔥
标准回答
sealed class 限制子类在同一文件/模块,配合 when 穷举分支,编译器校验完备性;子类可携带不同数据,全覆盖时可省略 else。
面试官可能继续追问
-
Android 里
sealed class典型用法?UI 状态
Loading/Success/Error,when(state)分支渲染,编译期检查漏分支。 -
sealed interface和sealed class区别?interface 可多实现、无状态;class 可有构造与字段,单继承。
#5 when 表达式和 Java switch 有什么不同? 🔥
标准回答
when 是表达式可返回值,支持类型判断、范围、多条件,无需 break;分支匹配后自动结束,常配合 is 触发智能转换。
面试官可能继续追问
-
Android 里
when常见场景?处理
sealed class的UiState、权限结果、网络错误码;Compose 按状态分支渲染。 -
when没有else会编译失败吗?作表达式且未穷尽所有情况时会报错;
sealed全覆盖可省略。
#6 什么是智能转换(smart cast)? 🔥
标准回答
编译器在判空或 is 检查后自动收窄类型,省去手动强转;局部 val 在检查点后类型已知,var 或可被并发改写时失效。
面试官可能继续追问
-
Android 里 smart cast 例子?
if (binding != null)后直接用binding.text;when (event) is ShowToast后访问event.message。 -
为什么
var不能 smart cast?另一线程可能改值,编译器无法保证检查与使用间类型不变。
#7 扩展函数是什么?为什么不能改原类? 🔥
标准回答
给已有类「追加」方法,编译为静态函数,第一个参数是接收者;不改原类字节码,按声明时静态类型解析,非多态。
面试官可能继续追问
-
Android 里扩展函数常见写法?
Context.showToast()、View可见性工具、Modifier链式扩展。 -
扩展函数能被子类覆盖吗?
不能,按声明时静态类型解析,非多态。
#8 lateinit 和 lazy 的区别? ⭐
标准回答
lateinit var 延迟赋值可变非空引用,未初始化访问抛异常;lazy 延迟初始化只读值,首次访问才算,默认 SYNCHRONIZED 线程安全。
面试官可能继续追问
-
Android 里
lateinit和lazy怎么用?lateinit用于onCreate注入的ViewBinding;lazy延迟创建昂贵对象,但勿持有Context泄漏。 -
lateinit能用于可空类型吗?不能,只适用于非空引用;可空场景用可空类型或
lazy。
进阶层(7 题)
#9 作用域函数 let/run/apply/also/with 怎么选? 🔥
标准回答
let :转结果,用 it;
run :配置并返回,用 this;
apply :配置返回自身;
also :副作用返回自身;
with :非扩展版 run。
面试官可能继续追问
-
Android 里作用域函数怎么选?
可空
user?.let { bind(it) };构建Bundle/Request用apply;调试旁路用also。 -
链式里
apply和also混用会乱吗?会,团队约定一种风格;配置对象用
apply,调试打印用also。
#10 object 和 companion object 的区别? ⭐
标准回答
object 是单例或匿名对象,编译为单例类;companion object 是类的伴生成员容器,成员可通过类名直接访问,类似 Java static。
面试官可能继续追问
-
Android 里
object/companion用在哪?object做无状态工具单例;companion object放const、newInstance()工厂方法。 -
object和companion object能同名吗?companion object可命名,便于 Java 侧Companion访问;一般保持默认即可。
#11 Kotlin 集合 List/Map/Set 与 Java 有何不同? ⭐
标准回答
Kotlin 区分只读与可变:List 只读、MutableList 可变;只读是协变视图,运行时仍可能是 ArrayList,listOf 只读、mutableListOf 可变。
面试官可能继续追问
-
Android 里只读/可变集合怎么用?
Repository 对外返回
List<T>;内部mutableListOf维护,发布前toList()再暴露。 -
只读
List真能防修改吗?不能绝对防,底层可变实现仍可被强转修改;约定+
toList()拷贝更安全。
#12 Lambda 与高阶函数在 Android 中怎么用? ⭐
标准回答
Lambda 是匿名函数字面量,高阶函数把函数当参数或返回值;非 inline lambda 可能生成对象,尾随 lambda 让 DSL 更简洁。
面试官可能继续追问
-
Android 里 Lambda 常见场景?
RecyclerView/Compose点击回调、filter/map处理列表数据。 -
Lambda 捕获外部变量有什么限制?
须
val或 effectively final,与 Java 匿名类规则一致。
#13 SAM 转换是什么? ⭐
标准回答
Kotlin 调用 Java 单抽象方法接口时,lambda 可自动转为 SAM;编译器生成适配器实现唯一抽象方法,要求 @FunctionalInterface 或 Kotlin fun interface。
面试官可能继续追问
-
Android 里 SAM 转换例子?
View.setOnClickListener { }、Runnable { };自定义fun interface回调。 -
Kotlin 定义的
fun interface优势?明确 SAM 语义,lambda 与 Java 互操作更稳定,Retrofit 回调常用。
#14 解构声明怎么用? ⭐
标准回答
componentN() 支持一对多拆包,如 val (a, b) = pair;data class 自动生成 component1..N,自定义类可实现 operator fun component1()。常见写法:for ((key, value) in map)、对 data class 一次拆多字段。
面试官可能继续追问
-
解构在 Android 里常见吗?
for ((key, value) in map)遍历;对data class网络响应一次拆多字段。 -
解构
data class会创建中间对象吗?编译为
componentN()调用,无额外包装对象。
#15 默认参数与命名参数怎么用? ⭐
标准回答
参数可设默认值,调用时可按名传参,减少重载;编译器为缺省参数生成合成重载,Java 调用侧需 @JvmOverloads。
面试官可能继续追问
-
Android 里默认参数怎么用?
自定义 View/Composable 多可选配置;测试数据
User(isVip = true)可读性更好。 -
默认参数值每次调用都重新计算吗?
是,每次调用求值;昂贵默认应用
lazy或工厂方法。
核心层(7 题)
#16 属性委托 by lazy 的原理与用法? ⭐
标准回答
by lazy 把属性 getter 委托给 Lazy 实例,首次访问才算值;LazyThreadSafetyMode 控制同步,默认 SYNCHRONIZED。
面试官可能继续追问
-
Android 里
by lazy注意什么?延迟创建
Gson等昂贵对象可以;Activity 中lazy持有Context易泄漏。 -
lazy和lateinit选型?只读且昂贵用
lazy;var且稍后赋值用lateinit。
#17 by viewModels 委托解决了什么? 🔥
标准回答
Activity/Fragment 用属性委托自动获取/缓存 ViewModel,内部通过 ViewModelStore + Factory 懒获取,旋转不丢状态。
面试官可能继续追问
-
by viewModels在 Android 里怎么用?private val vm: MainViewModel by viewModels(),旋转屏幕不丢状态;多 Fragment 共享用activityViewModels()。 -
和
by lazy { ViewModelProvider(...) }比?viewModels官方封装,自动处理Factory与 store 键,更少样板代码。
#18 Kotlin 与 Java 语法概览:现代 Android 为何首选 Kotlin? ⭐
标准回答
Kotlin 与 Java 100% JVM 互操作,同样编译为字节码;语法更简洁,有空安全、扩展函数、数据类、密封类等语法糖,协程(K2)是异步核心卖点。
面试官可能继续追问
-
为什么现代 Android 首选 Kotlin?
Google 官方推荐,Jetpack/Compose 与 KTX 均 Kotlin 优先;空安全与协程降低 NPE 与异步样板代码。
-
Kotlin 比 Java 慢吗?
运行时同为 JVM 字节码,性能同级;差异在开发效率,非执行速度。
#19 typealias 有什么用? 💡
标准回答
为已有类型起别名,编译期替换、运行时等价于原类型,可简化长泛型签名。例如 typealias NetworkMap = Map<String, List<Dto>>、回调类型 typealias ClickListener = (View) -> Unit,减少嵌套括号、提升接口可读性。
面试官可能继续追问
-
typealias在 Android 工程里有用吗?回调
typealias ClickListener = (View) -> Unit;长泛型Map<String, List<Dto>>起别名提升可读性。 -
typealias和import重命名区别?import只改引用名;typealias可在模块内定义新类型名供多处复用。
#20 可空类型常见 NPE 场景与常见变通写法? 💡
标准回答
Kotlin 侧 NPE 常见来源:!! 滥用、未初始化的 lateinit、lazy 递归初始化;Fragment 生命周期结束后仍访问视图也会崩。
面试官可能继续追问
-
Android 里 Kotlin NPE 常见场景?
!!滥用、未初始化lateinit、Fragmentview销毁后仍访问。 -
Elvis 右侧能抛异常吗?
可以,
?: throw IllegalStateException()用于不变量校验。
#21 只读集合与可变集合怎么选? 💡
标准回答
对外暴露只读 List<T>/Map<K,V>,内部用 MutableList 维护,发布前 toList()/toMap() 防御性拷贝,防止调用方意外修改。
面试官可能继续追问
-
Android 里只读集合最佳实践?
Repository 返回
List<Item>给 UI;内部可变、对外toList()发布。 -
Collections.unmodifiableList还要用吗?Kotlin 侧优先
List+toList();与 Java 互操作时可保留。
#22 inline 与 reified 一句话是什么? 💡
标准回答
inline 把函数体内联到调用处,减少 lambda 对象;reified T 仅用于 inline 函数,编译期展开,运行时可用 T::class。
面试官可能继续追问
-
Android 里
inline/reified用在哪?viewModels<T>()、startActivity<T>()等 Jetpack 扩展依赖编译期展开类型。 -
所有函数都该
inline吗?不应,只 inline 小函数+高阶参数;大函数内联会膨胀字节码。
面试策略速查
| 面试等级 | 建议掌握 |
|---|---|
| 初级 | ★★★☆☆ |
| 中级 | ★★★★☆ |
| 高级 | ★★★★★ |
必背知识(🔥)
空安全 ?/?:/!!;val/var;data class/sealed class;when+smart cast;扩展函数;作用域函数选型;SAM 转换;by viewModels
高频追问
「lateinit 和 lazy 区别」「let 和 apply 怎么选」「sealed class 和 enum」「为什么 Kotlin 能减少 NPE」「data class 的 copy 用途」
加分项(💡)
typealias;只读/可变集合防御性拷贝;可空链常见变通写法;inline/reified 概念(深度留 K4)
容易踩坑
滥用 !!;lateinit 未赋值就访问;只读 List 当绝对不可变;作用域函数嵌套过深可读性崩;lazy 持有 Activity Context 泄漏
完整链路一句通
Kotlin 语法一条线 :默认非空+可空类型消灭 NPE,data/sealed 建模 UI 状态,when+smart cast 穷举分支,扩展函数与作用域函数减少样板,属性委托 lazy/viewModels 对接 Jetpack 生命周期------语法扎实后再上协程(K2)。