Kotlin 面試問(wèn)題大全(帶解答和代碼示例)
基礎(chǔ)概念
1. Kotlin 與 Java 的主要區(qū)別是什么?
解答:
- 空安全:Kotlin 在類型系統(tǒng)中內(nèi)置了空安全
- 擴(kuò)展函數(shù):可以為現(xiàn)有類添加新方法
- 數(shù)據(jù)類:自動(dòng)生成 toString(), equals(), hashCode() 等方法
- 智能轉(zhuǎn)換:自動(dòng)進(jìn)行類型轉(zhuǎn)換
- 協(xié)程:內(nèi)置的異步編程支持
- 默認(rèn)參數(shù)和命名參數(shù):函數(shù)參數(shù)更靈活
- 沒(méi)有受檢異常:不需要捕獲所有異常
代碼示例:
// 空安全
var name: String = "Kotlin" // 非空
var nullableName: String? = null // 可空
// 擴(kuò)展函數(shù)
fun String.addExclamation(): String = this + "!"
println("Hello".addExclamation()) // 輸出: Hello!
// 數(shù)據(jù)類
data class User(val name: String, val age: Int)
val user = User("Alice", 25)
println(user) // 自動(dòng)生成 toString()
2. 什么是空安全?Kotlin 如何處理空指針異常?
解答:
Kotlin 通過(guò)類型系統(tǒng)區(qū)分可空和非空類型:
-
Type- 非空類型 -
Type?- 可空類型 - 安全調(diào)用操作符
?. - Elvis 操作符
?: - 非空斷言
!!
代碼示例:
fun processString(text: String?) {
// 安全調(diào)用
val length = text?.length // 如果 text 為 null,返回 null
// Elvis 操作符
val safeLength = text?.length ?: 0 // 如果 text 為 null,返回 0
// 非空斷言(不推薦)
val forcedLength = text!!.length // 如果 text 為 null,拋出 NPE
// 安全轉(zhuǎn)換
val number = text as? Int // 安全類型轉(zhuǎn)換
}
函數(shù)相關(guān)
3. 擴(kuò)展函數(shù)是什么?如何實(shí)現(xiàn)?
解答:
擴(kuò)展函數(shù)允許在不修改原類的情況下為類添加新函數(shù)。
代碼示例:
// 為 String 類添加擴(kuò)展函數(shù)
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
// 使用擴(kuò)展函數(shù)
println("radar".isPalindrome()) // 輸出: true
// 為 List 添加擴(kuò)展函數(shù)
fun <T> List<T>.secondOrNull(): T? {
return if (this.size >= 2) this[1] else null
}
val list = listOf(1, 2, 3)
println(list.secondOrNull()) // 輸出: 2
4. 內(nèi)聯(lián)函數(shù)的作用是什么?
解答:
內(nèi)聯(lián)函數(shù)通過(guò) inline 關(guān)鍵字聲明,編譯器會(huì)將函數(shù)體直接插入調(diào)用處,減少函數(shù)調(diào)用的開(kāi)銷,特別適用于高階函數(shù)。
代碼示例:
inline fun measureTime(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
// 使用
val time = measureTime {
// 執(zhí)行一些操作
Thread.sleep(100)
}
println("執(zhí)行時(shí)間: ${time}ms")
5. 中綴函數(shù)是什么?
解答:
中綴函數(shù)使用 infix 關(guān)鍵字,可以省略點(diǎn)號(hào)和括號(hào),使代碼更易讀。
代碼示例:
infix fun Int.times(str: String): String = str.repeat(this)
// 使用中綴表示法
val result = 3 times "Hello "
println(result) // 輸出: Hello Hello Hello
// 等同于
val result2 = 3.times("Hello ")
面向?qū)ο缶幊?/h2>
6. 數(shù)據(jù)類(Data Class)的特點(diǎn)是什么?
解答:
數(shù)據(jù)類自動(dòng)生成:
-
equals()/hashCode() toString()-
componentN()函數(shù)(用于解構(gòu)聲明) -
copy()函數(shù)
代碼示例:
data class Person(val name: String, val age: Int, val city: String)
// 自動(dòng)生成的函數(shù)使用
val person1 = Person("Alice", 25, "Beijing")
val person2 = Person("Alice", 25, "Beijing")
println(person1 == person2) // true - 自動(dòng)生成的 equals()
println(person1) // 自動(dòng)生成的 toString()
// 解構(gòu)聲明
val (name, age, city) = person1
println("$name, $age, $city")
// copy 函數(shù)
val person3 = person1.copy(age = 26)
println(person3) // Person(name=Alice, age=26, city=Beijing)
7. 密封類(Sealed Class)的作用是什么?
解答:
密封類用于表示受限的類層次結(jié)構(gòu),所有子類在編譯時(shí)已知。
代碼示例:
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}
fun handleResult(result: Result<String>) {
when (result) {
is Result.Success -> println("成功: ${result.data}")
is Result.Error -> println("錯(cuò)誤: ${result.message}")
Result.Loading -> println("加載中...")
// 不需要 else 分支,因?yàn)樗星闆r都已覆蓋
}
}
8. 對(duì)象聲明(Object Declaration)和伴生對(duì)象(Companion Object)的區(qū)別?
解答:
- 對(duì)象聲明:創(chuàng)建單例
- 伴生對(duì)象:類的"靜態(tài)"成員容器
代碼示例:
// 對(duì)象聲明 - 單例
object DatabaseManager {
private var connectionCount = 0
fun connect() {
connectionCount++
println("連接建立,當(dāng)前連接數(shù): $connectionCount")
}
}
// 伴生對(duì)象
class MyClass {
companion object {
const val MAX_INSTANCES = 10
private var instanceCount = 0
fun create(): MyClass {
if (instanceCount < MAX_INSTANCES) {
instanceCount++
return MyClass()
}
throw IllegalStateException("超過(guò)最大實(shí)例數(shù)")
}
}
}
// 使用
DatabaseManager.connect() // 直接通過(guò)對(duì)象名訪問(wèn)
val instance = MyClass.create() // 通過(guò)伴生對(duì)象訪問(wèn)
集合與函數(shù)式編程
9. Kotlin 集合與 Java 集合的區(qū)別?
解答:
Kotlin 區(qū)分可變和不可變集合:
-
ListvsMutableList -
SetvsMutableSet -
MapvsMutableMap
代碼示例:
// 不可變集合
val readOnlyList: List<Int> = listOf(1, 2, 3)
// readOnlyList.add(4) // 編譯錯(cuò)誤
// 可變集合
val mutableList: MutableList<Int> = mutableListOf(1, 2, 3)
mutableList.add(4) // 可以修改
// 集合操作
val numbers = listOf(1, 2, 3, 4, 5)
val evenSquares = numbers
.filter { it % 2 == 0 }
.map { it * it }
.take(2)
println(evenSquares) // 輸出: [4, 16]
10. 常用的集合操作函數(shù)有哪些?
代碼示例:
val numbers = listOf(1, 2, 3, 4, 5, 6)
// 過(guò)濾
val evens = numbers.filter { it % 2 == 0 }
// 映射
val squares = numbers.map { it * it }
// 查找
val firstEven = numbers.find { it % 2 == 0 }
// 分組
val grouped = numbers.groupBy { if (it % 2 == 0) "even" else "odd" }
// 折疊/歸約
val sum = numbers.reduce { acc, num -> acc + num }
val product = numbers.fold(1) { acc, num -> acc * num }
// 排序
val sorted = numbers.sortedByDescending { it }
println("偶數(shù): $evens")
println("平方: $squares")
println("第一個(gè)偶數(shù): $firstEven")
println("分組: $grouped")
println("總和: $sum")
println("乘積: $product")
協(xié)程
11. 什么是協(xié)程?與線程的區(qū)別?
解答:
協(xié)程是輕量級(jí)的線程,可以在不阻塞線程的情況下掛起和恢復(fù)執(zhí)行。
主要區(qū)別:
- 資源消耗:協(xié)程更輕量,可以創(chuàng)建數(shù)千個(gè)
- 調(diào)度:協(xié)程由程序員控制調(diào)度
- 阻塞:協(xié)程掛起不會(huì)阻塞線程
代碼示例:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 啟動(dòng)協(xié)程
val job = launch {
for (i in 1..5) {
println("協(xié)程執(zhí)行: $i")
delay(500) // 掛起協(xié)程,不阻塞線程
}
}
println("主線程繼續(xù)執(zhí)行")
job.join() // 等待協(xié)程完成
println("程序結(jié)束")
}
12. runBlocking, launch, async 的區(qū)別?
解答:
-
runBlocking:阻塞當(dāng)前線程直到協(xié)程完成 -
launch:?jiǎn)?dòng)不返回結(jié)果的協(xié)程 -
async:?jiǎn)?dòng)返回Deferred結(jié)果的協(xié)程
代碼示例:
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
fun main() = runBlocking {
// launch - 不返回結(jié)果
val job = launch {
delay(1000)
println("launch 完成")
}
// async - 返回結(jié)果
val deferred1 = async {
delay(500)
"結(jié)果1"
}
val deferred2 = async {
delay(300)
"結(jié)果2"
}
// 等待所有結(jié)果
val results = awaitAll(deferred1, deferred2)
println("async 結(jié)果: $results")
job.join()
}
13. 協(xié)程的調(diào)度器有哪些?
代碼示例:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 不同的調(diào)度器
launch(Dispatchers.Default) {
println("Default - 運(yùn)行在線程池: ${Thread.currentThread().name}")
}
launch(Dispatchers.IO) {
println("IO - 運(yùn)行在線程池: ${Thread.currentThread().name}")
}
launch(Dispatchers.Main) {
println("Main - 運(yùn)行在主線程") // 在 Android 中可用
}
launch(Dispatchers.Unconfined) {
println("Unconfined - 在調(diào)用者線程開(kāi)始: ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyThread")) {
println("自定義線程: ${Thread.currentThread().name}")
}
delay(1000)
}
高級(jí)特性
14. 什么是委托(Delegation)?舉例說(shuō)明屬性委托。
解答:
委托是一種設(shè)計(jì)模式,Kotlin 原生支持類委托和屬性委托。
代碼示例:
// 類委托
interface Repository {
fun getData(): String
}
class RealRepository : Repository {
override fun getData(): String = "真實(shí)數(shù)據(jù)"
}
class ProxyRepository(private val realRepository: RealRepository) : Repository by realRepository {
// 可以重寫(xiě)方法或添加額外邏輯
override fun getData(): String {
println("在代理中記錄日志")
return realRepository.getData()
}
}
// 屬性委托 - 延遲初始化
class Example {
val lazyValue: String by lazy {
println("計(jì)算 lazyValue")
"Hello"
}
// 可觀察屬性
var observedValue: String by Delegates.observable("初始值") {
property, oldValue, newValue ->
println("$oldValue -> $newValue")
}
}
// 使用
val example = Example()
println(example.lazyValue) // 第一次訪問(wèn)時(shí)計(jì)算
println(example.lazyValue) // 使用緩存的值
example.observedValue = "新值" // 觸發(fā)觀察者
15. 作用域函數(shù)(let, run, with, apply, also)的區(qū)別?
解答:
| 函數(shù) | 上下文對(duì)象 | 返回值 | 使用場(chǎng)景 |
|---|---|---|---|
let |
it |
lambda 結(jié)果 | 空檢查、轉(zhuǎn)換 |
run |
this |
lambda 結(jié)果 | 對(duì)象配置和計(jì)算 |
with |
this |
lambda 結(jié)果 | 對(duì)非空對(duì)象操作 |
apply |
this |
對(duì)象本身 | 對(duì)象配置 |
also |
it |
對(duì)象本身 | 附加效果 |
代碼示例:
data class Person(var name: String, var age: Int, var city: String)
fun main() {
val person = Person("Alice", 25, "Beijing")
// let - 使用 it 引用對(duì)象,返回 lambda 結(jié)果
val letResult = person.let {
"姓名: ${it.name}, 年齡: ${it.age}"
}
println(letResult)
// run - 使用 this 引用對(duì)象,返回 lambda 結(jié)果
val runResult = person.run {
age += 1
"明年年齡: $age"
}
println(runResult)
// with - 與 run 類似,但作為獨(dú)立函數(shù)
val withResult = with(person) {
city = "Shanghai"
"搬到了: $city"
}
println(withResult)
// apply - 使用 this 引用對(duì)象,返回對(duì)象本身
val applyResult = person.apply {
name = "Bob"
age = 30
}
println(applyResult)
// also - 使用 it 引用對(duì)象,返回對(duì)象本身
val alsoResult = person.also {
println("原始對(duì)象: $it")
it.city = "Guangzhou"
}
println(alsoResult)
}
實(shí)際應(yīng)用問(wèn)題
16. 如何在 Kotlin 中實(shí)現(xiàn)單例模式?
代碼示例:
// 方式1: 對(duì)象聲明(推薦)
object Singleton1 {
private var data: String = ""
fun setData(newData: String) {
data = newData
}
fun getData(): String = data
}
// 方式2: 伴生對(duì)象 + 私有構(gòu)造函數(shù)
class Singleton2 private constructor() {
companion object {
private var instance: Singleton2? = null
fun getInstance(): Singleton2 {
return instance ?: synchronized(this) {
instance ?: Singleton2().also { instance = it }
}
}
}
var data: String = ""
}
// 使用
Singleton1.setData("單例數(shù)據(jù)")
println(Singleton1.getData())
val singleton2 = Singleton2.getInstance()
singleton2.data = "另一種單例"
17. 如何在 Kotlin 與 Java 之間互操作?
代碼示例:
// Kotlin 調(diào)用 Java
class KotlinClass {
fun callJava() {
val javaClass = JavaClass()
javaClass.javaMethod("從 Kotlin 調(diào)用")
// 處理 Java 的可空性
val nullableString: String? = javaClass.getNullableString()
println(nullableString?.length)
}
}
// Java 調(diào)用 Kotlin
public class JavaClass {
public void callKotlin() {
KotlinClass kotlinClass = new KotlinClass();
kotlinClass.kotlinMethod("從 Java 調(diào)用");
// 處理 Kotlin 的默認(rèn)參數(shù)
kotlinClass.methodWithDefaultParam("參數(shù)");
}
}
18. 什么是內(nèi)聯(lián)類(Inline Class)?
解答:
內(nèi)聯(lián)類用于創(chuàng)建類型安全的包裝器,在運(yùn)行時(shí)通常會(huì)被解構(gòu)為底層類型。
代碼示例:
@JvmInline
value class Password(private val value: String) {
init {
require(value.length >= 8) { "密碼長(zhǎng)度至少8位" }
}
}
@JvmInline
value class UserId(val id: Int)
fun login(userId: UserId, password: Password) {
println("用戶 ${userId.id} 登錄")
}
// 使用
val userId = UserId(123)
val password = Password("securepassword123")
login(userId, password)
// 編譯錯(cuò)誤:類型安全
// login(123, "password") // 錯(cuò)誤!
性能與最佳實(shí)踐
19. 如何避免協(xié)程中的內(nèi)存泄漏?
代碼示例:
import kotlinx.coroutines.*
class MyViewModel {
private val scope = CoroutineScope(Dispatchers.Main + Job())
fun loadData() {
scope.launch {
// 執(zhí)行異步操作
val data = fetchData()
updateUI(data)
}
}
private suspend fun fetchData(): String {
delay(1000)
return "數(shù)據(jù)"
}
private fun updateUI(data: String) {
println("更新UI: $data")
}
// 清理資源
fun onCleared() {
scope.cancel()
}
}
// 使用結(jié)構(gòu)化并發(fā)
suspend fun fetchUserAndPosts(userId: String) = coroutineScope {
val userDeferred = async { fetchUser(userId) }
val postsDeferred = async { fetchPosts(userId) }
val user = userDeferred.await()
val posts = postsDeferred.await()
UserWithPosts(user, posts)
}
20. Kotlin 中的序列(Sequence)與集合的區(qū)別?
解答:
序列是惰性求值的,適用于大數(shù)據(jù)集或復(fù)雜鏈?zhǔn)讲僮鳌?/p>
代碼示例:
fun main() {
val numbers = (1..1_000_000)
// 集合操作 - 立即求值,創(chuàng)建中間集合
val listResult = numbers
.filter { it % 2 == 0 }
.map { it * it }
.take(10)
.toList()
// 序列操作 - 惰性求值,無(wú)中間集合
val sequenceResult = numbers.asSequence()
.filter { it % 2 == 0 }
.map { it * it }
.take(10)
.toList()
println("集合結(jié)果: $listResult")
println("序列結(jié)果: $sequenceResult")
// 無(wú)限序列
val infiniteSequence = generateSequence(1) { it + 1 }
val firstTen = infiniteSequence.take(10).toList()
println("前10個(gè)數(shù)字: $firstTen")
}
更多高級(jí)問(wèn)題
21. 什么是 reified 類型參數(shù)?
解答:
reified 關(guān)鍵字允許在泛型函數(shù)中訪問(wèn)類型信息。
代碼示例:
// 普通泛型函數(shù)無(wú)法訪問(wèn) T 的類信息
// fun <T> checkType(obj: Any): Boolean = obj is T // 編譯錯(cuò)誤
// 使用 reified
inline fun <reified T> checkType(obj: Any): Boolean = obj is T
inline fun <reified T> parseJson(json: String): T? {
// 這里可以使用 T::class
val type = T::class
println("解析類型: $type")
// 實(shí)際解析邏輯...
return null
}
// 使用
println(checkType<String>("Hello")) // true
println(checkType<Int>("Hello")) // false
parseJson<List<User>>("""[...]""")
22. Kotlin 中的注解使用
代碼示例:
// 定義注解
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class TestCase(val id: String)
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude
@Target(AnnotationTarget.PROPERTY)
annotation class JsonName(val name: String)
// 使用注解
@TestCase("user-test")
class UserServiceTest {
@JsonExclude
val password: String = "secret"
@JsonName("user_name")
val username: String = "alice"
@TestCase("login-test")
fun testLogin() {
// 測(cè)試代碼
}
}
// 處理注解(使用反射)
fun processAnnotations(obj: Any) {
obj::class.annotations.forEach { annotation ->
when (annotation) {
is TestCase -> println("測(cè)試用例: ${annotation.id}")
}
}
}
23. Kotlin 中的類型別名
代碼示例:
// 為復(fù)雜類型創(chuàng)建別名
typealias UserList = List<User>
typealias UserCallback = (User) -> Unit
typealias RequestHeaders = Map<String, String>
// 為泛型類型創(chuàng)建別名
typealias StringMap = Map<String, String>
typealias IntPredicate = (Int) -> Boolean
// 使用
fun processUsers(users: UserList, callback: UserCallback) {
users.forEach(callback)
}
val isEven: IntPredicate = { it % 2 == 0 }
println(isEven(4)) // true
24. Kotlin 的多平臺(tái)項(xiàng)目
代碼示例:
// commonMain - 公共代碼
expect class Platform() {
val platform: String
}
class Greeting {
fun greet(): String = "Hello from ${Platform().platform}"
}
// androidMain - Android 實(shí)現(xiàn)
actual class Platform actual constructor() {
actual val platform: String = "Android"
}
// iosMain - iOS 實(shí)現(xiàn)
actual class Platform actual constructor() {
actual val platform: String = "iOS"
}
總結(jié)
這些 Kotlin 面試問(wèn)題涵蓋了從基礎(chǔ)到高級(jí)的各個(gè)方面,包括:
- 基礎(chǔ)概念:空安全、類型系統(tǒng)、與 Java 的區(qū)別
- 函數(shù)特性:擴(kuò)展函數(shù)、內(nèi)聯(lián)函數(shù)、中綴函數(shù)
- 面向?qū)ο?/strong>:數(shù)據(jù)類、密封類、對(duì)象聲明
- 集合與函數(shù)式編程:集合操作、序列
- 協(xié)程:異步編程、調(diào)度器、結(jié)構(gòu)化并發(fā)
- 高級(jí)特性:委托、作用域函數(shù)、reified 類型
- 實(shí)際應(yīng)用:?jiǎn)卫J?、Java 互操作、性能優(yōu)化
準(zhǔn)備面試時(shí),建議不僅要理解概念,還要能夠編寫(xiě)實(shí)際的代碼示例,并解釋在不同場(chǎng)景下的最佳實(shí)踐選擇。