Android 资深岗 Kotlin 面试题:只会用协程不够,你得懂它为什么这么设计

Android 资深岗 Kotlin 面试题:只会用协程不够,你得懂它为什么这么设计

高级岗考察"会不会用",资深岗考察"为什么这么设计"。这篇文章不是更难的选择题,而是让你解释清楚每一个设计决策背后的 trade-off。

一、类型系统与编译期行为

1.1 平台类型(Platform Type)在工程中怎么处理?

核心 :平台类型是 Kotlin 与 Java 互操作时的妥协------编译器无法从 Java 字节码推断可空性,既不强制检查,也不保证非空。必须通过分级处理规范来治理,外部依赖按可空处理,内部契约按非空处理。

kotlin 复制代码
// 外部接口(第三方 SDK、HTTP 响应)------ 按可空处理
class ThirdPartyService {
    fun fetchUser(): UserDto? {
        return api.getUser()?.let { UserDto(it) }
    }
}

// 内部接口(模块间契约)------ 按非空 + 契约声明
@Contract("null -> null; !null -> !null")
fun String?.orThrow(message: String): String = this ?: throw IllegalStateException(message)

// 内部接口强制返回非空
class UserRepository {
    fun getUser(): User {
        return localDataSource.getUser() 
            ?: throw IllegalStateException("User must exist")
    }
}

Android 实战

kotlin 复制代码
// Java SDK 包装器,明确可空性
object SafePreferences {
    private val delegate = PreferenceManager.getDefaultSharedPreferences(context)
    
    fun getString(key: String, default: String?): String? = delegate.getString(key, default)
    
    fun requireString(key: String): String = getString(key, null) 
        ?: throw IllegalStateException("Missing required key: $key")
}

// Lint 检查配置
// .editorconfig 或 ktlint 规则中配置
ktlint:
  no-explicit-annotation-for-platform-type: error

面试加分

  • 核心业务模块间强制非空契约,外部依赖统一可空
  • CI 中加入 null-safety 检查,lint 规则强制显式标注平台类型
  • Java 库用 Kotlin 包装器隔离,接口层强制返回可空/非空
  • @Nullable 注解需要手动配置 Kapt/Kotlin 编译器才能生效
  • 团队需要制定 Platform Type 处理规范并严格执行

1.2 Smart Cast 为何不生效?

核心 :Smart cast 基于数据流分析 ------编译器在分支内"记住"变量的非空状态。但受限于线程安全性求值时机,var/可变属性/自定义 getter 无法触发。

kotlin 复制代码
// var 无法 smart cast(可能被其他线程修改)
var name: String? = "hello"
if (name != null) {
    println(name.length) // 编译错误!var 可能在 if 检查后被修改
}

// 封闭对象属性无法 smart cast(可能被其他线程修改)
class User { var name: String? = "Alice" }
fun test(user: User) {
    if (user.name != null) {
        println(user.name.length) // 编译错误!user.name 是 var
    }
}

// 自定义 getter 每次访问可能不同值
class Person { 
    val name: String? get() = computeName() // 每次访问执行计算
}
if (person.name != null) {
    println(person.name.length) // 编译错误!
}

// Lambda 捕获的 var(Kotlin 1.1+ 放宽限制)
fun process(list: MutableList<String>?) {
    if (list != null) {
        list.add("item") // OK,同一作用域内,编译器允许
    }
}

Android 实战

kotlin 复制代码
// 用 contract 扩展 smart cast 能力
@OptIn(ExperimentalContracts::class)
fun String?.orEmpty(): String {
    contract { returnsNotNull() implies (this@orEmpty != null) }
    return this ?: ""
}

// 使用后编译器知道 result 一定非空
fun test(s: String?) {
    val result = s.orEmpty()
    println(result.length) // OK
}

// 标准库实践
fun CharSequence?.isNullOrBlank(): Boolean {
    contract { returns(false) implies (this@isNullOrBlank != null) }
    return this == null || this.isBlank()
}

// ViewHolder 中的 smart cast
class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = getItem(position) ?: return
        holder.bind(item) // item 是 val,smart cast 生效
    }
}

面试加分

  • Kotlin 1.1+ 放宽了同一作用域内 Lambda 捕获 val 的限制
  • let 捕获局部变量可触发 smart cast
  • @JvmField 破坏 final 语义,导致 smart cast 失效
  • Contract 可以扩展编译器对空安全的认知
  • 遇到不生效时,用局部变量捕获或显式类型转换

1.3 类型投影(Type Projection)什么场景必须用?

核心 :当泛型类同时包含产出消费 操作时(如 MutableContainer<T>),无法声明型变,必须用投影限制使用方向。

kotlin 复制代码
class MutableContainer<T>(var value: T)

// 不使用投影:编译错误
// fun copyFrom(source: MutableContainer<String>, target: MutableContainer<Any>) {
//     target.value = source.value // 编译错误!
// }

// 使用 out 投影限制读取方向
fun copyFrom(source: MutableContainer<out Any>, target: MutableContainer<Any>) {
    target.value = source.value // OK,out 投影允许读取
}

// 决策树:
// - 同时读写?→ 不使用投影,保持不变
// - 只读(生产者)?→ <out T> 协变
// - 只写(消费者)?→ <in T> 逆变
// - 不知道类型,只读?→ <*>

Android 实战

kotlin

kotlin 复制代码
// Repository 设计:产出 vs 消费分离
interface Producer<out T> { fun produce(): T }
interface Consumer<in T> { fun consume(value: T) }

// 同时需要产出和消费时,拆成两个接口
interface Repository<T> {
    fun getAll(): List<T> // 产出
    fun save(item: T)     // 消费
    // 不能声明为协变,需要拆成 Producer/Consumer
}

// 只读容器
class ReadOnlyContainer<out T>(private val item: T) {
    fun get(): T = item
}

// 只写容器
class WriteOnlyContainer<in T> {
    fun set(value: T) { /* ... */ }
}

// 星投影使用
val box: MutableContainer<*> = MutableContainer("hello")
val value: Any? = box.value // 可以读取,返回 Any?
// box.value = 123 // 编译错误,不能写入

面试加分

  • out T = 协变,只能作为返回值类型(产出);in T = 逆变,只能作为参数类型(消费)
  • * 星投影等价于 out Any?,只能读、不能写
  • List<String> 可赋值给 List<Any>,因为 List 只读不变
  • MutableList<String> 不能赋值给 MutableList<Any>,否则可写入破坏类型安全
  • Kotlin 标准库中的 List 是协变的,MutableList 不是

1.4 Reified 为什么只能在 inline 函数中使用?

核心 :reified 通过字节码展开 实现------编译器在每个调用点用具体类型替换 T,避免泛型擦除。inline 确保调用点代码可见,字节码才能被替换。

kotlin 复制代码
inline fun <reified T> myInstanceOf(obj: Any): Boolean = obj is T

