Kotlin 面試題及答案大全

這里整理了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ì)泄漏。

原則

  1. 父子關(guān)系:父協(xié)程等待所有子協(xié)程完成
  2. 異常傳播:子協(xié)程異常會(huì)向上傳播
  3. 取消傳播:父協(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í)分析。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容