Kotlin核心:空安全都搞不明白,还敢说熟练Kotlin?

28 阅读4分钟

Kotlin核心:空安全都搞不明白,还敢说熟练Kotlin?

本文覆盖Kotlin语言8个核心知识点,每个知识点包含核心回答、原理分析、Android实战场景和面试加分点。

1. Kotlin空安全机制

核心回答

Kotlin通过可空类型系统将空指针异常从运行时提前到编译期检测。核心操作符包括:

  • T?:可空类型声明
  • ?.:安全调用,操作数为null时返回null
  • !!:非空断言,强制要求非空,为null时抛NPE
  • ?::Elvis操作符,null时提供默认值
  • let {}:空安全的作用域函数

原理/代码

// 可空类型声明
val name: String? = null

// 安全调用 ?. - 短路求值
val length: Int? = name?.length  // null

// Elvis操作符 ?:
val safeLength: Int = name?.length ?: 0  // 0

// let函数:非空时执行代码块
name?.let {
    println("名字长度为: ${it.length}")
}

// !! 操作符 - 明确知道非空时才用
val definitelyNotNull: String = name!!  // 若name为null则抛NPE

NPE产生的官方场景(据Kotlin官方文档):

// 场景1: 显式抛出
throw NullPointerException()

// 场景2: !! 操作符
val b: String? = null
val l = b!!.length  // NullPointerException(Kotlin 1.4+统一抛出NPE)

// 场景3: 初始化不一致(this泄露)
class MyActivity : Activity() {
    lateinit var view: View
    
    // 构造函数中this被泄露给其他对象
    val helper = Helper(this)
}

// 场景4: Java互操作:平台类型
val javaClass = JavaClass()
javaClass.platformTypeMethod()  // 返回平台类型,可能为null

Android实战场景

// Android中的空安全实践
class UserRepository(private val api: UserApi) {
    
    suspend fun getUser(id: String): Result<User> {
        return try {
            val response = api.getUser(id)
            // 安全调用链
            Result.Success(response.data)
        } catch (e: Exception) {
            Result.Error(e)
        }
    }
}

// Fragment参数获取 - 安全处理可空Bundle
class UserFragment : Fragment() {
    
    companion object {
        private const val ARG_USER_ID = "user_id"
        
        fun newInstance(userId: String?): UserFragment {
            return UserFragment().apply {
                arguments = bundleOf(ARG_USER_ID to userId)
            }
        }
    }
    
    private val userId: String?
        get() = arguments?.getString(ARG_USER_ID)
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 避免嵌套判空
        userId?.let { id ->
            viewModel.loadUser(id)
        } ?: run {
            showError("用户ID无效")
        }
    }
}

面试加分点

  • 明确Kotlin的NPE来源只有4种官方场景
  • ?.let vs if (x != null):前者适合链式调用,后者适合复杂条件分支
  • Android中lateinit varby lazy的空安全区别:lateinit未初始化时访问会抛UninitializedPropertyAccessException
  • 平台类型(Platform Type)是Kotlin调用Java代码时的特殊类型,编译器无法保证非空

2. Kotlin集合体系

核心回答

Kotlin集合分为不可变(只读)和可变两组,底层对应Java集合:

Kotlin底层实现特性
List<T>java.util.List只读,不可添加/删除元素
MutableList<T>java.util.ArrayList可读写
Sequence<T>自定义迭代器惰性求值,按元素处理
setOf() / mutableSetOf()LinkedHashSet唯一元素
mapOf() / mutableMapOf()LinkedHashMap键值对

原理/代码

// 不可变与可变的区别
val readOnlyList: List<String> = listOf("a", "b", "c")
// readOnlyList.add("d")  // 编译错误

val mutableList: MutableList<String> = mutableListOf("a", "b", "c")
mutableList.add("d")  // 正常

// List vs MutableList的继承关系
// List<out T> - 协变,只读
// MutableList<T> : List<T>, MutableCollection<T> - 既可读又可写

// 视图转换
val mutable = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = mutable  // 只读视图

Sequence惰性求值原理(据Kotlin官方文档):

// Iterable处理方式:先完成整个map,再进行filter
listOf(1, 2, 3, 4, 5)
    .map { it * 2 }    // [2, 4, 6, 8, 10] - 先完全计算
    .filter { it > 5 } // [6, 8, 10] - 再完全计算

