涵盖:作用域函数(
let/run/with/apply/also)、委托(by)、惰性初始化(lazy)、
以及takeIf、runCatching、use等高频工具函数。
目录
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 区分 apply 和 also
// 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 isInitialized(lateinit 安全检查)
private lateinit var adapter: OrderAdapter
fun refresh() {
if (::adapter.isInitialized) adapter.notifyDataSetChanged()
}

1401

被折叠的 条评论
为什么被折叠?



