小米Kotlin专项面试:小米Android开发覆盖MIUI系统定制、IoT设备互联。技术栈偏底层,面试官喜欢问系统级原理和性能数据。技术方向偏向MIUI定制、IoT互联。特色专项包括MIUI系统定制与IoT设备开发。今天8道题覆盖小米Kotlin专项面试核心考点。
Q1:data class自动生成了什么?
equals(基于构造参数的值比较而非引用)、hashCode、toString、copy(浅拷贝,可指定修改某些字段)、componentN(支持解构声明)。限制:主构造至少一个参数,参数必须val/var,不能abstract/open/sealed/inner。
data class User(val name: String, var age: Int)
val u2 = u1.copy(age = 26)
val (name, age) = u2
深度解析:这道题属于Kotlin的基础盘,面试官想确认你不是只会调 API。回答时先一句话给定义,再结合小米的澎湃 OS / HyperOS 与跨端体验。业务场景举例,最后补一个你做过的案例或踩过的坑,比背结论更得分。
追问:data class能继承吗?不能继承其他data class,也不能被其他类继承——因为会自动生成final的equals等方法。
Q2:Kotlin空安全和平台类型?
Kotlin类型系统严格区分可空(String?)和非空(String)。安全调用操作符?.、Elvis操作符?:、非空断言!!。平台类型(String!)来自Java互操作——Kotlin编译器不强制空检查,需要开发者自己判断。lateinit用于延迟初始化的非空var,访问前必须赋值。
val len: Int? = user?.address?.city?.length
val l = len ?: 0
val force = user!!.name
深度解析:Kotlin 相比 Java 更简洁安全:空安全(? 与 !!)、data class、扩展函数、高阶函数、协程、sealed class。字节码层面仍跑在 JVM/ART。容易踩的坑是:滥用 !! 会空指针;内联函数减少高阶函数开销但代码会膨胀。;面试官常追问:追问:Kotlin 和 Java 互操作注意什么?@JvmOverloads、@JvmStatic、可空性映射。。
追问:lateinit和by lazy的区别?lateinit是var,手动初始化,用于依赖注入等场景;lazy是val,首次访问自动初始化。
Q3:Kotlin value class(内联类)的原理和用途?
value class(原inline class)在编译时被内联为底层类型,运行时零额外内存开销。用途:类型安全包装——用value class UserId(val id: Long)而不是裸Long,编译时防止String和UserId混用,运行时就是Long没有包装对象。限制:只能有一个属性、不能有init块中的复杂逻辑、不能参与继承。
@JvmInline
value class UserId(val value: Long)
@JvmInline
value class Email(val value: String)
fun findUser(id: UserId) { ... } // 不会传错成Email
追问:value class和data class包装的区别?data class会生成包装对象(有内存开销),value class编译后完全内联为零开销。
Q4:密封类的优势和使用场景?
密封类(sealed class)限制所有子类必须在同一文件或同一模块内定义,编译时就能穷举所有子类。配合when表达式时编译器强制检查是否覆盖了所有情况,不需要写else分支。最适合表示有限状态集——网络状态、UI状态、操作结果等。
sealed class UiState {
object Loading : UiState()
data class Success(val data: String) : UiState()
data class Error(val msg: String) : UiState() }
追问:密封类和枚举的核心区别?枚举是单例无状态,密封类子类可以有不同字段、创建多个实例。
Q5:Kotlin委托模式(class delegation)?
Kotlin用by关键字实现委托模式,编译器自动生成转发方法。interface Base { fun print() }; class BaseImpl : Base { ... }; class Derived(b: Base) : Base by b——Derived把Base的方法委托给b,不用手写转发代码。应用场景:多个类需要相同行为但不同实现时,用委托代替继承,更灵活(运行时可替换委托对象)。
interface Logger { fun log(msg: String) }
class FileLogger : Logger { override fun log(msg: String) { ... } }
class MyService(logger: Logger) : Logger by logger
// 也可只委托部分方法,自己实现其余的
追问:委托和继承什么时候选哪个?is-a关系用继承,has-a关系用委托。Kotlin的by让委托代码量跟继承一样少。
Q6:inline内联函数的原理?
inline函数在编译时把函数体直接复制到调用处,消除函数调用开销。关键用途:高阶函数中避免lambda生成匿名内部类对象。noinline修饰不想内联的lambda参数,crossinline禁止lambda中的非局部return。reified配合inline让泛型在运行时可用。
inline fun <T, R> T.let(block: (T) -> R): R = block(this)
追问:什么情况不该用inline?函数体很大且调用点多→字节码体积膨胀。JVM本身对短方法有JIT内联。
Q7:Kotlin Multiplatform(KMP)的原理和实践?
KMP允许用Kotlin写共享代码,编译到Android/iOS/JS等平台。核心机制:common模块写平台无关逻辑(expect声明),platform模块写平台实现(actual实现)。共享的是业务逻辑(Model/Repository/UseCase),UI各平台自写(Android Compose/iOS SwiftUI)。Kotlin/Native编译到iOS机器码,Kotlin/JVM编译到Android字节码。
// commonMain
expect class Platform() { fun name(): String }
// androidMain
actual class Platform { actual fun name() = "Android ${Build.VERSION.SDK_INT}" }
// iosMain
actual class Platform { actual fun name() = "iOS" }
追问:KMP和Flutter的定位差异?KMP共享逻辑层各平台写原生UI,Flutter整套UI自绘——KMP更原生但需要各平台UI开发,Flutter一套代码全平台。
Q8:委托属性by的实现原理?
by关键字把属性的getter/setter委托给另一个对象处理。编译器会生成getValue/setValue的调用。内置委托:lazy(懒加载,首次访问时初始化)、observable(值变化监听)、vetoable(变化前可拦截)。自定义委托只需实现ReadOnlyProperty或ReadWriteProperty接口。
val data by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { fetchData() }
var name by Delegates.observable("") { _, old, new -> log("$old -> $new") }
追问:lazy的三种线程安全模式?SYNCHRONIZED用双重检查锁(默认)、PUBLICATION允许多次初始化取第一个完成的值、NONE无同步。
下一篇进入小米性能优化专项——RecyclerView滑动性能优化、耗电优化的最佳实践、内存优化进阶。
觉得有用的同学扣1,你面试被追问最狠的是哪道题?
本系列连载中,关注不迷路,下一篇:小米性能优化专项面经:RecyclerView滑动性能优化、耗电优化的最佳实践、内存优化进阶
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。