// Sequence处理方式:逐元素处理,遇到终止操作才执行
sequenceOf(1, 2, 3, 4, 5)
    .map { it * 2 }    // 只是描述操作
    .filter { it > 5 } // 只是描述操作
    .toList()          // 终止操作:逐元素处理

// 短路操作示例
sequenceOf(1, 2, 3, 4, 5)
    .map { it * 2 }
    .filter { it > 5 }
    .take(2)           // 只需2个结果,处理到第3个元素就停止
    .toList()          // [6, 8]

Android实战场景

// RecyclerView数据源处理
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
    
    private var users: List<User> = emptyList()  // 外部传入只读列表
    
    fun submitList(newUsers: List<User>) {
        users = newUsers  // 直接替换引用
        notifyDataSetChanged()
    }
    
    // 列表操作返回新列表
    fun getActiveUsers(): List<User> {
        return users.filter { it.isActive }  // 返回新List
    }
}

// 数据转换管道 - 使用Sequence优化
class UserMapper {
    fun mapUserResponses(responses: List<UserResponse>): List<User> {
        return responses
            .asSequence()  // 转为Sequence处理大数据量
            .filter { it.isActive }
            .map { it.toUser() }
            .sortedBy { it.name }
            .toList()
    }
}

// Android中的Map使用
class CacheManager {
    private val cache: MutableMap<String, Any> = mutableMapOf()
    
    fun put(key: String, value: Any) {
        cache[key] = value  // 操作符重载,等同于put
    }
    
    fun getOrCompute(key: String, compute: () -> Any): Any {
        return cache[key] ?: compute().also { cache[key] = it }
    }
}

面试加分点

  • Sequence的中间操作(mapfilter等)是惰性的,只有遇到终止操作(toListfirst等)才会执行
  • 小数据量用Iterable效率更高,因为Sequence有lambda调用开销
  • 大数据量或长操作链优先用Sequence
  • Kotlin集合与Java完全互操作,但注意Java的List<String>在Kotlin中是平台类型

3. Kotlin泛型

核心回答

Kotlin泛型采用声明处型变(declaration-site variance),使用out/in修饰符在定义时声明型变;JVM层同样存在类型擦除,但reified修饰符配合inline函数可在运行时保留类型信息。

原理/代码

// 类型擦除示例
class Box<T>(val value: T)

val box1: Box<String> = Box("hello")
val box2: Box<Int> = Box(123)
// 运行时都是 Box<*> ,无法通过value判断T的类型

// reified + inline 保留运行时类型
inline fun <reified T> isType(value: Any): Boolean {
    return value is T  // T在运行时可用
}

println(isType<String>("hello"))  // true
println(isType<String>(123))     // false

// 型变声明
interface Producer<out T> {
    fun produce(): T  // T只出现在out位置
}

interface Consumer<in T> {
    fun consume(item: T)  // T只出现在in位置
}

// 协变: Producer<String> 是 Producer<Any> 的子类型
val stringProducer: Producer<String> = StringProducer()
val anyProducer: Producer<Any> = stringProducer  // 合法

// 逆变: Consumer<Any> 是 Consumer<String> 的子类型
val anyConsumer: Consumer<Any> = AnyConsumer()
val stringConsumer: Consumer<String> = anyConsumer  // 合法

与Java泛型的区别

// Java: use-site variance (通配符)
// void copy(Collection<? extends T> from, Collection<? super T> to)

// Kotlin: declaration-site variance (声明处型变)
// class Producer<out T> - 在类定义时声明
// 使用时不需要写?
val strings: List<String> = listOf("a", "b")
val any: List<Any> = strings  // List<String>是List<Any>的子类型,协变

// Kotlin仍支持use-site投影
fun copy(from: Array<out Any>, to: Array<Any>) { }
// Array<out Any> 等同于 Java的 Array<? extends Object>

Android实战场景

// Android ViewModel中的泛型约束
abstract class BaseViewModel<State : UiState> : ViewModel() {
    protected val _state = MutableStateFlow(createInitialState())
    val state: StateFlow<State> = _state.asStateFlow()
    
