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种官方场景
?.letvsif (x != null):前者适合链式调用,后者适合复杂条件分支- Android中
lateinit var与by 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的中间操作(
map、filter等)是惰性的,只有遇到终止操作(toList、first等)才会执行 - 小数据量用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主构造函数参数必须声明为
val或var(这是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子类可以是
class、data class、object,灵活度高 - enum适合表示固定的、互斥的常量集合,如星期、颜色、状态码
- sealed class + when表达式 = 类型安全的分支处理,编译器强制检查所有情况
- sealed interface(Kotlin 1.5+)适合建模不相关子类型的公共行为
7. Kotlin反射
核心回答
Kotlin提供KClass作为反射入口,与Java的Class是不同类型。通过::class获取KClass,通过::class.java获取Java Class。reified泛型参数可以在运行时获取具体类型信息。
原理/代码
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获得JavaClassreified泛型参数解决了类型擦除问题,但只能在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- 属性委托可以用于实现
lazy、observable、vetoable等常见模式 - Kotlin标准库提供了
Delegates.notNull<T>()、Delegates.observable<T>()等工具
总结
Kotlin核心知识点之间的关联:
scss
空安全 ─────────> 集合(List/MutableList)
│ │
│ ▼
└──────> 数据类 ───> 序列(Sequence)
│
泛型 ─────> 扩展函数 ───> by委托
│
│
▼
密封类
│
│
▼
反射
这些知识点在Android开发中相互配合,形成了Kotlin独特的编程范式。掌握它们之间的内在联系,才能在实际项目中灵活运用。