Kotlin / Android 常用函数使用示例手册

涵盖:作用域函数(let / run / with / apply / also)、委托(by)、惰性初始化(lazy)、
以及 takeIfrunCatchinguse 等高频工具函数。


目录

  1. let
  2. run
  3. with
  4. apply
  5. also
  6. lazy 惰性初始化
  7. by 属性委托
  8. by 在 Android 中的实战
  9. 其他高频工具函数

1. let

签名inline fun <T, R> T.let(block: (T) -> R): R

对非空对象执行操作,并可改变返回类型。

1.1 空安全调用(最常用)

val name: String? = user?.nickname

// 传统写法
if (name != null) {
    Log.d(TAG, "长度 = ${name.length}")
}

// let 写法:只在非空时执行
name?.let {
    Log.d(TAG, "长度 = ${it.length}")
}

// 结合 Elvis:为空时给兜底
val display = name?.let { "昵称:$it" } ?: "昵称:未设置"

1.2 给表达式起局部名字

// 避免写两次耗时函数
getUserName()?.let { n ->
    println("你好,$n")
    cache.put("name", n)
}

1.3 链式转换(把 A 类型转成 B 类型)

val length: Int? = listOf("a", "bb", "ccc")
    .firstOrNull { it.length > 1 }
    .let { it?.length ?: 0 }

1.4 限制变量作用域

val result = calculateTotal().let { total ->
    // total 只在这个块内可见,避免污染外部作用域
    if (total > 1000) total * 0.9 else total
}

1.5 Android:点击事件里避免重复 find

binding.btnSubmit.setOnClickListener {
    viewModel.submit()?.let { state ->
        Toast.makeText(this, state.message, Toast.LENGTH_SHORT).show()
    }
}

2. run

签名

inline fun <R> run(block: () -> R): R            // 非扩展:开一个新作用域
inline fun <T, R> T.run(block: T.() -> R): R     // 扩展:接收者是 this

2.1 T.run {} —— 判空 + 一串操作

val user: User? = repository.findUser(id)

val summary = user?.run {
    // 这里 this 就是 user,属性可省略 this.
    "$name$age 岁)"
} ?: "用户不存在"

2.2 非扩展 run {} —— 需要一段"临时作用域"

val padding = run {
    val base = resources.getDimensionPixelSize(R.dimen.base)
    val half = base / 2
    half * 3            // 最后一行就是 run 的返回值
}

2.3 Android:一段有中间变量的计算

private fun buildQuery(): Map<String, String> = run {
    val page = currentPage + 1
    val size = pageSize.coerceAtMost(50)
    mapOf("page" to "$page", "size" to "$size")
}

2.4 Android:把一串 UI 操作收拢

fun showEmptyState() {
    binding.emptyView.run {
        isVisible = true
        text = getString(R.string.empty_tip)
        setOnClickListener { viewModel.reload() }
    }
}

3. with

签名inline fun <T, R> with(receiver: T, block: T.() -> R): R

注意:with 不是扩展函数,它是普通函数,接收者作为第一个参数传入。

3.1 对同一对象做多次操作

val sb = StringBuilder()
with(sb) {
    append("Hello")
    append(", ")
    append("Kotlin")
}
println(sb.toString())  // Hello, Kotlin

3.2 Android:一次性配置 RecyclerView

with(binding.recyclerView) {
    layoutManager = LinearLayoutManager(context)
    adapter = orderAdapter
    setHasFixedSize(true)
    addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}

3.3 Android:解析 Bundle / 读取 Intent 参数

val detail = with(requireArguments()) {
    DetailArgs(
        id = getString("id") ?: "",
        from = getString("from") ?: "unknown",
        editable = getBoolean("editable", false)
    )
}

3.4 什么时候用 with 而不是 T.run

// 两者等价,写法差异:
with(user) { println(name) }   // with(user) { ... }
user.run { println(name) }     // user.run { ... }

// 习惯:可空对象用 ?.run {},非空对象用 with(x) {} 更直观

4. apply

签名inline fun <T> T.apply(block: T.() -> Unit): T

返回对象本身,块内 this 是对象,适合"创建即配置"。

4.1 创建时批量设置属性

val intent = Intent(context, DetailActivity::class.java).apply {
    putExtra("id", orderId)
    putExtra("from", "list")
    flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)

4.2 Android:构建 Dialog / 配置 View

AlertDialog.Builder(this).apply {
    setTitle(R.string.tip)
    setMessage(R.string.confirm_delete)
    setPositiveButton(R.string.ok) { _, _ -> viewModel.delete() }
    setNegativeButton(R.string.cancel, null)
}.show()