    abstract fun createInitialState(): State
}

// 使用where子句约束多个上界
interface Comparable<T> {
    fun compareTo(other: T): Int
}

fun <T> maxOf(a: T, b: T): T 
    where T : Comparable<T>, T : Any {
    return if (a.compareTo(b) > 0) a else b
}

// Retrofit风格的网络请求泛型封装
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
}

suspend inline fun <reified T> apiCall(
    crossinline block: suspend () -> T
): Result<T> {
    return try {
        Result.Success(block())
    } catch (e: Exception) {
        Result.Error(e)
    }
}

// 使用
suspend fun getUser(): Result<User> = apiCall { api.getUser() }

面试加分点

  • reified必须配合inline使用,因为类型信息在编译期被内联到调用处
  • reified的局限:不能用于类声明、不能用于非内联函数
  • Java的<? extends T>等价于Kotlin的out T<? super T>等价于in T
  • Kotlin 1.1+支持类型别名中的泛型:typealias StringList = List<String>

4. 扩展函数

核心回答

扩展函数是静态解析的语法糖,本质是在编译时生成以接收者为参数的静态方法。它不能访问类的private/protected成员,与同名成员函数冲突时,成员函数优先。

原理/代码

// 扩展函数本质:编译器生成静态方法
// fun String.isEmail(): Boolean { ... }
// 编译后等价于
// class StringUtils { companion object { 
//     @JvmStatic fun isEmail(form: String): Boolean { ... }
// }}

// 可空接收者
fun String?.isNullOrEmpty(): Boolean {
    return this == null || this.isEmpty()
}

// 扩展属性
val String.lastChar: Char
    get() = this[length - 1]

// 不能访问private成员(据Kotlin官方文档)
class User(val name: String) {
    private val password: String = "secret"
    
    fun publicMethod() = "public"  // 公开方法
}

fun User.isSecure(): Boolean {
    // return password.length  // 编译错误:不能访问private
    return publicMethod().isNotEmpty()  // OK:通过公开API访问
}

// 成员函数优先(据Kotlin官方文档)
class Example {
    fun foo() = "member"
}

fun Example.foo() = "extension"

// 调用时
val e = Example()
println(e.foo())  // 输出: member,成员函数优先

Android实战场景

// Context扩展简化Toast和Log
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

fun View.showToast(message: String) {
    context.showToast(message)
}

// Fragment中使用
class MyFragment : Fragment() {
    fun onButtonClick() {
        showToast("操作成功")  // 自动推导context
        view?.showToast("视图已加载")  // View扩展
    }
}

// View扩展用于绑定数据
fun <T : TextView> T.setTextOrHide(text: String?) {
    if (text.isNullOrEmpty()) {
        visibility = View.GONE
    } else {
        this.text = text
        visibility = View.VISIBLE
    }
}

// 集合的安全扩展
fun <T> List<T>.getOrDefault(index: Int, default: T): T {
    return if (index in indices) this[index] else default
}

// LiveData扩展
inline fun <T> LiveData<T>.observeNotNull(
    owner: LifecycleOwner,
    crossinline observer: (T) -> Unit
) {
    observe(owner) { it?.let { observer(it) } }
}

面试加分点

  • 扩展函数不是真正的类成员,不会被继承覆盖
  • 扩展函数的解析是静态的,取决于变量的声明类型而非运行时类型
  • 扩展函数可以访问同文件内的private顶层声明
  • 在不同包使用需要显式导入,导入时可使用as重命名避免冲突

5. 数据类data class

核心回答

data class自动生成equals()hashCode()toString()copy()以及解构用的componentN()函数。copy()是浅拷贝,嵌套可变对象需要手动实现深拷贝。

原理/代码

data class User(
    val id: Int,
    val name: String,
    val email: String
)

// 编译器自动生成
class User(
    val id: Int,
    val name: String,
    val email: String
) {
    // toString: "User(id=1, name=Alice, email=alice@example.com)"
    // equals: 基于id、name、email比较
    // hashCode: 与equals一致
    // copy: 创建副本,可指定新值
    // componentN: 解构函数
}

// copy的浅拷贝问题(据Kotlin官方文档)
data class Address(var city: String)
data class Person(val name: String, val address: Address)