// 编译后等价于(字节码展开):
fun myInstanceOf_STRING(obj: Any): Boolean = obj is String
fun myInstanceOf_INT(obj: Any): Boolean = obj is Int
fun myInstanceOf_LONG(obj: Any): Boolean = obj is Long

// 调用 myInstanceOf<String>() 直接变成 obj is String

Android 实战

kotlin

kotlin 复制代码
// Gson 扩展:不需要 TypeToken
inline fun <reified T> Gson.fromJson(json: String): T = 
    this.fromJson(json, T::class.java)

// 使用
val user: User = gson.fromJson(jsonString)

// 安全类型转换
inline fun <reified T> Any.safeCast(): T? = this as? T

// 类型检查
inline fun <reified T> Any.isType(): Boolean = this is T

// 获取泛型类型
inline fun <reified T> getGenericType(): Class<T> = T::class.java

面试加分

  • JVM 泛型擦除:运行时无法区分 List<String>List<Int>
  • reified 的代价是字节码膨胀(N 个调用点 → N 份代码)
  • T::class.java 编译成字面量,无运行时查找开销
  • 性能敏感场景注意控制 reified 函数调用点数量
  • 不要为了"方便"滥用 reified,只在确实需要运行时类型时使用

1.5 Nothing 和 Unit 的本质区别?

核心Nothing 是底类型(Bottom Type),所有类型的子类型,表示"永不返回";Unit 是单例,表示"无意义返回值"。两者在语义和使用场景上有本质区别。

kotlin 复制代码
// Nothing:永不返回,用于标记异常路径
fun fail(message: String): Nothing = throw IllegalStateException(message)

// Unit:表示"无意义返回"的单例
fun log(message: String): Unit {
    println(message)
    return Unit // 显式返回,但可省略
}

// Nothing 在泛型中的特殊地位
val failure: Result<Nothing> = Result.failure(Exception())

// Result<Nothing> 可以赋值给 Result<T>,因为 Nothing 是所有类型的子类型
fun <T> handle(r: Result<T>) { /* ... */ }
handle(failure) // OK:Result<Nothing> → Result<T>

// TODO() 返回 Nothing,编译器知道后续代码不可达
fun calculate(x: Int): Int = when (x) {
    0 -> 0
    else -> TODO("Implement for x=$x")
}

Android 实战

kotlin 复制代码
// sealed class 用 Nothing 表示"无数据"的错误分支
sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Error(val exception: Throwable) : ApiResult<Nothing>() // Nothing!
}

// 编译器知道 Error 分支不可能返回数据
when (result) {
    is ApiResult.Success -> render(result.data)
    is ApiResult.Error -> showError(result.exception.message)
}

// requireNotNull 内部返回 Nothing
fun validate(user: User?) {
    requireNotNull(user) { "User must exist" }
    println(user.name) // 编译器知道 user 现在非空
}

// 不可达代码检测
fun process(x: Int): String = when (x) {
    1 -> "one"
    else -> {
        fail("Unsupported value: $x")
        // 编译器知道这里不可达
    }
}

面试加分

  • requireNotNull / checkNotNull 内部返回 Nothing,编译器知道非空
  • sealed class 配合 Nothing 可以精确表达"失败无数据"的语义
  • Nothing 的唯一实例是它自己(不存在),用于标记不可达路径
  • exitProcess() 等函数返回 Nothing 表示终止进程永不返回
  • Result<Nothing> 是所有 Result<T> 的子类型,这在泛型中非常有用

1.6 Value Class(@JvmInline)解决了什么?有哪些限制?

核心 :编译期提供类型安全标签,运行时零开销(内联到基础类型)。适合 ID、度量单位等需要类型区分但又不想增加运行时开销的场景。

kotlin 复制代码
@JvmInline
value class UserId(val value: Long)

@JvmInline
value class OrderId(val value: Long)

fun findUser(id: UserId) = /* ... */
fun findOrder(id: OrderId) = /* ... */

findUser(OrderId(123L)) // 编译错误!类型不匹配
// 运行时:findUser(123L) - 无对象创建,直接传 Long

Android 实战

less 复制代码
@JvmInline
value class Milliseconds(val value: Long)

@JvmInline
value class Seconds(val value: Long)

@JvmInline
value class Bytes(val value: Int)

fun sleep(ms: Milliseconds) = Thread.sleep(ms.value)
fun sleep(seconds: Seconds) = Thread.sleep(seconds.value * 1000)

sleep(Milliseconds(1000)) // OK
sleep(Seconds(1))          // OK
sleep(1000)               // 编译错误!必须显式转换

面试加分

  • 必须有且仅有一个 val 属性,不能有 init 块
  • 不能继承或被继承
  • 运行时无对象创建,方法直接接受基础类型参数
  • data class 会创建对象包装,value class 不会
  • 字节码对比:value class 用 invokestatic,data class 用 new + invokespecial

二、协程与异步的工程深度

2.1 Dispatcher 是怎么实现的?能不能自定义?

核心 :Dispatcher 基于 ContinuationInterceptor 实现------协程启动时,拦截器包装 Continuation,在 dispatch 方法中决定在哪里执行。自定义 Dispatcher 只需重写 dispatch 方法。

kotlin 复制代码
// CoroutineDispatcher 核心方法
abstract class CoroutineDispatcher : AbstractCoroutineContextElement(), ContinuationInterceptor {
    abstract fun dispatch(context: CoroutineContext, block: Runnable)
    open fun dispatchYield(context: CoroutineContext, block: Runnable) {
        dispatch(context, block)
    }
}

// 自定义 Dispatcher
class SingleThreadDispatcher(private val name: String) : CoroutineDispatcher() {
    private val executor = Executors.newSingleThreadExecutor { Thread(it, name) }
    
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        executor.submit(block)
    }
    
    override fun dispatchYield(context: CoroutineContext, block: Runnable) {
        executor.submit(block) // 让出 CPU 后继续
    }
}

Android 实战

kotlin 复制代码
// 图片预加载低优先级 Dispatcher
class ImagePreloadDispatcher : CoroutineDispatcher() {
    private val executor = ForkJoinPool.commonPool()
    
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        executor.execute {
            try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
                block.run()
            } catch (e: CancellationException) {
                // 忽略取消异常
            }
        }
    }
}

// 限制并发数
class LimitedConcurrencyDispatcher(private val maxConcurrency: Int) : CoroutineDispatcher() {
    private val semaphore = Semaphore(maxConcurrency)
    
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        if (semaphore.tryAcquire()) {
            try { block.run() } finally { semaphore.release() }
        } else {
            launch(Dispatchers.IO) {
                semaphore.acquire()
                try { block.run() } finally { semaphore.release() }
            }
        }
    }
}

// 优先级调度
class PriorityDispatcher : CoroutineDispatcher() {
    private val queue = PriorityBlockingQueue<Runnable>()
    
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        val priority = context[ContinuationInterceptor]?.let { /* 提取优先级 */ } ?: 0
        queue.add(PrioritizedRunnable(priority, block))
    }
}