4.3 初始化一个数据对象

val request = OrderRequest().apply {
    userId = uid
    page = 1
    pageSize = 20
    sort = "createTime,desc"
}

4.4 链式:apply 之后继续用对象

val text = TextView(context).apply {
    this.text = "标题"          // 与属性名冲突时用 this 区分
    textSize = 16f
    setTextColor(Color.BLACK)
    gravity = Gravity.CENTER
}.text.toString()

⚠️ apply 里如果省略 this.,要注意「参数名/属性名同名」的遮蔽问题。


5. also

签名inline fun <T> T.also(block: (T) -> Unit): T

返回对象本身,块内用 it,非常适合插入副作用而不打断链式调用。

5.1 打日志 / 埋点

val order = repository.createOrder(req)
    .also { Log.d(TAG, "创建订单成功: ${it.id}") }
    .also { analytics.track("order_created", mapOf("id" to it.id)) }

5.2 链式中的校验

val file = File(path)
    .also { require(it.exists()) { "文件不存在: $path" } }
    .also { require(it.canRead()) { "文件不可读" } }

5.3 区分 applyalso

// apply:配置对象内部,省略 this
val user = User().apply {
    name = "Tom"        // 等价 this.name = "Tom"
}

// also:对外部变量做副作用,必须用 it
val user = User().also {
    it.name = "Tom"     // 必须写 it.name
    logUser(it)         // 副作用:记录日志
}

6. lazy 惰性初始化

签名fun <T> lazy(initializer: () -> T): Lazy<T>

第一次访问才初始化,之后复用缓存值。

6.1 基础用法

val heavyData: List<String> by lazy {
    Log.d(TAG, "只在首次访问时执行")
    loadFromDb()          // 耗时操作
}

// 第一次访问触发初始化
println(heavyData.size)
// 第二次访问直接拿缓存,不会再次执行 loadFromDb()
println(heavyData.size)

6.2 线程安全模式

// 默认:SYNCHRONIZED,加锁保证只初始化一次(多线程安全,略有开销)
val a: String by lazy { "A" }

// PUBLICATION:允许多线程并发初始化,最终取第一次完成的值
val b: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "B" }

// NONE:无任何线程安全保证,最快;仅限单线程确定场景
val c: String by lazy(LazyThreadSafetyMode.NONE) { "C" }

6.3 Android:by lazy 三件套

class MainActivity : AppCompatActivity() {

    // 1. ViewBinding
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    // 2. 只在用到时才创建的适配器
    private val adapter by lazy {
        OrderAdapter(onItemClick = { order -> openDetail(order.id) })
    }

    // 3. Gson / Retrofit 等单例工具
    private val gson: Gson by lazy { GsonBuilder().setLenient().create() }
}

7. by 属性委托

Kotlin 委托语法:val/var 属性 by 委托对象,属性的 get/set 交给委托对象实现。

7.1 标准委托一:lazy(延迟初始化)

见上一节。

7.2 标准委托二:observable(监听变化)

var score: Int by Delegates.observable(0) { prop, old, new ->
    // 每次赋值后回调(无法拦截)
    Log.d(TAG, "${prop.name}: $old -> $new")
}

score = 90   // 输出 score: 0 -> 90

7.3 标准委托三:vetoable(可否决的赋值)

var age: Int by Delegates.vetoable(0) { _, old, new ->
    new >= 0          // 返回 false 则本次赋值被拒绝
}

age = 25    // 生效
age = -5    // 被拒绝,age 仍是 25

7.4 标准委托四:Delegates.notNull()(延迟赋值 + 非空)

var token: String by Delegates.notNull()

fun setToken(t: String) { token = t }

fun use() {
    // 未赋值就读取会抛 IllegalStateException,比可空类型更安全
    println(token.length)
}

7.5 委托给 Map(适合解析 JSON Map)

class Config(map: Map<String, Any?>) {
    val host: String by map
    val port: Int by map
}

val cfg = Config(mapOf("host" to "192.168.2.40", "port" to 1680))
println(cfg.host)   // 192.168.2.40
println(cfg.port)   // 1680

7.6 接口委托(by 而不是属性)

interface Logger {
    fun log(msg: String)
    fun logError(e: Throwable)
}

class DefaultLogger : Logger {
    override fun log(msg: String) = println("[INFO] $msg")
    override fun logError(e: Throwable) = println("[ERROR] $e")
}

// 只需要重写一部分,其余全部转发给 impl
class OrderViewModel(private val impl: Logger = DefaultLogger()) : Logger by impl {
    override fun logError(e: Throwable) {
        super.logError(e)            // 转发给被委托对象
        crashReporter.report(e)      // 额外增强
    }
}

