面试复习题-kotlin
一、基础语法与特性
1. val
和 var
的区别?
val
:声明不可变变量(只读),类似 Java 的final
。var
:声明可变变量。- 推荐优先使用
val
,提高代码安全性。
2. lateinit
和 by lazy
的区别?
特性 | lateinit | by lazy |
---|---|---|
类型 | 只能用于 var ,且不能是基本类型 | 只能用于 val |
初始化 | 手动赋值(延迟到运行时) | 第一次访问时初始化 |
线程安全 | 否(需手动保证) | 是(默认 LazyThreadSafetyMode.SYNCHRONIZED ) |
使用场景 | 依赖注入、Android onCreate() 中初始化 | 单例、耗时对象初始化 |
kotlin
深色版本
class MyClass {lateinit var name: String // 必须在使用前初始化val data by lazy { expensiveOperation() } // 第一次访问时计算
}
3. ==
和 ===
的区别?
==
:结构相等(调用equals()
)。===
:引用相等(内存地址相同)。- 对于基本类型,
===
会进行值比较(装箱优化)。
4. data class
有什么作用?自动生成哪些方法?
- 用于数据载体类,自动提供:
equals()
/hashCode()
toString()
(格式:User(name=John, age=30)
)copy()
(复制并可修改部分属性)componentN()
(解构支持)
- 要求主构造函数至少有一个参数。
kotlin
深色版本
data class User(val name: String, val age: Int)
5. sealed class
(密封类)的作用?
- 表示受限的类继承结构,所有子类必须在同一个文件中定义。
- 在
when
表达式中可以穷尽检查,无需else
分支。
kotlin
深色版本
sealed class Result {data class Success(val data: String) : Result()data class Error(val exception: Exception) : Result()
}fun handle(result: Result) = when(result) {is Success -> println(result.data)is Error -> println(result.exception)// 不需要 else,编译器知道已覆盖所有情况
}
二、空安全(Null Safety)
6. Kotlin 如何解决空指针异常(NPE)?
- 类型系统区分可空类型(
String?
)和非空类型(String
)。 - 非空类型不能赋值为
null
。 - 可空类型必须进行空检查才能使用。
7. 安全调用(?.
)、Elvis 操作符(?:
)、非空断言(!!
)的区别?
kotlin
深色版本
val name: String? = getName()// 安全调用:如果 name 不为 null,调用 toUpperCase()
val upper = name?.toUpperCase()// Elvis 操作符:提供默认值
val result = name ?: "Unknown"// 非空断言:强制认为不为 null,可能抛出 NPE
val mustHave = name!!.length
8. 如何安全地进行类型转换?
- 使用
as?
(安全转换):
kotlin
深色版本
val str: String? = obj as? String
三、函数与高阶函数
9. 什么是高阶函数?举例说明。
- 函数可以作为参数或返回值。
kotlin
深色版本
fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {return operation(a, b)
}val result = operate(2, 3) { x, y -> x + y }
10. inline
函数的作用?
- 内联:编译时将函数体插入调用处,避免创建匿名类和对象(提升性能)。
- 常用于高阶函数(如
let
、apply
、run
)。 - 使用
noinline
可阻止部分参数内联,crossinline
限制return
。
11. 常用作用域函数(let
、run
、with
、apply
、also
)的区别?
函数 | 上下文对象 | 返回值 | 适用场景 |
---|---|---|---|
let | it | Lambda 结果 | 空安全调用、局部变量 |
run | this | Lambda 结果 | 多个操作、初始化 |
with | this | Lambda 结果 | 调用对象多个方法 |
apply | this | 对象自身 | 对象配置(Builder 模式) |
also | it | 对象自身 | 副作用(如日志) |
kotlin
深色版本
val user = User().apply { name = "John"; age = 30 } // 配置.also { println("Created: $it") } // 副作用
四、协程(Coroutines)
12. 什么是协程?与线程的区别?
- 协程:轻量级线程,由用户态调度,挂起不阻塞线程。
- 线程:操作系统调度,阻塞会占用线程资源。
- 协程开销小,可创建成千上万个。
13. launch
和 async
的区别?
launch
:启动协程,返回Job
,用于不返回结果的场景。async
:启动协程,返回Deferred<T>
,用于需要返回结果的场景,需调用.await()
。
kotlin
深色版本
// launch:只关心执行
scope.launch { fetchData() }// async:需要结果
val deferred = scope.async { fetchData() }
val result = deferred.await()
14. 协程的上下文(CoroutineContext
)包含哪些元素?
Job
:协程的生命周期。Dispatcher
:指定运行线程(如Dispatchers.IO
、Main
)。CoroutineExceptionHandler
:处理未捕获异常。CoroutineName
:协程名称(调试用)。
15. viewModelScope
和 lifecycleScope
的作用?
viewModelScope
:在ViewModel
中使用,ViewModel
销毁时自动取消协程。lifecycleScope
:在Activity
/Fragment
中使用,生命周期结束时取消。- 都属于 AndroidX Lifecycle 库,避免内存泄漏。
五、面向对象与继承
16. Kotlin 类默认是 final
的,如何允许继承?
- 使用
open
关键字:
kotlin
深色版本
open class Animal // 允许继承
class Dog : Animal() // 继承
17. object
关键字有哪些用法?
- 单例模式:kotlin
深色版本
object NetworkManager { fun request() { ... } }
- 伴生对象(Companion Object):kotlin
深色版本
class MyClass {companion object { const val TAG = "MyClass" } }
- 对象表达式(匿名内部类):kotlin
深色版本
val listener = object : OnClickListener { ... }
六、集合与函数式编程
18. map
、flatMap
、filter
、reduce
的作用?
kotlin
深色版本
val list = listOf(1, 2, 3)list.map { it * 2 } // [2, 4, 6]
list.filter { it > 1 } // [2, 3]
list.reduce { acc, i -> acc + i } // 6val nested = listOf(listOf(1,2), listOf(3,4))
nested.flatMap { it } // [1, 2, 3, 4] (展平)
19. MutableList
和 List
的区别?
List
:只读接口(不能添加/删除)。MutableList
:可变接口。
七、与 Java 互操作
20. Kotlin 如何调用 Java 代码?有哪些注意事项?
- 大部分无缝互操作。
- 注意:
- Java 的
null
在 Kotlin 中是可空类型。 - Java 集合在 Kotlin 中可能是可变的。
- 使用
@JvmOverloads
、@JvmStatic
、@JvmField
控制 JVM 字节码生成。
- Java 的
21. @JvmOverloads
的作用?
- 让 Kotlin 函数支持默认参数,在 Java 中生成多个重载方法。
kotlin
深色版本
@JvmOverloads
fun greet(name: String = "World", age: Int = 0) { ... }
Java 中可调用:greet()
, greet("John")
, greet("John", 25)
。
八、进阶问题
22. tailrec
关键字的作用?
- 标记尾递归函数,编译器将其优化为循环,避免栈溢出。
kotlin
深色版本
tailrec fun factorial(n: Int, acc: Int = 1): Int =if (n <= 1) acc else factorial(n - 1, n * acc)
23. 协程取消是协作式的,如何检查取消?
- 使用
ensureActive()
或检查isActive
:
kotlin
深色版本
while (isActive) {// 执行任务
}
24. Channel
和 Flow
的区别?
Channel
:冷流,生产者-消费者队列,支持挂起。Flow
:热流,冷数据流,用于异步数据序列,支持背压。
总结
Kotlin 面试重点:
- 空安全:
?
、!!
、?:
、?.
- 协程:
launch
/async
、scope
、异常处理 - 函数式编程:高阶函数、
let
/apply
等作用域函数 - 语法糖:
data class
、sealed class
、object
- Android 特有:
viewModelScope
、lifecycleScope
建议结合实际项目经验,解释这些特性如何提升代码质量、可读性和安全性
一、深入理解 Kotlin 编译与字节码
25. const val
和 val
的区别?什么情况下必须用 const
?
val
:运行时赋值(可以是函数调用结果)。const val
:编译期常量,必须是基本类型或String
,且值在编译时确定。const
可用于注解、when
分支、默认参数。
kotlin
深色版本
const val TAG = "MyActivity" // ✅ 可用于注解
val version = getVersion() // ❌ 不能用于注解@SomeAnnotation(TAG) // 只有 const val 可以
fun log(message: String = TAG) { ... } // 默认参数也需 const
26. @JvmStatic
和 @JvmField
的作用?
@JvmStatic
:在伴生对象中,生成 静态方法(而非静态内部类的实例方法)。@JvmField
:将属性暴露为 public 字段,不生成 getter/setter。
kotlin
深色版本
class Utils {companion object {@JvmStatic fun doWork() { ... } // Java 中可直接 Utils.doWork()@JvmField val NAME = "Utils" // Java 中可直接 Utils.NAME}
}
27. Kotlin 的默认参数是如何在字节码中实现的?
- 编译器会生成多个重载方法(除非加
@JvmOverloads
)。 - 调用时,未传参的位置由编译器插入默认值。
- 在 Java 中调用带默认参数的 Kotlin 函数,必须使用
@JvmOverloads
才能生成重载。
二、协程进阶与实战
28. 协程取消后,如何确保资源正确释放(如文件、网络连接)?
- 使用
try { ... } finally { ... }
或use
语句。 finally
块中的代码总是执行,即使协程被取消。
kotlin
深色版本
scope.launch {val file = openFile()try {while (isActive) {// 读取文件}} finally {file.close() // 即使被 cancel(),也会执行}
}
29. withContext(Dispatcher.IO)
和 launch(Dispatcher.IO)
的区别?
withContext
:挂起函数,切换上下文并返回结果,常用于函数内部。launch
:启动新协程,不阻塞当前协程,通常用于“开火即忘”(fire and forget)。
kotlin
深色版本
// 推荐:在 suspend 函数中使用 withContext
suspend fun fetchData(): String {return withContext(Dispatcher.IO) {// 耗时操作}
}// launch 用于启动独立任务
scope.launch { fetchData() }
30. 什么是“协程泄露”(Coroutine Leak)?如何避免?
- 定义:协程启动后未被正确取消或等待,导致资源浪费或内存泄漏。
- 场景:
- 在
ViewModel
中启动launch
但未绑定viewModelScope
。 async
启动后未调用await()
或cancel()
。
- 在
- 避免:
- 使用结构化并发(
CoroutineScope
)。 - 优先使用
viewModelScope
/lifecycleScope
。 - 对
async
的结果必须处理。
- 使用结构化并发(
三、泛型与类型系统
31. in
和 out
关键字的作用?(型变)
out T
(协变):T
只作为返回值(生产者),List<Cat>
是List<Animal>
的子类型。in T
(逆变):T
只作为参数(消费者),Comparator<Animal>
是Comparator<Cat>
的子类型。
kotlin
深色版本
interface Producer<out T> {fun get(): T // T 是输出
}interface Consumer<in T> {fun consume(item: T) // T 是输入
}
32. reified
类型参数的作用?
- 允许在内联函数中使用
T::class
或is T
,因为类型在编译时已知。
kotlin
深色版本
inline fun <reified T> getInstance(): T {return when (T::class) {String::class -> "Hello" as TInt::class -> 42 as Telse -> throw IllegalArgumentException()}
}
四、DSL(领域特定语言)
33. Kotlin 如何构建 DSL?@DslMarker
的作用?
- 使用 lambda with receiver 和 高阶函数 构建 DSL。
@DslMarker
确保 DSL 的层级结构,防止跨层级调用。
kotlin
深色版本
@DslMarker
annotation class HtmlTagMarker@HtmlTagMarker
class Tag(val name: String) {fun render() = "<$name>$content</$name>"private var content = ""fun content(text: String) { content += text }
}fun tag(name: String, init: Tag.() -> Unit): Tag {val tag = Tag(name)tag.init() // 在 Tag 的上下文中执行return tag
}// 使用 DSL
val html = tag("div") {content("Hello")// 无法调用其他 Tag 的方法,因为 @DslMarker 限制了作用域
}
这是 Kotlin 构建类型安全 HTML、Gradle 脚本、Ktor 路由等的基础。
五、性能与最佳实践
34. Sequence
和 List
的操作有何不同?何时使用 Sequence
?
List
操作:立即执行,每步生成新集合(中间集合开销大)。Sequence
操作:惰性求值,类似 Java Stream,只有在toList()
或forEach()
时才执行。
kotlin
深色版本
// List:三次遍历,生成两个中间列表
list.filter { it > 0 }.map { it * 2 }.first { it > 10 }// Sequence:一次遍历,无中间集合
sequence.filter { it > 0 }.map { it * 2 }.first { it > 10 }
- 使用场景:大数据集、复杂链式操作、早期终止(如
first
)。
35. 如何避免 Kotlin 的“过度语法糖”导致代码可读性下降?
- 原则:
- 不滥用
apply
/also
嵌套。 - 复杂逻辑避免单行 lambda。
- 团队统一编码规范。
- 优先选择清晰而非“聪明”的代码。
- 不滥用
六、Android 与 Kotlin 特有
36. by viewModels()
和 by activityViewModels()
的原理?
- 基于 委托属性(
Delegates
)和ViewModelProvider
。 by
调用getValue()
,内部通过ViewModelProvider
获取ViewModel
。- 自动绑定生命周期。
37. Kotlin 的 when
表达式比 Java switch
强在哪里?
- 支持任意类型(对象、字符串、枚举)。
- 支持范围(
in 1..10
)、类型检查(is String
)、条件(-> value > 0
)。 - 可作为表达式返回值。
kotlin
深色版本
val result = when (x) {in 1..10 -> "Small"is String -> "It's a string"else -> "Unknown"
}
七、开放性问题(考察设计能力)
38. 如何用 Kotlin 实现一个简单的依赖注入(DI)容器?
- 使用
object
存储单例。 - 使用高阶函数注册和获取实例。
- 利用委托属性延迟初始化。
39. 如何设计一个线程安全的缓存(Cache)?
- 使用
ConcurrentHashMap
。 - 结合
@Volatile
和双重检查锁定(Double-Checked Locking)。 - 或使用
synchronized
块。
40. Kotlin 多平台(KMP)的了解?
- 一套代码,多平台共享(Android、iOS、Web、Desktop)。
- 共享业务逻辑、网络、数据层。
- 平台特定代码通过
expect
/actual
声明。
总结:Kotlin 面试准备策略
层次 | 考察点 | 准备建议 |
---|---|---|
基础 | val/var 、空安全、函数 | 熟练语法,能手写 |
核心 | 协程、作用域函数、集合 | 理解原理,能解释区别 |
进阶 | 泛型、DSL、字节码 | 了解编译机制,有实战经验 |
实战 | 内存泄漏、性能优化、架构 | 结合项目讲清楚“为什么” |
最后建议:
- 准备 1-2 个 Kotlin 特性优化项目代码 的例子(如用
sealed class
替代枚举,用协程替代回调)。 - 了解 Kotlin 1.9+ 的新特性(如
K2
编译器、required
关键字等)。
Kotlin 不仅是语法糖,更是一种设计哲学。面试官希望看到你如何用 Kotlin 写出更安全、更简洁、更易维护的代码