面试加分

  • Dispatchers.IO 用阻塞 IO 线程池(64 线程),Default 用 CPU 密集型线程池(核心数线程)
  • 自定义 Dispatcher 可实现并发限制(Semaphore)、优先级调度
  • dispatchYield 让出 CPU 后继续执行,适合低优先级任务
  • Dispatcher 切换不创建新协程,只是改变执行线程
  • Android 主线程调度通过 Handler.dispatch() 实现

2.2 StateFlow 和 SharedFlow 选型标准?

核心 :需要最新值 (只关心当前状态)选 StateFlow;需要完整事件流(不能丢事件)选 SharedFlow。StateFlow 的 conflation 策略会丢失中间值。

ini 复制代码
// StateFlow:值合并,只保留最新值
val stateFlow = MutableStateFlow(0)
stateFlow.value = 1
stateFlow.value = 2
stateFlow.value = 3
stateFlow.collect { println(it) } // 只输出 3

// SharedFlow:可配置 replay 和 buffer
val sharedFlow = MutableSharedFlow<Int>(replay = 2, extraBufferCapacity = 3)
sharedFlow.emit(1)
sharedFlow.emit(2)
// replay=2:新订阅者收到 [2, 3]
// buffer=[1,2,3] - replay拿走2个,还剩1个在buffer

Android 实战

kotlin 复制代码
// UI 状态 → StateFlow
class UiState(val items: List<Item>, val loading: Boolean)
val _state = MutableStateFlow(UiState(emptyList(), false))
val state: StateFlow<UiState> = _state

// 一次性事件 → SharedFlow(replay=0)
private val _events = MutableSharedFlow<UiEvent>(replay = 0)
val events: SharedFlow<UiEvent> = _events

// StateFlow equals 去重问题
data class UiState2(val value: Int)

val flow = MutableStateFlow(UiState2(0))
flow.value = flow.value.copy(value = 0) // equals 为 true,不触发 collect!

// 解决方案:version 字段
data class UiState3(val value: Int, val version: Long = 0) {
    fun increment() = UiState3(value, version + 1)
}
val flow2 = MutableStateFlow(UiState3(0))
flow2.value = flow2.value.increment() // version 不同,触发通知

面试加分

  • StateFlow 的 equals 比较:相同值不触发 collect
  • SharedFlow 的 replay 控制新订阅者收到几个历史值
  • bufferOverflow 控制缓冲区满时的策略(SUSPEND/DROP_OLDEST/DROP_LATEST)
  • 配置变更恢复用 StateFlow + SavedStateHandle
  • MutableSharedFlow(replay=1) 可替代 StateFlow 的 replay 行为

2.3 协程异常传播机制:为什么 structured concurrency 重要?

核心 :structured concurrency 确保父子协程的生命周期绑定 。子协程异常会传播并取消兄弟协程,形成"全有或全无"的语义。除非用 supervisorScope 阻断。

kotlin 复制代码
// 异常传播链路
suspend fun parent() = coroutineScope {
    launch { throw Exception("Child error") } // 1. 子协程抛异常
    launch { delay(1000); println("This won't run") } // 2. 兄弟协程被取消
}
// 3. 父协程重新抛出异常

// supervisorScope 阻断传播
suspend fun isolated() = supervisorScope {
    launch { throw Exception("Child error") }
    launch { delay(1000); println("This still runs!") } // 不受影响
}
// 协程正常完成,异常不传播

Android 实战

kotlin

kotlin 复制代码
// 并行请求,一个失败不影响另一个
suspend fun fetchAll(): List<Result<Data>> = supervisorScope {
    urls.map { url -> async { fetch(url) } }
        .awaitAll()
        .map { it.getOrElse { e -> Result.failure(e) } }
}

// 串行步骤,任何一步失败回滚
suspend fun processWithRollback() = coroutineScope {
    try {
        val data = fetchData()
        saveToCache(data)
        notifyListeners(data)
    } catch (e: Exception) {
        rollback()
        throw e
    }
}

// 后台任务,失败静默重试
suspend fun backgroundTask() = supervisorScope {
    launch {
        retry(times = 3) { performBackgroundWork() }
    }
}

// 坑:withContext 中的异常行为
suspend fun tricky() = coroutineScope {
    launch {
        withContext(Dispatchers.Default) {
            throw Exception("Error in withContext")
        }
    }
    launch {
        delay(1000)
        println("This won't run!") // 兄弟协程仍然被取消
    }
}
// withContext 中的异常会传播到 launch,coroutineScope 取消所有子协程

面试加分

  • withContext 不创建新作用域,不影响兄弟协程
  • coroutineScope 会传播异常并取消所有子协程
  • 全局 CoroutineScope(ViewModel.viewModelScope)默认是 SupervisorJob
  • 异常处理要在合适的作用域层级
  • CoroutineExceptionHandler 只对根协程生效

2.4 select 表达式做什么的?什么场景必须用?

核心 :select 用于竞速场景------等待多个挂起操作,第一个完成的结果立即返回,其他未完成的继续执行(或被取消)。

kotlin 复制代码
suspend fun selectWinner() {
    val deferred1 = async { delay(100); "result1" }
    val deferred2 = async { delay(200); "result2" }
    
    val result = select<String> {
        deferred1.onAwait { it }
        deferred2.onAwait { it }
    }
    // "result1" 先返回,deferred2 可选择继续执行或取消
}

Android 实战

kotlin 复制代码
// 多数据源竞速:本地缓存 vs 网络请求
suspend fun getUser(userId: String): User {
    val localDeferred = async(Dispatchers.IO) { local.getUser(userId) }
    val remoteDeferred = async(Dispatchers.IO) { remote.fetchUser(userId) }
    
    return select {
        localDeferred.onAwait { user ->
            local.cacheUser(user); user // 本地命中,保存后返回
        }
        remoteDeferred.onAwait { user ->
            local.cacheUser(user); user // 网络更快,同步缓存后返回
        }
    }.also {
        // 取消未完成的任务
        if (!localDeferred.isCompleted) localDeferred.cancel()
        if (!remoteDeferred.isCompleted) remoteDeferred.cancel()
    }
}

// 超时控制
suspend fun <T> withTimeoutOrElse(timeout: Long, fallback: () -> T, block: suspend () -> T): T {
    return select {
        block().onAwait { it }
        delay(timeout).onAwait { fallback() }
    }
}

// 多支付渠道竞速
suspend fun selectWinner(): PaymentChannel = select {
    async { alipayPay() }.onAwait { PaymentChannel("alipay", it) }
    async { wechatPay() }.onAwait { PaymentChannel("wechat", it) }
    async { unionPay() }.onAwait { PaymentChannel("union", it) }
}

