好的,这里为你整理了一份 Kotlin 的高频面试题清单,并附上详细的解答要点。这些问题覆盖了 Kotlin 的核心特性、与 Java 的区别以及实际开发中的常见场景。
一、基础概念题
1. val 和 var 的区别是什么?
val:用于声明只读变量,相当于 Java 中的final变量。一旦赋值,就不能再被重新赋值。var:用于声明可变变量,值可以被多次修改。- 最佳实践:优先使用
val,除非变量确实需要被改变,这能使代码更安全、更易于推理。
2. Kotlin 中如何处理空指针异常(Null Safety)?
这是 Kotlin 的核心安全特性。
- 不可空类型:默认声明的变量都不能为
null。var a: String = "abc" a = null // 编译错误 - 可空类型:在类型后加
?表示该变量可以为null。var b: String? = "abc" b = null // 编译通过 - 安全调用操作符(
?.):如果对象不为空,则执行调用,否则返回null。val length = b?.length // 类型是 Int? - Elvis 操作符(
?:):提供空值情况下的默认值。val length = b?.length ?: -1 // 如果 b 为 null,则返回 -1 - 非空断言操作符(
!!):断言一个可空对象不为空,如果为空则抛出NPE。应尽量避免使用。
二、与 Java 的对比和互操作
3. Kotlin 如何与 Java 互操作?
- 完全互操作:Kotlin 和 Java 可以 100% 相互调用。
- Getter/Setter 映射:Kotlin 会将 Java 类的 getter/setter 方法视为属性(如
javaObj.name实际调用getName())。 - 空安全处理:在调用 Java 代码时,Kotlin 会将类型视为平台类型(如
String!),需要开发者自行决定是否可空,或使用注解(如@Nullable,@NotNull)来指导 Kotlin 编译器。 - 关键字转义:如果 Java 方法名是 Kotlin 关键字(如
is,when),可以用反引号`包围来调用,例如:javaObj.is()。
三、函数相关
4. 什么是扩展函数?
允许你为已有的类(包括第三方库的类)添加新的函数,而无需继承或使用装饰器模式。
// 为 String 类添加一个新函数
fun String.addExclamation(): String {
return this + "!"
}
fun main() {
println("Hello".addExclamation()) // 输出 "Hello!"
}
- 解答要点:它实质上是静态函数的语法糖,不会真正修改原类。
5. infix 函数是什么?
允许以中缀表示法(省略点和括号)调用函数。必须是成员函数或扩展函数,且只有一个参数。
infix fun Int.multiply(factor: Int): Int = this * factor
fun main() {
val result = 5 multiply 3 // 等同于 5.multiply(3)
println(result) // 输出 15
}
6. 什么是高阶函数和 Lambda 表达式?
- 高阶函数:将函数用作参数或返回值的函数。
- Lambda 表达式:一种简洁的匿名函数表示法。
// 高阶函数
fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}
fun main() {
// 使用 Lambda 表达式
val sum = calculate(5, 3) { a, b -> a + b }
println(sum) // 输出 8
}
四、类与对象
7. 数据类有什么特点?用 data class 声明一个类有什么用?
用于专门持有数据的类。编译器会自动为其生成:
equals()/hashCode()toString()(格式为"User(name=John, age=42)")componentN()函数(用于解构声明)copy()函数(非常实用,用于复制对象并修改部分属性)
data class User(val name: String, val age: Int)
// 使用
val user = User("Alice", 25)
val olderUser = user.copy(age = 26) // 复制并修改 age
val (name, age) = user // 解构声明
8. Kotlin 中 object 关键字有哪几种用途?
- 对象声明(单例):声明一个单例对象。
object Singleton { fun doSomething() { ... } } // 调用:Singleton.doSomething() - 伴生对象:类内部的对象声明,其成员可通过类名直接访问,类似于 Java 的静态成员。
class MyClass { companion object { fun create(): MyClass = MyClass() } } // 调用:MyClass.create() - 对象表达式(匿名内部类):
window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { ... } })
9. 密封类有什么作用?
密封类(sealed class)用于表示受限的类层次结构,当一个值只能是有限集合中的一种类型时使用。它是枚举类的增强版,每个子类可以有多个实例,并且可以持有不同的数据。
sealed class Result<out T>
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
// 在 when 表达式中使用,无需 else 分支,因为所有情况已覆盖
fun handleResult(result: Result<Data>) {
when(result) {
is Success -> showData(result.data)
is Error -> showError(result.exception)
}
}
五、高级特性
10. 协程是什么?它和线程有什么区别?
- 是什么:Kotlin 的协程是轻量级的线程,用于简化异步编程。它本质上是基于线程池的线程框架封装,提供了挂起和恢复的能力。
- 与线程的区别:
- 资源开销:协程非常轻量,可以在一个线程中运行成千上万个协程,而线程创建和上下文切换成本很高。
- 控制权:协程是协作式的,由程序员控制挂起(
suspend)点;线程是抢占式的,由系统调度器决定切换。 - 阻塞 vs. 挂起:线程阻塞会浪费 CPU 资源,而协程挂起不会阻塞线程,线程可以被其他协程使用。
11. 解释一下 lateinit 和 by lazy 的区别。
两者都用于延迟初始化。
lateinit var:- 用于
var可变变量。 - 不能用于原生类型(如
Int,Boolean)。 - 表示你承诺会在使用前初始化,否则会抛出
UninitializedPropertyAccessException。 - 常用于依赖注入或在
onCreate等方法中初始化的字段(如 Android 中的View)。
- 用于
by lazy:- 用于
val只读变量。 - 是委托属性的一种。
- 在第一次访问时执行初始化 lambda 表达式,并将结果缓存起来供后续使用。
- 默认是线程安全的。
val heavyObject: HeavyClass by lazy { HeavyClass() // 仅在第一次访问 heavyObject 时创建 }- 用于
六、实战与场景题
12. 在 Kotlin 中,如何实现一个单例?
使用对象声明是最简单、最推荐的方式。
object MySingleton {
init {
// 初始化代码
}
fun doWork() {
println("Doing work")
}
}
// 使用
MySingleton.doWork()
13. 解释 let, apply, with, run, also 这几个作用域函数的区别。
它们都是在对象的上下文中执行代码块的便捷函数,区别在于接收者(this/it)和返回值。
| 函数 | 上下文对象 (Receiver) | 返回值 | 典型使用场景 |
|---|---|---|---|
let | it | Lambda 结果 | 非空执行、转换 |
apply | this | 对象本身 (this) | 对象配置 |
run | this | Lambda 结果 | 对象配置并计算结果 |
with | this | Lambda 结果 | 非扩展函数,对对象执行一组操作 |
also | it | 对象本身 (it) | 附加效果(如日志) |
示例:
// let: 对可空对象执行操作
user?.let {
println(it.name)
sendEmail(it.email)
}
// apply: 配置对象属性
val task = Task().apply {
description = "Learn Kotlin"
priority = 5
}
// also: 执行附加操作
val numbers = mutableListOf(1, 2, 3).also {
println("The list elements are: $it")
}

2292

被折叠的 条评论
为什么被折叠?



