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 注销
相关推荐
StarShip7 小时前
第一阶段:应用层视图绘制
android
StarShip7 小时前
第二阶段:RenderThread 渲染处理
android
通玄7 小时前
Jetpack Compose 入门系列(一):从零搭建到基础控件使用
android
StarShip7 小时前
Android 图形渲染流水线完整架构与执行流程分析
android
流年如夢7 小时前
类和对象(上)
android·java·开发语言
用户86022504674728 小时前
从入门到进阶的 React Native 实战指南
android·前端
沐言人生8 小时前
ReactNative 源码分析10——Native View创建流程createView
android·react native
问心无愧05138 小时前
ctf show web入门98
android·前端·笔记