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

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

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

1. Kotlin空安全机制

核心回答

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

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

原理/代码

kotlin 复制代码
// 可空类型声明
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官方文档):

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实战场景

kotlin 复制代码
// 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 键值对

原理/代码

kotlin 复制代码
// 不可变与可变的区别
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官方文档):

scss 复制代码
// 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实战场景

kotlin 复制代码
// 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函数可在运行时保留类型信息。

原理/代码

kotlin 复制代码
// 类型擦除示例
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泛型的区别

kotlin 复制代码
// 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实战场景

kotlin 复制代码
// 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成员,与同名成员函数冲突时,成员函数优先。

原理/代码

kotlin 复制代码
// 扩展函数本质:编译器生成静态方法
// 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实战场景

kotlin 复制代码
// 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()是浅拷贝,嵌套可变对象需要手动实现深拷贝。

原理/代码

kotlin 复制代码
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与解构

kotlin 复制代码
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实战场景

kotlin 复制代码
// 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 class enum class abstract class
子类数量 有限且已知 固定 无限
子类实例 每个子类可有多实例 每个常量仅单例 可创建多实例
状态存储 可带构造参数和属性 只能有常量属性 任意属性
编译期穷尽检查 支持 支持 不支持
继承限制 同文件内 不能继承其他类 正常继承规则

原理/代码

kotlin 复制代码
// 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实战场景

kotlin 复制代码
// 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泛型参数可以在运行时获取具体类型信息。

原理/代码

kotlin 复制代码
// 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"

类型擦除对反射的影响

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

反射创建实例

kotlin 复制代码
// 通过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实战场景

kotlin 复制代码
// 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 复制代码
// 属性委托接口(据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委托

kotlin 复制代码
// 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实战场景

kotlin 复制代码
// 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核心知识点之间的关联:

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

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

相关推荐
Cosolar8 小时前
从零搭建本地 RAG 系统:LangChain + LM Studio 完整实战指南
人工智能·后端·面试
罗超驿8 小时前
20.MySQL事务隔离级别示例详解(脏读、不可重复读、幻读)
java·数据库·mysql·面试
mCell9 小时前
JavaScript:从事件循环到手写 Promise
javascript·面试·浏览器
huaCodeA9 小时前
Android面试-Flow相关
android·面试·职场和发展
繁星星繁9 小时前
Python基础语法(二)
android·服务器·python
JAVA社区9 小时前
Java进阶全套教程(三)—— Spring框架核心精讲
java·开发语言·spring·面试·职场和发展·mybatis
Lang-12109 小时前
Frida + Android Hook 完整指南
android·逆向·hook·frida
jzlhll1239 小时前
Kotlin 协程高级用法之 NonCancellable
android·开发语言·kotlin
fqq310 小时前
java基础面试题目
面试·职场和发展