val person1 = Person("Alice", Address("Beijing"))
val person2 = person1.copy()  // 浅拷贝

person2.address.city = "Shanghai"

println(person1.address.city)  // "Shanghai" - 原对象也被修改!

// 正确实现深拷贝
data class PersonWithDeepCopy(
    val name: String,
    val address: Address
) {
    fun deepCopy(
        name: String = this.name,
        address: Address = this.address.copy()  // 递归copy
    ) = PersonWithDeepCopy(name, address)
}

componentN与解构

data class Point(val x: Int, val y: Int)

val point = Point(10, 20)

// 解构声明
val (a, b) = point
println("$a, $b")  // 10, 20

// 只解构部分
val (x, _) = point  // 只取x,忽略y

// map遍历中的解构
val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {
    println("$key -> $value")
}

Android实战场景

// UI状态建模
data class UserListUiState(
    val isLoading: Boolean = false,
    val users: List<User> = emptyList(),
    val errorMessage: String? = null
)

// Sealed class与data class结合
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String, val code: Int) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

// Intent/Event建模
data class LoginEvent(
    val type: EventType,
    val timestamp: Long = System.currentTimeMillis()
) {
    enum class EventType { LOGIN_SUCCESS, LOGIN_FAILED, SESSION_EXPIRED }
}

// Room Entity中使用data class
@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: Int,
    val name: String,
    val email: String,
    val profileImageUrl: String?
) {
    // 业务方法
    fun hasProfileImage(): Boolean = !profileImageUrl.isNullOrEmpty()
}

// Mapper
fun UserEntity.toUser(): User = User(id, name, email)
fun User.toEntity(): UserEntity = UserEntity(id, name, email, null)

面试加分点

  • data class主构造函数参数必须声明为valvar(这是data class的语法要求)
  • data class不能继承其他类(但可以实现接口)
  • data class的copy()是浅拷贝,嵌套的可变对象需要手动深拷贝
  • 主构造函数的所有参数都会参与equals/hashCode/toString/copy的自动生成

6. 密封类sealed class vs 枚举enum vs 抽象类

核心回答

特性sealed classenum classabstract class
子类数量有限且已知固定无限
子类实例每个子类可有多实例每个常量仅单例可创建多实例
状态存储可带构造参数和属性只能有常量属性任意属性
编译期穷尽检查支持支持不支持
继承限制同文件内不能继承其他类正常继承规则

原理/代码

// sealed class - 受限的类层次结构(据Kotlin官方文档)
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>()  // object单例
}

// when表达式穷尽检查(不需要else分支)
fun handleResult(result: Result<*>) = when(result) {
    is Result.Success -> "数据: ${result.data}"
    is Result.Error -> "错误: ${result.message}"
    Result.Loading -> "加载中..."
    // 编译器保证全覆盖,添加新子类会触发编译错误
}

// enum - 固定的常量集合
enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF);
    
    fun hex() = "#${Integer.toHexString(rgb)}"
}

// enum实现接口
enum class Priority : Comparable<Priority> {
    LOW, MEDIUM, HIGH;
    
    override fun compareTo(other: Priority): Int {
        return ordinal.compareTo(other.ordinal)
    }
}

// sealed interface (Kotlin 1.5+)
sealed interface Error {
    class NetworkError(val code: Int) : Error
    class FileError(val path: String) : Error
}

// sealed class嵌套使用
sealed class AuthState {
    object Unauthenticated : AuthState()
    data class Authenticated(
        val userId: String,
        val token: String,
        val refreshToken: String
    ) : AuthState()
    data class Error(val message: String) : AuthState()
}

Android实战场景

// UI状态建模 - sealed class最合适
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String, val code: Int? = null) : UiState<Nothing>()
}

// 一次性事件(Side Effect)
sealed class SingleEvent {
    data class ShowToast(val message: String) : SingleEvent()
    data class Navigate(val route: String) : SingleEvent()
    object GoBack : SingleEvent()
}

// Fragment/ViewModel中使用
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val uiState: StateFlow<UiState<User>> = _uiState.asStateFlow()
    
    private val _events = MutableSharedFlow<SingleEvent>()
    val events: SharedFlow<SingleEvent> = _events.asSharedFlow()
    
    fun loadUser() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = repository.getUser()
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "未知错误")
            }
        }
    }
}