面试加分

  • 需要所有结果用 awaitAllflow { ... }.buffer() 并发收集
  • select 可用于 Channel 的多路复用(selectChannel)
  • onAwait 等待 Deferred,onReceive 等待 Channel
  • select 不是 suspend 函数,不会阻塞当前协程
  • kotlinx.coroutines.select 需要单独依赖

三、性能与编译期优化

3.1 by lazy 三种线程安全模式怎么选?

核心:默认 SYNCHRONIZED 用双重检查锁保证线程安全;PUBLICATION 可能多次初始化但只使用第一个结果;NONE 无锁最快但不安全。

scss 复制代码
// SYNCHRONIZED(默认):双重检查锁
val resource1 by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { HeavyResource() }

// PUBLICATION:可能多次初始化,只使用第一个
val resource2 by lazy(LazyThreadSafetyMode.PUBLICATION) { HeavyResource() }

// NONE:无锁,最快但不安全
val resource3 by lazy(LazyThreadSafetyMode.NONE) { HeavyResource() }

Android 实战

kotlin 复制代码
class MyViewModel : ViewModel() {
    // 主线程创建,重复初始化代价小 → PUBLICATION
    val repository by lazy(LazyThreadSafetyMode.PUBLICATION) { UserRepository() }
}

class MyFragment : Fragment() {
    // 主线程创建,UI 初始化,且 Fragment 只在主线程访问 → NONE
    private val adapter by lazy(LazyThreadSafetyMode.NONE) { MyAdapter() }
}

class App : Application() {
    // Application 可能通过多路径初始化 → SYNCHRONIZED
    val config by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { AppConfig() }
}

// NONE 模式字节码(最简单)
// private final Object resource3$delegate;
// public final Object getResource3() {
//     Object $instance;
//     if ((($instance = this.resource3$delegate) != null) ||
//         ($instance = new Object()) != null) {
//         return $instance; // 直接返回,无任何锁
//     }
// }

面试加分

  • NONE 模式字节码最简单,无任何同步开销
  • PUBLICATION 可能产生多个对象,但只有第一个被使用
  • Application 单例推荐 SYNCHRONIZED,防止 ContentProvider 等多初始化路径
  • ViewModel 创建在主线程,但构造可能被多次调用(配置变更)→ PUBLICATION
  • 选错模式可能导致重复初始化或多线程问题

3.2 data class copy() 有哪些隐蔽的坑?

核心 :copy 是浅拷贝 ------嵌套对象共享引用;copy 产生的新对象与原对象 equals 为 true 时,StateFlow/collect 不会触发回调。

ini 复制代码
data class Address(val city: String, val street: String)
data class Person(val name: String, val address: Address)

val alice = Person("Alice", Address("Beijing", "Main St"))
val bob = alice.copy(name = "Bob")

// bob.address === alice.address → true(嵌套对象是同一个引用!)
println(bob.address === alice.address) // true

// equals 去重问题
val stateFlow = MutableStateFlow(State(0))
stateFlow.value = stateFlow.value.copy(count = 0) // equals 为 true,不触发!

// copy() 显式传参 vs 默认参数
val state1 = State(0)
val state2 = state1.copy()       // 新对象,equals 为 true
val state3 = state1.copy(count = 0) // 也是新对象,equals 为 true

Android 实战

kotlin 复制代码
// 正确的深拷贝
val bob = alice.copy(
    name = "Bob",
    address = alice.address.copy() // 嵌套也要 copy
)

// Immutable 模式(推荐)
class Person private constructor(
    val name: String,
    val address: Address
) {
    companion object {
        fun create(name: String, address: Address) = Person(name, address)
    }
    fun withName(newName: String) = Person(newName, address)
    fun withAddress(newAddress: Address) = Person(name, newAddress)
}

// 序列化/反序列化深拷贝
val bob = alice.copyDeepClone() // 需要实现深拷贝方法

面试加分

  • 嵌套对象需要手动 copy 或用序列化/反序列化实现深拷贝
  • StateFlow 用 version 字段解决 equals 去重问题
  • copy() 默认参数只在未传参时使用,显式传相同值会产生新对象(但 equals 仍为 true)
  • Immutable 设计 + copy-on-write 是更彻底的解决方案
  • 使用 copy 时要注意嵌套对象的深拷贝问题

3.3 Kotlin 空安全在编译期和运行期的保障分别是什么?

核心 :编译期靠类型系统强制检查 ,运行期靠 Intrinsics.checkNotNullParameter 等 intrinsic 函数。Java 互调、反射、多线程竞态、泛型擦除等场景会失效。

kotlin 复制代码
// 编译期保障
val name: String? = null
println(name.length) // 编译错误!

// 运行期保障
fun processName(name: String) {
    // 编译器插入:checkNotNullParameter(name, "name")
    // 如果 name 为 null,抛出 IllegalArgumentException
}

// 运行期失效场景
val name: String = javaMethodReturningNull() // 编译通过,运行时 NPE

// 反射修改 val
val person = Person("Alice")
Person::class.java.getDeclaredField("name").apply { 
    isAccessible = true; set(person, null) // val 也能改!
}

// 多线程 TOCTOU
var value: String? = "initialized"
fun process() {
    if (value != null) {
        // Thread B: value = null
        println(value!!.length) // 可能 NPE
    }
}

// 泛型擦除
fun <T> printList(list: List<T>) {
    // 无法区分 T 是 String 还是 String?
}

// 序列化/反序列化
val json = """{"data":null}"""
val response = ObjectMapper().readValue(json, Response::class.java)
// data 字段被反序列化为 null,但类型是 String(非空)

Android 实战

kotlin 复制代码
// 关键路径加运行时断言
fun processUser(user: User) {
    requireNotNull(user.id) { "User ID must not be null" }
    requireNotNull(user.email) { "User email must not be null" }
}

// 外部数据做契约验证
fun parseUserResponse(json: String): User {
    return json.toUser() ?: throw IllegalStateException("Invalid user data")
}

// @JvmStatic 确保 Java 调用时也进行空检查
class UserConfig {
    companion object {
        @JvmStatic fun getInstance(): UserConfig = instance
    }
}

面试加分

  • Java 注解(@Nullable/@NotNull)需要配置 Kapt/Kotlin 编译器才生效
  • 泛型擦除导致 <String><String?> 运行时无法区分
  • 序列化/反序列化可能破坏空安全(Gson 默认将 null 反序列化为字段类型默认值)
  • @JvmStatic 确保 Java 调用时也进行空检查
  • 反射可以修改任何字段,包括 val(通过 Unsafe)

3.4 集合操作在性能敏感场景怎么选?

核心多次中间操作 用 Sequence(懒执行),单次终态操作用 List(无额外开销)。小数据量用 ArrayMap/SparseArray 避免装箱开销。

scss 复制代码
// Sequence:懒执行,只遍历一次
sequenceOf(1, 2, 3, 4, 5)
    .map { it * 2 }      // 装饰器,不执行
    .filter { it > 4 }   // 装饰器,不执行
    .forEach { println(it) } // 按需执行