7.7 自定义委托(实现 ReadWriteProperty / ReadOnlyProperty

// 场景:SharedPreferences 属性化读写
class PreferenceDelegate<T>(
    private val prefs: SharedPreferences,
    private val key: String,
    private val default: T
) : ReadWriteProperty<Any?, T> {

    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
        when (default) {
            is String -> prefs.getString(key, default) as T
            is Int -> prefs.getInt(key, default) as T
            is Boolean -> prefs.getBoolean(key, default) as T
            is Long -> prefs.getLong(key, default) as T
            else -> error("不支持的类型: ${default!!::class.java}")
        }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        prefs.edit().apply {
            when (value) {
                is String -> putString(key, value)
                is Int -> putInt(key, value)
                is Boolean -> putBoolean(key, value)
                is Long -> putLong(key, value)
                else -> error("不支持的类型")
            }
        }.apply()
    }
}

// 使用
class Settings(context: Context) {
    private val prefs = context.getSharedPreferences("app", Context.MODE_PRIVATE)

    var isDarkMode: Boolean by PreferenceDelegate(prefs, "dark_mode", false)
    var userName: String by PreferenceDelegate(prefs, "user_name", "guest")
}

val s = Settings(context)
s.isDarkMode = true                       // 自动写入 SP
println(s.isDarkMode)                     // 自动读取

8. by 在 Android 中的实战

8.1 by viewModels() / by activityViewModels()

class OrderFragment : Fragment(R.layout.fragment_order) {

    // 作用域绑定 Fragment,配置变更不重建
    private val viewModel: OrderViewModel by viewModels()

    // 作用域绑定宿主 Activity(Fragment 之间共享数据)
    private val sharedViewModel: SharedViewModel by activityViewModels()

    // 需要工厂时
    private val vm: OrderViewModel by viewModels {
        OrderViewModelFactory(repository)
    }
}

需要额外依赖:implementation "androidx.fragment:fragment-ktx:1.x.x"

8.2 by lazy + ViewBinding

class OrderFragment : Fragment(R.layout.fragment_order) {
    private var _binding: FragmentOrderBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(...): View {
        _binding = FragmentOrderBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null     // Fragment 视图销毁必须置空,否则内存泄漏
    }
}

8.3 by preference()(androidx.preference)

class SettingsFragment : PreferenceFragmentCompat() {

    val nickname: String by preference("nickname", "guest")

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.settings, rootKey)
    }
}

8.4 by lazy + Kotlin 单例(object 天然线程安全)

val retrofit: Retrofit by lazy {
    Retrofit.Builder()
        .baseUrl(BaseConfig.BASE_URL)          // 例如 http://192.168.2.40:1680/api/q5-bill/
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
        .client(okHttpClient)
        .build()
}

9. 其他高频工具函数

9.1 takeIf / takeUnless

// takeIf: 条件成立返回对象本身,否则返回 null
val validAge = age.takeIf { it in 0..150 }

// 结合 ?.let 使用,替代 if-else
userInput.takeIf { it.isNotBlank() }?.let { submit(it) }

// takeUnless: 与 takeIf 相反,条件不成立才返回对象
val file = File(path).takeUnless { it.isDirectory }

9.2 runCatching(替代 try-catch)

val result = runCatching {
    // 可能抛异常的代码
    val json = api.getOrder(id)
    parse(json)
}

result
    .onSuccess { order -> render(order) }
    .onFailure { e -> showError(e.message) }

// 取值:getOrDefault / getOrNull / getOrElse
val order = result.getOrNull()
val safe = result.getOrElse { Order.empty() }

9.3 use(自动关闭资源,类似 Java try-with-resources)

FileInputStream(file).use { input ->
    val bytes = input.readBytes()
    parser.parse(bytes)
}   // 块结束自动 close,异常也会关闭

// 一次性发送请求的场景
HttpURLConnection(url).apply {
    connectTimeout = 5000
}.use { conn ->
    conn.inputStream.bufferedReader().readText()
}

9.4 measureTimeMillis / measureNanoTime(性能计时)

val cost = measureTimeMillis {
    formulas.forEach { it.evaluate() }
}
Log.d(TAG, "公式计算耗时:${cost}ms")

9.5 repeat(固定次数循环)

repeat(3) { index ->
    Log.d(TAG, "第 ${index + 1} 次重试")
}

9.6 isInitializedlateinit 安全检查)

private lateinit var adapter: OrderAdapter

fun refresh() {
    if (::adapter.isInitialized) adapter.notifyDataSetChanged()
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hai_android

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值