// Sealed class用于API响应建模
sealed class ApiResponse<out T> {
    data class Success<T>(val body: T, val code: Int) : ApiResponse<T>()
    data class Error(val message: String, val code: Int) : ApiResponse<Nothing>()
    object Loading : ApiResponse<Nothing>()
}

// 导航路由建模
sealed class Screen(val route: String) {
    object Home : Screen("home")
    object Profile : Screen("profile/{userId}") {
        fun createRoute(userId: String) = "profile/$userId"
    }
    object Settings : Screen("settings")
}

面试加分点

  • sealed class子类可以是classdata classobject,灵活度高
  • enum适合表示固定的、互斥的常量集合,如星期、颜色、状态码
  • sealed class + when表达式 = 类型安全的分支处理,编译器强制检查所有情况
  • sealed interface(Kotlin 1.5+)适合建模不相关子类型的公共行为

7. Kotlin反射

核心回答

Kotlin提供KClass作为反射入口,与Java的Class是不同类型。通过::class获取KClass,通过::class.java获取Java Classreified泛型参数可以在运行时获取具体类型信息。

原理/代码

// KClass vs Class
class MyClass

val kClass: KClass<MyClass> = MyClass::class  // Kotlin反射入口
val javaClass: Class<MyClass> = MyClass::class.java  // Java反射入口

// KClass的主要功能
kClass.simpleName          // "MyClass"
kClass.qualifiedName       // "com.example.MyClass"
kClass.members             // 所有成员(属性+函数)
kClass.functions           // 所有函数
kClass.constructors        // 所有构造函数

// 属性引用
class Person(val name: String, var age: Int)

val person = Person("Alice", 30)

// 属性引用 - 获取KProperty
val nameProperty = Person::name
nameProperty.get(person)  // "Alice"

// 可变属性引用 - 获取KMutableProperty
val ageProperty = Person::age
ageProperty.set(person, 31)
println(person.age)  // 31

// 函数引用
fun isAdult(age: Int) = age >= 18
val isAdultRef = ::isAdult  // KFunction1<Int, Boolean>
println(isAdultRef.invoke(20))  // true

// reified泛型获取运行时类型
inline fun <reified T> typeName() = T::class.simpleName

println(typeName<String>())      // "String"

类型擦除对反射的影响

// 泛型类型参数在运行时会被擦除
val listOfString: List<String> = listOf("a", "b")
// listOfString::class.java 返回的是 raw type,无法区分 List<String> 和 List<Int>

反射创建实例

// 通过KClass创建实例
val clazz = MyClass::class
val instance = clazz.createInstance()  // 调用无参构造函数

// 通过Java Class反射
val javaClazz = MyClass::class.java
val constructor = javaClazz.getDeclaredConstructor(String::class.java, Int::class.java)
constructor.isAccessible = true
val obj = constructor.newInstance("Alice", 30)

// 遍历属性
data class User(val name: String, val age: Int)

fun printProperties(user: User) {
    User::class.declaredMemberProperties.forEach { prop ->
        prop.isAccessible = true
        println("${prop.name} = ${prop.get(user)}")
    }
}

Android实战场景

// ViewModel反射工厂
object ViewModelFactory : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return try {
            val constructor = modelClass.getDeclaredConstructor()
            constructor.isAccessible = true
            constructor.newInstance()
        } catch (e: Exception) {
            throw IllegalArgumentException("无法创建ViewModel: ${modelClass.name}", e)
        }
    }
}

// JSON通用解析器
inline fun <reified T> String.parseJson(): T {
    return Json.decodeFromString(this)
}

// Retrofit类型Token(reified替代TypeToken)
inline fun <reified T> apiService(): T {
    return retrofit.create(T::class.java)
}

面试加分点

  • KClass是Kotlin的反射类型,提供更Kotlin化的API;::class.java获得Java Class
  • reified泛型参数解决了类型擦除问题,但只能在inline函数中使用
  • 属性引用::propertyName返回KProperty0/1/2,可以延迟获取/设置属性值
  • Kotlin反射需要添加依赖:implementation "org.jetbrains.kotlin:kotlin-reflect"