// List:每次操作都创建中间集合
listOf(1, 2, 3, 4, 5)
    .map { it * 2 }      // 创建 List(5)
    .filter { it > 4 }   // 创建 List(3)
    .forEach { println(it) }

// 单次操作无区别
listOf(1, 2, 3).first() // 与 sequence.first() 几乎相同

// JMH 基准测试结果(10000 元素)
// List chain: ~0.5ms
// Sequence chain: ~0.2ms (60% faster)

Android 实战

dart 复制代码
// 小数据量 ArrayMap(<100 元素)
val arrayMap = ArrayMap<String, String>().apply {
    put("key1", "value1")
    put("key2", "value2")
}

// SparseArray 避免 Integer 装箱(比 HashMap 省 60% 内存)
val sparseArray = SparseArray<String>()
sparseArray.put(1, "value1")
val value: String? = sparseArray.get(1)

// 大数据量 HashMap
val hashMap = HashMap<String, String>()
repeat(1000) { put("key$it", "value$it") }

// 经验法则:
// < 100 元素:ArrayMap
// >= 100 元素:HashMap
// Integer/Int 作为 Key:SparseArray

面试加分

  • Sequence 中间操作是装饰器,终态操作才真正执行
  • 经验法则:< 100 元素 ArrayMap 更快,>= 100 HashMap 更快
  • SparseArray 比 Map<Int, V> 节省 Integer 装箱开销
  • Android 的 ArrayMapSparseArray 是内存敏感场景的优化利器
  • Sequence 的惰性求值在大数据量或昂贵操作时效果显著

四、架构设计

4.1 如何设计类型安全的路由框架?

核心 :用 sealed class 定义路由表实现穷举检查,用 reified 泛型 传递目标类型,导航参数作为 data class 属性,编译期确保类型安全。

kotlin 复制代码
sealed class Route {
    @Route("home") data object Home : Route()
    @Route("detail/{id}") data class Detail(val id: String) : Route()
    @Route("settings") data object Settings : Route()
    // 新增路由时,编译器强制你处理所有 when 分支
}

object Router {
    inline fun <reified T : Route> navigate(context: Context) {
        val route = T::class.java.getAnnotation(Route::class.java)
        val path = route?.value?.parsePath(T::class) ?: "/unknown"
        Navigation.findNavController(context).navigate(path)
    }
    
    private fun String.parsePath(clazz: KClass<out Route>): String {
        return this.replace(Regex("\{(\w+)}")) { match ->
            when (clazz) {
                Route.Detail::class -> (clazz.objectInstance as? Route.Detail)?.id ?: ""
                else -> ""
            }
        }
    }
}

// 使用
navigate<Route.Detail> { id = "123" } // 类型安全

Android 实战

kotlin 复制代码
@OptIn(ExperimentalContracts::class)
inline fun <reified T : Route> navigateTo(context: Context, block: T.() -> Unit = {}) {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    
    val route = T::class.java.getAnnotation(Route::class.java)
    check(route != null) { "Route annotation not found for ${T::class.java}" }
    
    val routeInstance = when (val obj = T::class.objectInstance) {
        is Route -> obj
        else -> createRouteInstance<T>()
    }
    routeInstance.block()
    navigate(context)
}

// 对比 ARouter
// | 维度         | ARouter        | Kotlin 类型安全路由 |
// |-------------|----------------|-------------------|
// | 路由定义      | 注解 + 编译时生成  | sealed class 编译期穷举 |
// | 参数传递      | 字符串 key        | 属性直接赋值         |
// | 类型安全      | 运行时检查         | 编译期检查           |
// | when 穷举    | 无              | 有                 |

面试加分

  • sealed class 配合 when 实现穷举检查,新增路由时编译器强制处理
  • reified 在编译时获取类型信息,避免字符串路径
  • ARouter 等框架是运行时检查,Kotlin 类型安全方案更优
  • 导航参数作为 data class 属性,有 IDE 自动补全和类型检查
  • Navigation Component 的 SafeArgs 也是类型安全的方案

4.2 Kotlin Multiplatform 先共享哪层?

核心数据层(Model + Repository) 最安全,改动最小。Domain 层中等风险。UI 层(Compose Multiplatform)生态不成熟,暂不推荐。

arduino 复制代码
// 共享策略决策
// 只是 Android 优化?→ 不需要 KMP,用 Kotlin DSL 优化构建

// 只需要共享数据层?→ ✅ 最安全
// - Entity/DTO 定义在 common
// - Repository 接口在 common,实现分开
// - Network/Database 具体实现 platform-specific

// 需要共享 Domain 层?→ ⚠️ 中等风险
// - Domain 逻辑跨平台
// - DI 需要 platform-specific 适配器

// 想共享 UI 层?→ ❌ 不推荐
// - 生态不成熟,库支持有限
// - 学习曲线陡峭
// - 调试困难

Android 实战

kotlin 复制代码
// expect/actual 机制
expect class Logger {
    fun log(message: String)
    fun setLevel(level: LogLevel)
}

enum class LogLevel { DEBUG, INFO, WARN, ERROR }

// androidMain
actual class Logger {
    private val logger = android.util.Log
    
    actual fun log(message: String) {
        logger.d("Platform", message)
    }
    
    actual fun setLevel(level: LogLevel) {
        // Android: 可以设置日志级别过滤
    }
}

// 坑:expect 只能声明,不能写实现
// expect class Logger {
//     fun log(message: String) // 不能有实现
// }

// Android 特有的 API 无法直接共享
expect fun getCurrentTime(): Long
// actual android
// actual fun getCurrentTime(): Long = System.currentTimeMillis()
// actual ios
// actual fun getCurrentTime(): Long = mach_absolute_time()

面试加分

  • expect/actual 维护成本高,新增方法需改所有平台
  • Context、Lifecycle 等 Android 特有概念无法共享
  • KMP 生态正在成熟,但库支持仍有限
  • 建议从数据层切入,逐步扩展
  • Kotlin 1.9+ 改进了 expect/actual 语法

4.3 如何设计可靠的"一次性事件"机制?

核心 :用 SharedFlow(replay=0) 配合 repeatOnLifecycle,确保事件只被消费一次,配置变更后可恢复。

kotlin 复制代码
class SingleLiveEvent<T> {
    private val _events = MutableSharedFlow<T>(replay = 0)
    val events: SharedFlow<T> = _events.asSharedFlow()
    
    fun emitEvent(event: T) = _events.tryEmit(event)
    suspend fun sendEvent(event: T) = _events.emit(event)
}

// 方案对比
// | 方案             | 事件丢失风险   | 配置变更恢复 | 代码复杂度 |
// |-----------------|-------------|-----------|---------|
// | SingleLiveEvent | 无          | 重新发射     | 中       |
// | Event Wrapper   | 无          | 重新发射     | 低       |
// | SharedFlow      | 有(无消费者时)| 重新发射     | 低       |
// | Channel         | 有(无消费者时)| 重新发射     | 中       |

