Android开发常用Kotlin高级语法

一、扩展函数与扩展属性:为系统类 "量身定制" 工具方法

Kotlin 的扩展机制允许为现有类(包括 Java 类)添加新功能,无需继承或修改原类。这在 Android 开发中尤其适合封装高频重复操作(如 View 操作、上下文获取)。

1.1 为 View 添加扩展函数:简化 UI 操作

Android 中操作 View 时,常需要处理可见性、点击事件等重复逻辑。通过扩展函数可以将这些逻辑封装,提升代码简洁性。

复制代码
// 扩展函数:快速切换View可见性
fun View.setVisible(visible: Boolean) {
    visibility = if (visible) View.VISIBLE else View.GONE
}

// 扩展函数:安全的点击事件(防重复点击)
fun View.setOnSingleClickListener(
    interval: Long = 500L, // 防重复点击间隔
    action: (View) -> Unit
) {
    var lastClickTime = 0L
    setOnClickListener { view ->
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastClickTime > interval) {
            lastClickTime = currentTime
            action(view)
        }
    }
}

// 使用示例
val button = findViewById<Button>(R.id.btn_submit)
button.setVisible(true) // 替代 visibility = View.VISIBLE
button.setOnSingleClickListener { 
    // 点击逻辑(500ms内仅触发一次)
}
1.2 为 Context 添加扩展属性:快速获取系统服务

Android 中获取系统服务(如LayoutInflaterSharedPreferences)需要调用getSystemService(),通过扩展属性可以简化这一过程。

复制代码
// 扩展属性:获取LayoutInflater
val Context.layoutInflater: LayoutInflater
    get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

// 扩展属性:获取默认SharedPreferences
val Context.defaultSp: SharedPreferences
    get() = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)

// 使用示例(在Activity/Fragment中)
val inflater = layoutInflater // 直接使用扩展属性
val sp = defaultSp.edit().putString("user", "douban").apply()

二、作用域函数:优雅处理对象作用域

Kotlin 提供letrunwithapplyalso等作用域函数,用于在特定作用域内操作对象。选择正确的函数可提升代码可读性。

1.1 作用域函数对比与最佳实践
函数 适用场景 返回值 内部用this还是it
let 对对象执行转换或操作,处理null安全 最后一行表达式 it(单个参数)
run 对象配置 + 计算返回值(需同时处理配置和结果) 最后一行表达式 this(对象自身)
with 对单个对象执行多个操作(类似run,但通过参数传递对象) 最后一行表达式 this(对象自身)
apply 对象配置(无需返回值,仅需修改对象属性) 对象自身 this(对象自身)
also 对对象执行副作用操作(如日志、统计,不影响对象本身) 对象自身 it(单个参数)

Android 开发中的典型用例

复制代码
// apply:配置View属性(返回View自身,适合链式调用)
val textView = TextView(context).apply {
    text = "Hello Douban"
    textSize = 16f
    setTextColor(ContextCompat.getColor(context, R.color.primary))
    layoutParams = LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.WRAP_CONTENT,
        ViewGroup.LayoutParams.WRAP_CONTENT
    )
}

// let:处理可能为null的值(安全调用链)
intent?.extras?.let { extras -> 
    val userId = extras.getString("user_id")
    userId?.let { loadUser(it) } // 嵌套let处理多级null
}

// also:记录对象状态(不修改对象)
user.also { 
    Log.d("User", "Loaded user: ${it.name}") 
}.run { 
    updateUI(this) // 继续对user执行操作
}

三、object 的核心用法

3.1 单例类(Singleton)

使用 object 关键字可以直接定义一个单例类,无需手动实现单例模式的样板代码。

复制代码
// 定义单例类
object AppSettings {
    // 成员变量
    var theme: String = "light"
    var isDarkMode: Boolean = false
    
    // 成员函数
    fun saveSettings() {
        // 保存配置到本地
    }
    
    fun loadSettings() {
        // 从本地加载配置
    }
}

// 使用单例(直接通过类名访问)
AppSettings.theme = "dark"
AppSettings.saveSettings()

特点

  • 全局唯一实例,由 Kotlin 编译器自动生成线程安全的单例实现。
  • 可继承类或实现接口,但不能有构造函数(因为无法实例化)。
3.2 对象表达式(匿名内部类)

object 可用于创建匿名类的实例,常用于实现接口或继承类的一次性对象。

复制代码
// 示例1:实现接口
val listener = object : View.OnClickListener {
    override fun onClick(v: View) {
        // 处理点击事件
    }
}

// 示例2:继承类并扩展
val customRunnable = object : Runnable {
    var counter = 0
    override fun run() {
        counter++
        println("Running $counter times")
    }
}