8. by委托(属性委托)

核心回答

by关键字将属性的getter/setter实现委托给其他对象。委托类需实现getValue(和setValue)运算符方法。lazy是最常用的委托实现,支持三种线程安全模式。

原理/代码

// 属性委托接口(据Kotlin官方文档)
interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

// 自定义委托示例
class NotNull<T> : ReadWriteProperty<Any?, T> {
    private var value: T? = null
    
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("${property.name}未初始化")
    }
    
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

// 使用自定义委托
class User {
    var name: String by NotNull()
    var age: Int by NotNull()
}

// lazy委托(据Kotlin官方文档)
class ExpensiveService private constructor() {
    
    companion object {
        // 默认线程安全模式:SYNCHRONIZED
        val instance: ExpensiveService by lazy { ExpensiveService() }
        
        // 指定线程安全模式
        val instanceV2: ExpensiveService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            ExpensiveService()
        }
        
        // PUBLICATION模式:初始化函数可被多次调用,取第一个返回值
        val sharedResource: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
            println("初始化中...")
            "resource"
        }
        
        // NONE模式:无锁,不保证线程安全
        val unsafeData: MutableList<Int> by lazy(LazyThreadSafetyMode.NONE) {
            mutableListOf(1, 2, 3)
        }
    }
}

LazyThreadSafetyMode说明(据Kotlin官方文档):

  • SYNCHRONIZED:默认,使用锁保证只有一个线程初始化
  • PUBLICATION:允许并发调用初始化函数,取最先完成的返回值
  • NONE:无锁,适合单线程或确保不会有并发的场景

map委托

// Map委托 - 将属性存储到Map
class UserFromMap(private val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
    val email: String? by map
}

val user = UserFromMap(mapOf(
    "name" to "Alice",
    "age" to 30
))

println(user.name)  // "Alice"

Android实战场景

// ViewModel的by viewModels委托
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {
    
    // 使用savedStateHandle委托保存状态
    private val savedState = SavedStateHandle()
    
    // 状态恢复
    private val userId: String? by savedState.getStateFlow("userId", null)
    
    // 懒加载的复杂计算
    private val processedData: List<DataItem> by lazy {
        println("开始处理数据...")  // 仅在首次访问时执行
        heavyComputation()
    }
}

// Fragment中使用by viewModels
class UserFragment : Fragment() {
    
    // 方式1: 简单用法(需要导入fragment-ktx)
    private val viewModel: UserViewModel by viewModels()
    
    // 方式2: 带工厂
    private val viewModel: UserViewModel by viewModels {
        object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return UserViewModel(repository) as T
            }
        }
    }
    
    // 方式3: Activity级别的ViewModel
    private val activityViewModel: SharedViewModel by activityViewModels()
}

// 自定义View属性委托
class CustomEditText @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : AppCompatEditText(context, attrs) {
    
    // 延迟初始化的视图引用
    private val clearButton: ImageButton? by lazy {
        findViewById(R.id.btn_clear)
    }
    
    // Observable委托 - 属性变化监听
    var textChangedCallback: ((String) -> Unit)? = null
    
    var inputText: String
        get() = text?.toString() ?: ""
        set(value) {
            setText(value)
            textChangedCallback?.invoke(value)
        }
}

面试加分点

  • lazy默认使用LazyThreadSafetyMode.SYNCHRONIZED,保证多线程安全
  • LazyThreadSafetyMode.NONE适合在主线程初始化或确认不会有并发的场景
  • by viewModels()fragment-ktx库提供的扩展函数,内部使用ViewModelProvider
  • 属性委托可以用于实现lazyobservablevetoable等常见模式
  • Kotlin标准库提供了Delegates.notNull<T>()Delegates.observable<T>()等工具

总结

Kotlin核心知识点之间的关联:

空安全 ─────────> 集合(List/MutableList)
   │                    │
   │                    ▼
   └──────> 数据类 ───> 序列(Sequence)
                   │
泛型 ─────> 扩展函数 ───> by委托
   │
   │
   ▼
密封类
   │
   │
   ▼
反射

这些知识点在Android开发中相互配合,形成了Kotlin独特的编程范式。掌握它们之间的内在联系,才能在实际项目中灵活运用。