Android 实战

kotlin 复制代码
sealed class UiEvent {
    data class ShowToast(val message: String) : UiEvent()
    data class ShowSnackbar(val message: String, val action: String? = null) : UiEvent()
    data class Navigate(val route: Route) : UiEvent()
    object NavigateBack : UiEvent()
}

class UiEventHandler(
    private val snackbarHost: SnackbarHostState
) {
    fun handleEvents(events: SharedFlow<UiEvent>, scope: CoroutineScope) {
        scope.launch {
            events.collect { event ->
                when (event) {
                    is UiEvent.ShowToast -> showToast(event.message)
                    is UiEvent.ShowSnackbar -> snackbarHost.showSnackbar(event.message, event.action)
                    is UiEvent.Navigate -> navigateTo(event.route)
                    is UiEvent.NavigateBack -> navigateBack()
                }
            }
        }
    }
}

// Fragment 中使用
viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiEvents.collect { /* 处理事件 */ }
    }
}

// 权限请求结果
class PermissionViewModel : ViewModel() {
    private val _permissionResult = MutableSharedFlow<PermissionResult>(replay = 0)
    val permissionResult: SharedFlow<PermissionResult> = _permissionResult
}

面试加分

  • LiveData 事件会在配置变更后重复发射
  • SharedFlow(replay=0) 无缓冲,有消费者才能接收
  • repeatOnLifecycle 确保只在 STARTED 状态收集
  • replay=1 可让新订阅者收到最近一次状态
  • 权限请求结果、Toast/Snackbar、导航事件都是一次性事件的典型场景

4.4 expect/actual 在 KMP 中怎么用?

核心:expect 声明公共接口,actual 提供平台实现。接口设计要谨慎,确保签名完全匹配。

kotlin 复制代码
// commonMain
expect class DateFormatter {
    fun formatDate(timestamp: Long): String
    fun formatTime(timestamp: Long): String
    fun formatDateTime(timestamp: Long): String
}

// androidMain
actual class DateFormatter {
    private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
    private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
    private val dateTimeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
    
    actual fun formatDate(timestamp: Long) = dateFormat.format(Date(timestamp))
    actual fun formatTime(timestamp: Long) = timeFormat.format(Date(timestamp))
    actual fun formatDateTime(timestamp: Long) = dateTimeFormat.format(Date(timestamp))
}

// iosMain
actual class DateFormatter {
    private val dateFormatter = NSDateFormatter().apply { dateFormat = "yyyy-MM-dd" }
    private val timeFormatter = NSDateFormatter().apply { dateFormat = "HH:mm:ss" }
    
    actual fun formatDate(timestamp: Long): String {
        return dateFormatter.stringFromDate(Date(timestamp / 1000.0))
    }
    actual fun formatTime(timestamp: Long): String {
        return timeFormatter.stringFromDate(Date(timestamp / 1000.0))
    }
    actual fun formatDateTime(timestamp: Long): String {
        return formatDate(timestamp) + " " + formatTime(timestamp)
    }
}

Android 实战

kotlin 复制代码
// 坑 1:expect 类不能有默认实现
expect class Logger {
    fun log(message: String) // ❌ 不能写实现
}

// 正确
expect class Logger {
    fun log(message: String)
}

// 坑 2:actual 必须完全匹配签名
expect class Parser {
    fun parse(date: String): Long
}
// ❌ 错误:参数类型不匹配
actual class Parser {
    actual fun parse(timestamp: Long): String
}
// ✅ 正确
actual class Parser {
    actual fun parse(date: String): Long
}

// 坑 3:平台实现差异过大
// 如果 Android 和 iOS 的行为差异太大,考虑拆分接口

面试加分

  • expect/actual 支持函数、类、属性
  • 平台实现差异过大时应考虑拆分接口
  • Kotlin 1.9+ 改进了 expect/actual 语法
  • 测试需要在每个平台编写集成测试
  • expect class 可以有 primary constructor

五、Android 实战场景深度

5.1 ViewModel 创建和销毁过程有哪些注意点?

核心 :by viewModels() 在 onViewCreated 时才初始化;SavedStateHandle 是配置变更后状态恢复的关键;理解 ViewModel 的生命周期边界。

kotlin 复制代码
// by viewModels() Lazy 实现
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> ViewModelProvider.Factory)?
): Lazy<VM> = ViewModelLazy(
    VM::class,                           // ViewModel 类
    { viewModelStore },                  // ViewModelStore
    { factory ?: getDefaultViewModelProviderFactory() } // Factory
)

// ViewModelProvider.Factory 默认实现
class NewInstanceFactory : ViewModelProvider.Factory {
    @Suppress("ClassNewInstance")
    public open fun <T : ViewModel> create(modelClass: Class<T>): T {
        return modelClass.newInstance()
    }
}

Android 实战

kotlin 复制代码
// 带参数的 ViewModel
class DetailViewModel(
    private val savedStateHandle: SavedStateHandle,
    private val userId: String
) : ViewModel() {
    
    // SavedStateHandle 的使用
    val user: String? = savedStateHandle["user_id"]
    
    fun saveData(data: String) {
        savedStateHandle["key"] = data
    }
    
    class Factory(private val userId: String) : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return DetailViewModel(
                SavedStateHandle.createHandle(null, null),
                userId
            ) as T
        }
    }
}

class DetailFragment : Fragment() {
    private val userId: String by navArgs()
    private val viewModel: DetailViewModel by viewModels {
        DetailViewModel.Factory(userId)
    }
    
    // ⚠️ onCreate 中访问可能还未初始化
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // viewModel 可能还未初始化!
    }
    
    // ✅ onViewCreated 后才初始化
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.initialize()
    }
}

面试加分

  • onCreate 中访问 viewmodel 可能还未初始化,应在 onViewCreated 之后
  • SavedStateHandle 依赖 CreationExtras 注入
  • ViewModel 销毁只因配置变更,onCleared() 只在进程被销毁时调用
  • Koin/Hilt 等 DI 框架简化了参数传递
  • ViewModelProvider 用 key 区分不同实例

5.2 Delegates.observable 和 vetoable 实用场景?

核心 :observable 在属性变更 触发回调,适合日志、验证、联动更新;vetoable 在变更可否决,适合输入校验。

kotlin 复制代码
// observable:监听变更
var count by observable(0) { property, oldValue, newValue ->
    println("${property.name}: $oldValue -> $newValue")
}

count = 1 // 输出:Property count: 0 -> 1
count = 2 // 输出:Property count: 1 -> 2

// vetoable:变更前否决
var age by vetoable(0) { property, oldValue, newValue ->
    newValue in 0..150 // 拒绝不合理的年龄
}

age = 25  // OK
age = 200 // 被否决,age 仍为 25

Android 实战