应用场景

  • 替代 Java 中的匿名内部类(如点击监听器、回调接口)。
  • 临时需要的对象(如单元测试中的 mock 对象)。

四、密封类与数据类:构建安全的状态模型

密封类(Sealed Class)定义受限的类层次结构,适合表示有限的状态类型 (如加载状态、网络请求结果);数据类(Data Class)自动生成equals()hashCode()等方法,适合表示数据模型

4.1 密封类:实现状态机(State Machine)

在 MVVM 架构中,常用密封类表示 UI 状态(加载中、成功、失败),确保状态覆盖全面,避免遗漏。

复制代码
// 密封类:定义网络请求状态
sealed class UserState {
    object Loading : UserState() // 加载中
    data class Success(val user: User) : UserState() // 成功(携带数据)
    data class Error(val exception: Throwable) : UserState() // 失败(携带异常)
}

// ViewModel中更新状态
class UserViewModel : ViewModel() {
    private val _state = MutableLiveData<UserState>()
    val state: LiveData<UserState> = _state

    fun loadUser() {
        _state.value = UserState.Loading // 触发加载状态
        viewModelScope.launch {
            try {
                val user = fetchUser() // 异步获取数据
                _state.value = UserState.Success(user) // 更新为成功状态
            } catch (e: Exception) {
                _state.value = UserState.Error(e) // 更新为失败状态
            }
        }
    }
}

// Activity中根据状态更新UI(配合when表达式,确保覆盖所有情况)
lifecycleScope.launch {
    viewModel.state.collect { state ->
        when (state) {
            is UserState.Loading -> showLoading()
            is UserState.Success -> showUser(state.user)
            is UserState.Error -> showError(state.exception)
        }
    }
}
4.2 数据类:简化数据模型与 Parcelable 集成

数据类默认生成copy()componentN()等方法,适合表示结构化数据。结合@Parcelize注解可快速实现Parcelable接口(用于Intent传参)。

复制代码
// 数据类(自动生成equals、hashCode、toString)
@Parcelize // 需要添加kotlin-parcelize插件
data class User(
    val id: String,
    val name: String,
    val age: Int,
    val isVip: Boolean = false
) : Parcelable

// 使用示例:通过Intent传递User对象
val user = User(id = "1", name = "Douban", age = 10)
val intent = Intent(context, DetailActivity::class.java).apply {
    putExtra("user", user) // 直接传递Parcelable对象
}
startActivity(intent)

// 在DetailActivity中获取数据
val user = intent.getParcelableExtra<User>("user")

五、内联函数与具体化类型参数:突破泛型擦除限制

内联函数(inline)将函数体直接插入调用处,减少函数调用开销;具体化类型参数(reified)允许在运行时访问泛型类型,解决 Java 泛型擦除问题。

5.1 具体化类型参数:简化泛型类型检查

在 Android 中,常需要根据泛型类型动态创建对象或解析数据(如 JSON 反序列化),具体化类型参数可直接获取类型信息。

复制代码
// 内联函数:通过反射获取泛型类型
inline fun <reified T> Gson.fromJson(json: String): T {
    return fromJson(json, T::class.java) // T::class.java 可获取具体类型
}

// 使用示例(无需手动传递Type)
val user: User = gson.fromJson(jsonString) 

// 内联函数:启动Activity(泛型指定目标类)
inline fun <reified T : Activity> Context.startActivity() {
    startActivity(Intent(this, T::class.java))
}

// 使用示例
startActivity<MainActivity>() // 替代 startActivity(Intent(this, MainActivity::class.java))

六、协程 (Coroutines):Android 异步编程的 "标准答案"

Android 异步编程中协程的完整实战示例-CSDN博客

七、lazy与lateinit语法对比

7.1 lazy 示例
复制代码
// 单线程懒加载(Android 主线程)
val expensiveObject: ExpensiveObject by lazy(LazyThreadSafetyMode.NONE) {
    ExpensiveObject() // 首次访问时执行初始化
}

// 多线程安全懒加载(默认模式)
val config: AppConfig by lazy {
    loadConfigFromDisk() // 自动加锁保证线程安全
}
7.2 lateinit 示例
复制代码
// 在 Activity 中延迟初始化 ViewModel
lateinit var viewModel: MyViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
    // 必须在使用 viewModel 前初始化
}
7.3关键差异详解
  • lazy :只能用于 val(只读属性),因为其内部实现通过委托管理初始化值,初始化后不可修改。
  • lateinit :只能用于 var(可变属性),因为需要在后续代码中手动赋值。
相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨8 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜10 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa10 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio