這里整理了Kotlin面試中最高頻的題目,從基礎(chǔ)到進(jìn)階,附帶詳細(xì)答案和代碼示例。
?? 一、基礎(chǔ)篇
1. Kotlin和Java有什么區(qū)別?
答案要點(diǎn):
- 空安全:Kotlin在編譯時(shí)就處理了空指針問題
- 擴(kuò)展函數(shù):Kotlin可以給現(xiàn)有類添加新方法
- 數(shù)據(jù)類:一行代碼生成POJO類,自動(dòng)生成getter/setter/toString等
- 協(xié)程:輕量級(jí)線程,簡(jiǎn)化異步編程
- 函數(shù)式編程:支持Lambda、高階函數(shù)
- 類型推斷:可以自動(dòng)推斷變量類型
- 智能類型轉(zhuǎn)換:類型檢查后自動(dòng)轉(zhuǎn)換
// Java
if (obj instanceof String) {
String str = (String) obj;
}
// Kotlin - 智能轉(zhuǎn)換
if (obj is String) {
print(obj.length) // 自動(dòng)轉(zhuǎn)換為String
}
2. Kotlin中val和var的區(qū)別?
答案:
- val:不可變引用,值初始化后不能改變(類似Java的final)
- var:可變引用,值可以改變
val name = "張三"
// name = "李四" // 編譯錯(cuò)誤
var age = 25
age = 26 // 可以修改
注意:val修飾的對(duì)象內(nèi)部狀態(tài)可以改變
val list = mutableListOf(1, 2, 3)
list.add(4) // 可以,list指向的對(duì)象沒變
3. Kotlin中的可空類型和非空類型
答案:
Kotlin通過類型系統(tǒng)消除空指針異常,類型后面加?表示可以為null。
var nonNull: String = "不能為null"
// nonNull = null // 編譯錯(cuò)誤
var nullable: String? = "可以為null"
nullable = null // 允許
// 安全調(diào)用
val length = nullable?.length // 如果nullable為null,結(jié)果為null
// Elvis操作符
val len = nullable?.length ?: 0 // 如果為null返回0
// 非空斷言(謹(jǐn)慎使用)
val mustLen = nullable!!.length // 如果為null拋出NPE
4. Kotlin中的==和===有什么區(qū)別?
答案:
- ==:比較結(jié)構(gòu)相等性(equals方法),類似Java的equals
- ===:比較引用相等性,類似Java的==
val a = "Hello"
val b = String(charArrayOf('H', 'e', 'l', 'l', 'o'))
println(a == b) // true,內(nèi)容相等
println(a === b) // false,不同對(duì)象
5. Kotlin中的lateinit和lazy有什么區(qū)別?
答案:
| 特性 | lateinit | lazy |
|---|---|---|
| 適用對(duì)象 | var屬性(可變) | val屬性(不可變) |
| 初始化時(shí)機(jī) | 手動(dòng)指定 | 第一次訪問時(shí)自動(dòng)初始化 |
| 線程安全 | 不安全 | 默認(rèn)線程安全 |
| 適用類型 | 非空類型 | 任何類型 |
| 能否多次賦值 | 能 | 不能 |
// lateinit - 用于依賴注入、單元測(cè)試等
lateinit var view: TextView
// 后續(xù)必須初始化
view = findViewById(R.id.text_view)
// lazy - 用于耗時(shí)初始化
val config: String by lazy {
println("第一次訪問時(shí)初始化")
loadConfig() // 耗時(shí)操作
}
?? 二、中級(jí)篇
6. Kotlin中的data class是什么?有什么特點(diǎn)?
答案:
數(shù)據(jù)類專門用于持有數(shù)據(jù)的類,用data關(guān)鍵字修飾。
自動(dòng)生成的方法:
-
equals()/hashCode() -
toString()格式如 "User(name=張三, age=25)" -
copy()函數(shù) - 解構(gòu)函數(shù)
componentN()
data class User(val name: String, val age: Int)
val user1 = User("張三", 25)
val user2 = user1.copy(age = 26) // 復(fù)制并修改年齡
println(user1) // 輸出: User(name=張三, age=25)
// 解構(gòu)
val (name, age) = user1
println("$name $age")
要求:
- 主構(gòu)造函數(shù)至少有一個(gè)參數(shù)
- 主構(gòu)造函數(shù)參數(shù)必須用val/var標(biāo)記
- 不能是抽象、開放、密封或內(nèi)部類
7. Kotlin中的伴生對(duì)象是什么?
答案:
伴生對(duì)象(companion object)是Kotlin中實(shí)現(xiàn)靜態(tài)成員的替代方案。
class MyClass {
companion object {
const val TAG = "MyClass"
fun create(): MyClass = MyClass()
}
}
// 調(diào)用方式
MyClass.TAG
MyClass.create()
特點(diǎn):
- 一個(gè)類只能有一個(gè)伴生對(duì)象
- 伴生對(duì)象的名字可以省略(默認(rèn)名為Companion)
- 編譯后會(huì)變成真正的靜態(tài)成員(使用@JvmStatic注解)
- 伴生對(duì)象可以實(shí)現(xiàn)接口
8. Kotlin中的擴(kuò)展函數(shù)是什么?原理是什么?
答案:
擴(kuò)展函數(shù)可以在不修改原類的情況下給類添加新方法。
// 給String類添加擴(kuò)展函數(shù)
fun String.addExclamation(): String {
return this + "!"
}
// 給Context添加擴(kuò)展
fun Context.showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// 使用
println("Hello".addExclamation()) // "Hello!"
showToast("操作成功")
原理:
- 擴(kuò)展函數(shù)是靜態(tài)解析的,不是真正修改了原類
- 編譯后會(huì)生成靜態(tài)方法,第一個(gè)參數(shù)是接收者類型
- 不能訪問私有成員
- 如果成員函數(shù)和擴(kuò)展函數(shù)同名,成員函數(shù)優(yōu)先級(jí)高
9. Kotlin中的高階函數(shù)和Lambda
答案:
高階函數(shù)是指參數(shù)或返回值是函數(shù)的函數(shù)。
// 高階函數(shù)示例
fun <T> List<T>.customFilter(predicate: (T) -> Boolean): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) {
result.add(item)
}
}
return result
}
// Lambda表達(dá)式
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.customFilter { it % 2 == 0 }
// 常用高階函數(shù)
list.filter { it > 3 }
.map { it * 2 }
.sorted()
.take(3)
常見高階函數(shù):
-
filter:過濾 -
map:映射轉(zhuǎn)換 -
flatMap:扁平化映射 -
fold/reduce:折疊 -
forEach:遍歷 -
groupBy:分組
10. Kotlin中的sealed class有什么用?
答案:
密封類(sealed class)用于表示受限的類層次結(jié)構(gòu),是一種特殊的抽象類。
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
fun handleResult(result: Result) = when (result) {
is Result.Success -> println("成功: ${result.data}")
is Result.Error -> println("錯(cuò)誤: ${result.message}")
Result.Loading -> println("加載中")
// 不需要else分支,編譯器知道所有情況
}
特點(diǎn):
- 所有子類必須在同一文件中聲明
- when表達(dá)式處理時(shí)不需要else分支
- 常用于表示狀態(tài)、錯(cuò)誤處理等場(chǎng)景
- Kotlin 1.5后支持密封接口(sealed interface)
?? 三、高級(jí)篇
11. Kotlin協(xié)程和線程的區(qū)別?
答案:
| 特性 | 協(xié)程 | 線程 |
|---|---|---|
| 調(diào)度 | 用戶態(tài)調(diào)度,由編譯器支持 | 內(nèi)核態(tài)調(diào)度,由操作系統(tǒng)管理 |
| 資源消耗 | 輕量級(jí),百萬(wàn)級(jí)沒問題 | 重量級(jí),幾千個(gè)就吃力 |
| 切換成本 | 極低 | 高(上下文切換) |
| 代碼風(fēng)格 | 順序式編寫異步代碼 | 回調(diào)或復(fù)雜同步 |
| 取消 | 支持結(jié)構(gòu)化取消 | 較難安全取消 |
// 協(xié)程示例
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
fetchFromNetwork() // 自動(dòng)切線程
}
textView.text = data // 自動(dòng)切回主線程
}
核心概念:
- 掛起函數(shù):suspend關(guān)鍵字,不會(huì)阻塞線程
- 調(diào)度器:Dispatchers.Main/IO/Default
- 作用域:viewModelScope、lifecycleScope
- 結(jié)構(gòu)化并發(fā):取消父協(xié)程會(huì)自動(dòng)取消所有子協(xié)程
12. Kotlin中的掛起函數(shù)是如何工作的?
答案:
掛起函數(shù)是Kotlin協(xié)程的核心,它在不阻塞線程的情況下暫停執(zhí)行。
原理:
- 編譯器將掛起函數(shù)轉(zhuǎn)換為狀態(tài)機(jī)
- 每個(gè)掛起點(diǎn)是一個(gè)狀態(tài)
- 通過Continuation傳遞控制權(quán)
// 看似簡(jiǎn)單的掛起函數(shù)
suspend fun fetchData(): String {
val data1 = fetchFromNetwork() // 掛起點(diǎn)1
val data2 = processData(data1) // 掛起點(diǎn)2
return data2
}
// 編譯后類似(簡(jiǎn)化版)
fun fetchData(continuation: Continuation): Any {
// 狀態(tài)機(jī)實(shí)現(xiàn)
when (continuation.label) {
0 -> {
// 第一次調(diào)用
continuation.label = 1
fetchFromNetwork(continuation)
return COROUTINE_SUSPENDED
}
1 -> {
// 從掛起點(diǎn)1恢復(fù)
val data1 = continuation.result
continuation.label = 2
processData(data1, continuation)
return COROUTINE_SUSPENDED
}
2 -> {
// 從掛起點(diǎn)2恢復(fù)
val data2 = continuation.result
return data2
}
else -> throw IllegalStateException()
}
}
13. Kotlin Flow和LiveData的區(qū)別?
答案:
| 特性 | Flow | LiveData |
|---|---|---|
| 設(shè)計(jì)目標(biāo) | 響應(yīng)式流處理 | UI狀態(tài)持有 |
| 線程切換 | 內(nèi)置flowOn/launchIn | 需手動(dòng)處理 |
| 操作符 | 豐富(map、filter等) | 較少 |
| 背壓 | 支持 | 不支持 |
| 生命周期感知 | 需配合repeatOnLifecycle | 自動(dòng)感知 |
| 數(shù)據(jù)發(fā)射 | 冷流 | 熱 |
// Flow - 適合復(fù)雜數(shù)據(jù)流
fun getUsers(): Flow<List<User>> = flow {
val users = database.getUsers() // 數(shù)據(jù)庫(kù)查詢
emit(users)
}.flowOn(Dispatchers.IO) // 指定上游線程
// 收集Flow
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.users.collect { users ->
// 更新UI
}
}
}
// LiveData - 適合簡(jiǎn)單UI狀態(tài)
val userLiveData: LiveData<User> = liveData {
emit(loadUser())
}
14. Kotlin中的inline、noinline、crossinline有什么區(qū)別?
答案:
inline:
- 內(nèi)聯(lián)函數(shù),編譯時(shí)將函數(shù)體復(fù)制到調(diào)用處
- 減少Lambda對(duì)象創(chuàng)建,提高性能
- 可以return外層函數(shù)
inline fun inlineTest(block: () -> Unit) {
block()
println("內(nèi)聯(lián)函數(shù)")
}
fun main() {
inlineTest {
println("Lambda")
return // 允許,會(huì)退出main函數(shù)
}
}
noinline:
- 用于不想內(nèi)聯(lián)的參數(shù)
- 當(dāng)有多個(gè)Lambda參數(shù),只想內(nèi)聯(lián)部分時(shí)使用
inline fun test(
inlined: () -> Unit,
noinline notInlined: () -> Unit // 這個(gè)參數(shù)不內(nèi)聯(lián)
) {
// ...
}
crossinline:
- 禁止Lambda中的非局部返回
- 用于Lambda在另一個(gè)執(zhí)行上下文中執(zhí)行的場(chǎng)景
inline fun crossinlineTest(crossinline block: () -> Unit) {
Runnable {
block() // 在另一個(gè)線程執(zhí)行
// block中的return不允許,否則會(huì)破壞調(diào)用棧
}.run()
}
15. Kotlin中的委托屬性是怎么實(shí)現(xiàn)的?
答案:
委托屬性通過by關(guān)鍵字將屬性的get/set委托給另一個(gè)對(duì)象。
// 自定義委托
class StringDelegate {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("獲取屬性 ${property.name} 的值")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
println("設(shè)置屬性 ${property.name} 的值為 $newValue")
value = newValue
}
}
class MyClass {
var name: String by StringDelegate()
}
標(biāo)準(zhǔn)委托:
- lazy:延遲初始化
- observable:觀察屬性變化
- vetoable:可否決的觀察
- notNull:非空委托
- map:從Map中獲取屬性
// observable示例
var age: Int by Delegates.observable(0) { prop, old, new ->
println("年齡從 $old 變?yōu)?$new")
}
// map委托
class User(val map: Map<String, Any>) {
val name: String by map
val age: Int by map
}
16. Kotlin協(xié)程中的結(jié)構(gòu)化并發(fā)是什么?
答案:
結(jié)構(gòu)化并發(fā)確保協(xié)程的生命周期被正確管理,不會(huì)泄漏。
原則:
- 父子關(guān)系:父協(xié)程等待所有子協(xié)程完成
- 異常傳播:子協(xié)程異常會(huì)向上傳播
- 取消傳播:父協(xié)程取消會(huì)取消所有子協(xié)程
viewModelScope.launch {
// 父協(xié)程
launch {
// 子協(xié)程1
delay(1000)
println("子任務(wù)1完成")
}
launch {
// 子協(xié)程2
delay(2000)
println("子任務(wù)2完成")
}
// 父協(xié)程會(huì)等待兩個(gè)子協(xié)程都完成
println("所有任務(wù)完成")
}
// 異常處理
viewModelScope.launch {
try {
coroutineScope { // 作用域構(gòu)建器
launch {
throw Exception("子協(xié)程異常")
}
}
} catch (e: Exception) {
// 能捕獲到異常
println("捕獲異常: $e")
}
}
17. Kotlin中的reified關(guān)鍵字有什么用?
答案:
reified用于內(nèi)聯(lián)函數(shù)中,在運(yùn)行時(shí)獲取泛型類型信息。
// 普通泛型函數(shù)
fun <T> getType() {
// T.class // 編譯錯(cuò)誤,無(wú)法獲取類型
}
// 使用reified
inline fun <reified T> getGenericType() {
println(T::class.java) // 可以獲取類型
}
// 實(shí)用示例
inline fun <reified T> Gson.fromJson(json: String): T {
return fromJson(json, T::class.java)
}
// Activity啟動(dòng)
inline fun <reified T : Activity> Context.startActivity() {
val intent = Intent(this, T::class.java)
startActivity(intent)
}
// 使用
startActivity<DetailActivity>()
18. Kotlin中的協(xié)程異常處理策略
答案:
異常傳播機(jī)制:
- launch:自動(dòng)拋出異常,立即崩潰
- async:等待await時(shí)拋出異常
// launch異常處理
viewModelScope.launch {
try {
doWork()
} catch (e: Exception) {
println("捕獲異常: $e")
}
}
// 全局異常處理
val handler = CoroutineExceptionHandler { _, exception ->
println("協(xié)程異常: $exception")
}
viewModelScope.launch(handler) {
throw Exception("出錯(cuò)啦")
}
// SupervisorJob - 不向上傳播異常
val supervisor = SupervisorJob()
val scope = CoroutineScope(supervisor)
scope.launch {
throw Exception("子協(xié)程異常")
}
scope.launch {
delay(1000)
println("這個(gè)協(xié)程還會(huì)執(zhí)行")
}
// supervisorScope
viewModelScope.launch {
supervisorScope {
launch {
throw Exception() // 不會(huì)影響其他子協(xié)程
}
launch {
delay(1000)
println("仍然會(huì)執(zhí)行")
}
}
}
19. Kotlin Flow的操作符分類
答案:
// 1. 構(gòu)建操作符
flow { emit(1) } // flow構(gòu)建器
flowOf(1, 2, 3) // 從固定值
list.asFlow() // 集合轉(zhuǎn)Flow
// 2. 中間操作符
flow.map { it * 2 } // 映射
flow.filter { it > 5 } // 過濾
flow.transform { emit(it) } // 轉(zhuǎn)換
flow.take(3) // 取前N個(gè)
flow.drop(2) // 跳過前N個(gè)
flow.distinctUntilChanged() // 去重連續(xù)重復(fù)值
// 3. 線程操作符
flow.flowOn(Dispatchers.IO) // 指定上游線程
// 4. 緩沖與背壓
flow.buffer() // 緩沖
flow.conflate() // 只保留最新值
flow.collectLatest { } // 只處理最新值
// 5. 組合操作符
flow1.combine(flow2) { a, b -> a + b } // 組合
flow1.zip(flow2) { a, b -> a + b } // 配對(duì)
flow.flatMapConcat { } // 扁平化
// 6. 末端操作符
flow.collect { } // 收集
flow.toList() // 轉(zhuǎn)List
flow.single() // 確保只有一個(gè)值
flow.first() // 取第一個(gè)
20. Kotlin中實(shí)現(xiàn)單例的幾種方式
答案:
// 1. object聲明(最簡(jiǎn)單)
object AppConfig {
const val BASE_URL = "https://api.example.com"
fun log(message: String) {
println(message)
}
}
// 2. 伴生對(duì)象
class AppManager private constructor() {
companion object {
@Volatile
private var instance: AppManager? = null
fun getInstance(): AppManager {
return instance ?: synchronized(this) {
instance ?: AppManager().also { instance = it }
}
}
}
}
// 3. 懶加載委托
class DatabaseHelper private constructor() {
companion object {
val instance: DatabaseHelper by lazy {
DatabaseHelper()
}
}
}
// 4. 依賴注入(Dagger/Hilt)
@Singleton
class UserRepository @Inject constructor() {
// 由DI容器管理單例
}
?? 面試答題技巧
1. STAR法則回答項(xiàng)目問題
- Situation:項(xiàng)目背景
- Task:任務(wù)目標(biāo)
- Action:具體行動(dòng)(用了Kotlin的什么特性)
- Result:成果(性能提升、代碼量減少等)
2. Kotlin相關(guān)問題加分點(diǎn)
- 提到Kotlin與Java的互操作性
- 強(qiáng)調(diào)Kotlin如何解決Java痛點(diǎn)(空安全、代碼冗長(zhǎng))
- 結(jié)合實(shí)際項(xiàng)目經(jīng)驗(yàn)
- 關(guān)注最新版本特性(Kotlin 1.8/1.9新功能)
3. 常見反問問題
- "你們項(xiàng)目Kotlin化程度如何?"
- "遇到Kotlin版本升級(jí)兼容性問題嗎?"
- "協(xié)程和RxJava的選擇依據(jù)?"
需要針對(duì)某個(gè)具體問題深入展開嗎?我可以提供更詳細(xì)的源碼級(jí)分析。