kotlin 复制代码
// 配置变更监听
class SettingsViewModel : ViewModel() {
    var theme by observable(Theme.LIGHT) { _, old, new ->
        if (old != new) _themeChanged.tryEmit(new)
    }
    
    var language by observable(Language.EN) { _, old, new ->
        if (old != new) _languageChanged.tryEmit(new)
    }
    
    private val _themeChanged = MutableSharedFlow<Theme>(replay = 0)
    val themeChanged: SharedFlow<Theme> = _themeChanged
}

// RecyclerView Adapter 数据变更
class SmartAdapter : RecyclerView.Adapter<SmartAdapter.ViewHolder>() {
    private var _items: List<Item> by observable(emptyList()) { _, oldList, newList ->
        val diffResult = DiffUtil.calculateDiff(ItemDiffCallback(oldList, newList))
        submitList(newList)
        diffResult.dispatchUpdatesTo(this)
    }
    
    val items: List<Item> get() = _items
    fun updateItems(newItems: List<Item>) {
        _items = newItems // 自动触发 diff
    }
}

// 权限管理
class PermissionManager {
    var grantedPermissions by vetoable(emptySet<String>()) { _, _, newValue ->
        // 权限只能增加不能减少(简化版)
        true
    }
    
    fun grantPermission(permission: String) {
        grantedPermissions = grantedPermissions + permission
    }
}

面试加分

  • observable 适合日志记录、跨组件联动、状态同步
  • vetoable 适合输入校验(但更好的做法是用函数验证)
  • Kotlin 属性代理支持自定义 getValue/setValue
  • 配合 StateFlow/SharedFlow 可实现响应式数据流
  • DiffUtil.calculateDiff 需要 oldList 和 newList

5.3 如何实现类型安全的 Builder 模式?

核心 :用 DSL 风格 + 扩展函数,比 Java Builder 更简洁、类型安全更好。

kotlin 复制代码
class HttpRequest private constructor(
    val url: String,
    val method: String,
    val headers: Map<String, String>,
    val body: String?
) {
    class Builder {
        private var url = ""
        private var method = "GET"
        private val headers = mutableMapOf<String, String>()
        private var body: String? = null
        
        fun url(url: String) = apply { this.url = url }
        fun method(method: String) = apply { this.method = method }
        fun header(key: String, value: String) = apply { headers[key] = value }
        fun body(body: String) = apply { this.body = body }
        fun build() = HttpRequest(url, method, headers, body)
    }
}

fun httpRequest(block: HttpRequest.Builder.() -> Unit): HttpRequest {
    return HttpRequest.Builder().apply(block).build()
}

// 使用
val request = httpRequest {
    url("https://api.example.com/users")
    method("POST")
    header("Content-Type", "application/json")
    header("Authorization", "Bearer token")
    body("""{"name": "Alice"}""")
}

Android 实战

kotlin 复制代码
// Retrofit Interceptor 链配置
class InterceptorChainBuilder {
    private val interceptors = mutableListOf<Interceptor>()
    
    fun addInterceptor(interceptor: Interceptor) = interceptors.add(interceptor)
    
    fun addInterceptor(block: Request.() -> Request) {
        interceptors.add { chain ->
            chain.proceed(chain.request.block())
        }
    }
    
    fun build(): List<Interceptor> = interceptors.toList()
}

fun interceptorChain(block: InterceptorChainBuilder.() -> Unit): List<Interceptor> {
    return InterceptorChainBuilder().apply(block).build()
}

val chain = interceptorChain {
    addInterceptor { chain ->
        println("Request: ${chain.request.url}")
        chain.proceed(chain.request)
    }
    addInterceptor { chain ->
        val auth = chain.request.header("Authorization")
            ?: return@addInterceptor chain.proceed(
                chain.request.newBuilder()
                    .header("Authorization", "Bearer token")
                    .build()
            )
        chain.proceed(chain.request)
    }
    addInterceptor { chain ->
        var request = chain.request
        repeat(3) {
            try {
                return@addInterceptor chain.proceed(request)
            } catch (e: IOException) {
                if (it == 2) throw e
            }
        }
        chain.proceed(request)
    }
}

面试加分

  • DSL 用 apply 块传递配置块,语法更自然
  • 私有构造函数强制通过 Builder 创建
  • Kotlin 的 lambda 尾调用语法使 DSL 更优雅
  • 可嵌套 DSL 处理复杂配置结构
  • Retrofit OkHttpClient 配置常用 DSL 风格

5.4 Kotlin 哪些特性是 Compose 的基础?

核心 :Compose 深度依赖 Kotlin 的多个特性:inline (性能优化)、reified (remember)、协程 (LaunchedEffect)、扩展函数 (Modifier)、委托(mutableStateOf by)。

kotlin 复制代码
// @Composable 本质:编译器变换为状态机
@Composable
fun Greeting(name: String) {
    Text("Hello, $name!")
}

// 编译器生成等价的状态机(简化)
class GreetingComposer(val composer: Composer, var changed: Int = 0) {
    operator fun invoke(name: String) {
        composer.start(123456) // 组 ID
        if (changed != composer.changed) {
            Text("Hello, $name!")
        }
        composer.end()
    }
}

Android 实战

kotlin

kotlin 复制代码
@Composable
fun DataLoader(url: String) {
    var data by remember { mutableStateOf<String?>(null) }
    var error by remember { mutableStateOf<Throwable?>(null) }
    
    LaunchedEffect(url) {
        try {
            data = fetchData(url)
        } catch (e: Exception) {
            error = e
        }
    }
    
    DisposableEffect(Unit) {
        val job = scope.launch { /* 后台任务 */ }
        onDispose { job.cancel() }
    }
}

// derivedStateOf 性能优化
@Composable
fun TodoList(todos: List<Todo>) {
    // 反面:每次重组都过滤
    val filteredTodos = todos.filter { it.isCompleted }
    
    // 正面:只在 todos 变化时重新计算
    val filteredTodos = remember(todos) {
        derivedStateOf { todos.filter { it.isCompleted } }
    }
}

// remember vs rememberSaveable
@Composable
fun Example() {
    var count by remember { mutableStateOf(0) }
    var config by rememberSaveable { mutableStateOf(Config()) }
    // remember: 重组时保持
    // rememberSaveable: 配置变更后也保持
}

// Kotlin 特性在 Compose 中的用途
// | Kotlin 特性    | Compose 中的用途              |
// |--------------|----------------------------|
// | inline       | 性能优化,减少对象创建            |
// | reified      | remember { derivedStateOf }  |
// | 协程          | LaunchedEffect, rememberCoroutineScope |
// | 扩展函数       | Modifier.extension functions |
// | 委托          | mutableStateOf by            |

面试加分

  • remember 跨重组保持状态,rememberSaveable 配置变更也保持
  • LaunchedEffect 依赖 key 变化时重启协程
  • derivedStateOf 只在依赖变化时重新计算,减少不必要的重组
  • Compose Modifier 本质是扩展函数链:Modifier.padding().background().clickable()
  • @Composable 函数不能递归调用,不能有默认值参数

