面试复习题-kotlin

发布于:2025-09-07 ⋅ 阅读:(26) ⋅ 点赞:(0)

一、基础语法与特性

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 函数的作用?
  • 内联:编译时将函数体插入调用处,避免创建匿名类和对象(提升性能)。
  • 常用于高阶函数(如 letapplyrun)。
  • 使用 noinline 可阻止部分参数内联,crossinline 限制 return
11. 常用作用域函数(letrunwithapplyalso)的区别?
函数 上下文对象 返回值 适用场景
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.IOMain)。
  • 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 关键字有哪些用法?
  1. 单例模式
     kotlin 

    深色版本

    object NetworkManager { fun request() { ... } }
  2. 伴生对象(Companion Object)
     kotlin 

    深色版本

    class MyClass {
        companion object { const val TAG = "MyClass" }
    }
  3. 对象表达式(匿名内部类)
     kotlin 

    深色版本

    val listener = object : OnClickListener { ... }

六、集合与函数式编程

18. mapflatMapfilterreduce 的作用?

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 } // 6

val 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 字节码生成。
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 面试重点:

  1. 空安全?!!?:?.
  2. 协程launch/asyncscope、异常处理
  3. 函数式编程:高阶函数、let/apply 等作用域函数
  4. 语法糖data classsealed classobject
  5. Android 特有viewModelScopelifecycleScope

建议结合实际项目经验,解释这些特性如何提升代码质量、可读性和安全性

一、深入理解 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 T
        Int::class -> 42 as T
        else -> 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 写出更安全、更简洁、更易维护的代码


网站公告

今日签到

点亮在社区的每一天
去签到