5.5 伴生对象(companion object)高级用法?

核心 :实现 Factory 模式接口实现扩展函数,配合 @JvmStatic/@JvmField 控制 Java 互操作。

kotlin 复制代码
class NetworkClient private constructor(
    private val baseUrl: String,
    private val timeout: Int
) {
    companion object : ViewModelProvider.Factory {
        private const val DEFAULT_URL = "https://api.example.com"
        private const val DEFAULT_TIMEOUT = 30_000
        
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return when {
                modelClass.isAssignableFrom(NetworkClient::class.java) -> {
                    NetworkClient(DEFAULT_URL, DEFAULT_TIMEOUT) as T
                }
                else -> throw IllegalArgumentException("Unknown: $modelClass")
            }
        }
        
        fun create(baseUrl: String, timeout: Int = DEFAULT_TIMEOUT) = 
            NetworkClient(baseUrl, timeout)
    }
}

Android 实战

kotlin 复制代码
// 伴生对象扩展函数
class User private constructor(val name: String, val age: Int) {
    companion object // 空伴生对象,允许外部扩展
}

fun User.Companion.create(name: String, age: Int): User {
    require(age >= 0) { "Age must be non-negative" }
    return User(name, age)
}

fun User.Companion.createRandom(): User {
    return User.create(listOf("Alice", "Bob").random(), Random.nextInt(0, 100))
}

// Room TypeConverter
class Converters {
    companion object {
        @JvmStatic fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) }
        @JvmStatic fun dateToTimestamp(date: Date?): Long? = date?.time
        @JvmStatic fun fromStringList(value: String?): List<String> = value?.split(",") ?: emptyList()
        @JvmStatic fun toStringList(list: List<String>?): String? = list?.joinToString(",")
    }
}

// @JvmField vs @JvmStatic
class Constants {
    companion object {
        @JvmField val API_KEY = "abc123" // 直接访问 Constants.API_KEY
        @JvmStatic var baseUrl = "https://api.com" // Constants.getBaseUrl()
        val version = "1.0" // Constants.Companion.getVersion()
    }
}

// Fragment newInstance
class DetailFragment : Fragment() {
    private val args: DetailFragmentArgs by navArgs()
    
    companion object {
        private const val ARG_USER_ID = "user_id"
        private const val ARG_FROM_NOTIFICATION = "from_notification"
        
        fun newInstance(userId: String, fromNotification: Boolean = false): DetailFragment {
            return DetailFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_USER_ID, userId)
                    putBoolean(ARG_FROM_NOTIFICATION, fromNotification)
                }
            }
        }
    }
}

面试加分

  • 伴生对象实现接口时,接口方法即静态方法
  • @JvmField 直接生成静态字段,@JvmStatic 生成静态方法
  • 私有伴生对象可被外部扩展
  • Fragment.newInstance 工厂方法推荐放在 companion object
  • @JvmOverloads 可生成多个重载方法

5.6 object 表达式在回调场景的最佳实践?

核心 :Lambda 用于单方法接口 ,object 表达式用于多方法接口 或需要持有状态 的场景。警惕内存泄漏------匿名 object 持有外部类引用。

kotlin 复制代码
// Lambda:单方法接口
view.setOnClickListener { /* 处理点击 */ }
viewModelScope.launch { /* 协程 */ }

// object:多方法或需持有状态
val gestureDetector = GestureDetector(context, object : OnGestureListener {
    override fun onDown(e: MotionEvent) = true
    override fun onShowPress(e: MotionEvent) { }
    override fun onSingleTapUp(e: MotionEvent) = false
    override fun onScroll(...) = false
    override fun onLongPress(e: MotionEvent) { }
    override fun onFling(...) = false
})

Android 实战

kotlin

kotlin 复制代码
// 防止内存泄漏:使用 lifecycleScope + 生命周期感知
class MyFragment : Fragment() {
    fun startTimerSafe() {
        val owner = viewLifecycleOwner
        lifecycleScope.launch {
            delay(1000)
            if (owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                updateUI()
            }
        }
    }
    
    // 反面:Handler.postDelayed 可能泄漏
    fun startTimerUnsafe() {
        handler.postDelayed(object : Runnable {
            override fun run() {
                // 匿名对象持有 Activity 引用
                updateUI() // Activity 已销毁时崩溃
            }
        }, 1000)
    }
}

// ViewPager2 多状态监听
viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
    override fun onPageSelected(position: Int) {
        updateIndicator(position)
        sendAnalytics("page_view", position)
    }
    
    override fun onPageScrollStateChanged(state: Int) {
        when (state) {
            SCROLL_STATE_IDLE -> { /* 滚动结束 */ }
            SCROLL_STATE_DRAGGING -> { /* 开始拖动 */ }
            SCROLL_STATE_SETTLING -> { /* 惯性滚动 */ }
        }
    }
    
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
        updateParallax(position, positionOffset)
    }
})

// Animation AnimatorListener
animator.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationStart(animation: Animator) {
        view.visibility = View.VISIBLE
    }
    override fun onAnimationEnd(animation: Animator) {
        onComplete()
    }
    // AnimatorListenerAdapter 只重写需要的方法
})

面试加分

  • 匿名 object 持有外部类引用,可能导致 Activity/Fragment 内存泄漏
  • Handler.postDelayed 用 object 表达式要小心生命周期
  • DisposableEffect 替代手动清理,更安全
  • AndroidX 提供 AnimatorListenerAdapter 只重写需要的方法
  • registerOnPageChangeCallback 需要在 onDestroyView 注销
相关推荐
像风一样自由202015 分钟前
量化压缩实战:INT8 / INT4 / AWQ / GPTQ 全面对比
android·人工智能·语言模型·大模型
brycegao32138 分钟前
Android MVI进阶:纯原生实现Slot化可插拔架构
android·kotlin·架构设计·mvi·viewmodel
2601_961194021 小时前
27考研资料|百度网盘|夸克网盘
android·xml·考研·ios·iphone·xcode·webview
故渊at1 小时前
第二板块:Android 四大组件标准化学理 | 第十篇:ContentProvider 数据共享与 SQLite 引擎
android·jvm·数据库·sqlite·contentprovider
Kapaseker1 小时前
你遇到过 Kotlin 协程中的竞争问题吗?
android·kotlin
与水同流1 小时前
Android13 AIDL HAL服务实现Demo
android·hal·aidl
AsiaLYF1 小时前
Kotlin MutableSharedFlow: emit vs tryEmit 详解
开发语言·前端·kotlin
吴梓穆1 小时前
Python 基础语法2 if 运算符 循环
android·开发语言·python
流星白龙2 小时前
【MySQL高阶】27.事务(2)-锁
android·mysql·adb
我命由我123452 小时前
Kotlin 开发 - Kotlin 反引号转义关键字
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime