高阶函数
Lambda表达式: 一种匿名函数的简写形式
val myLambda: (String, Int) -> Unit = { s, i -> println("$s$i") }
注:多行lambda中,最后一行的表达式结果会自动作为整个lambda的返回值,无需额外声明。
unit 类似于void
什么是it
it是Kotlin为单参数lambda表达式提供的默认参数名- 当lambda只有一个参数且未显式命名时,可以直接用
it引用该参数 - 这是Kotlin的一种语法糖,旨在减少样板代码
list.map { it.length } // "it"自动代表列表元素
高阶函数:一种函数类型(以函数为参数或返回值)
高阶函数是指满足以下任一条件的函数:
- 接收一个或多个函数作为参数
- 返回一个函数
函数作为参数的高阶函数
// 定义高阶函数
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// 使用高阶函数
val sum = calculate(10, 5) { x, y -> x + y } // 结果为15
val product = calculate(10, 5) { x, y -> x * y } // 结果为50
- 当lambda作为最后一个参数时,可以移到括号外
- 当lambda是唯一参数时,可以省略括号
it可用作默认参数名
函数作为返回值的高阶函数
// 定义返回函数的高阶函数
fun getOperation(type: String): (Int, Int) -> Int {
return when (type) {
"sum" -> { a, b -> a + b }
"subtract" -> { a, b -> a - b }
else -> { _, _ -> 0 }
}
}
val operation = getOperation("sum")//返回加法函数
val result = operation(10, 5) // 结果为15
| 特性 | Kotlin when | Java switch |
|---|---|---|
| 支持类型 | 任意类型(包括表达式、范围、类型检查) | 仅限整型、枚举、String(Java 14+) |
| 返回值 | 可作为表达式返回值 | 仅语句,需额外变量赋值 |
| 智能转换 | 支持(is 检查后自动转换类型) | 不支持 |
| 多条件匹配 | 支持(逗号分隔或范围) | 需穿透(case 1: case 2:) |
when (x) {
0, 1 -> "Binary digit" // 匹配多个值
in 2..10 -> "Between 2 and 10" // 范围匹配
!in 20..30 -> "Not in 20..30" // 反向范围
else -> "Other"
}
带状态的函数的高阶函数
fun createCounter(): () -> Int {
var count = 0 // 被返回的函数捕获
return { ++count } // 每次调用递增count
}
fun main() {
val counter = createCounter()
println(counter()) // 输出: 1
println(counter()) // 输出: 2
}
闭包的本质是 函数 + 其访问的外部变量环境。在 Kotlin 中,它通过 Lambda 或嵌套函数实现,常用于:
- 状态封装(如计数器、缓存)
- 回调处理(如事件监听)
- 函数式编程(如高阶函数返回带状态的函数)
扩展函数
扩展函数(Extension Functions)是 Kotlin 的核心特性之一,允许在不修改原类代码的情况下,为现有类添加新功能。它的本质是静态方法,但通过语法糖实现类成员式的调用。 扩展函数是静态解析的,不会实际插入到类中,编译后会转换为静态方法:
基本语法
// 为 String 类添加一个扩展函数
fun String.addExclamation(): String {
return "$this!" // this 指向接收者对象(String 实例)
}
// 调用
println("Hello".addExclamation()) // 输出: "Hello!"
fun <T> List<T>.printSize() {
println("Size: ${this.size}")
}
listOf(1, 2, 3).printSize() // 输出: "Size: 3"
// 扩展函数 + 高阶函数(Lambda 参数)
fun List<String>.filterBy(predicate: (String) -> Boolean): List<String> {
return this.filter(predicate)
}
// 调用
val names = listOf("Alice", "Bob", "Charlie", "David")
val result = names.filterBy { it.length > 4 } // 过滤长度 >4 的字符串
println(result) // 输出: [Alice, Charlie, David]
fun <T> T.runTest(mm:T.()->Boolean)=mm();
123.runTest{
println(this);
}
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
String.:指定要扩展的类(接收者类型)。this:在扩展函数内指向调用该函数的对象实例。
函数引用和函数类型转换
(1) 高阶函数 showAction
fun showAction(action: (Int) -> String) {
println(action(88)) // 调用传入的函数,并打印结果
}
- 接收一个函数参数
action,该函数接受Int返回String。 - 调用时固定传入参数
88。
(2) Lambda 实现 lambdaImpl
fun lambdaImpl(value: Int) = "我的值是:$value"
- 普通函数,逻辑与匿名 Lambda 相同。
2. 三种调用方式对比
方式 1:直接传递 Lambda(匿名函数)
showAction {
"我的值是:$it" // `it` 是隐式参数名(单个参数时可用)
}
- 特点:最简洁的写法,适用于简单逻辑。
方式 2:传递函数引用(:: 操作符)
showAction(::lambdaImpl) // 将函数转为引用传递
- 原理:
::lambdaImpl将函数lambdaImpl转换为(Int) -> String类型的对象。 - 优势:复用已有函数,避免重复代码。
方式 3:通过变量传递函数引用
val r1: Function1<Int, String> = ::lambdaImpl // 显式指定函数类型接口
val r2: (Int) -> String = r1 // 转换为 Kotlin 函数类型
val r3: Int.() -> String = r2 // 转换为带接收者的函数类型
showAction(r3) // 最终调用
- 关键点:
Function1<Int, String>是 Kotlin 标准库中表示(Int) -> String的函数接口。Int.() -> String是 带接收者的函数类型,等价于(Int) -> String,但调用语法不同(见下文)。
3. 带接收者的函数类型(Int.() -> String)
定义与调用
val receiverFunc: Int.() -> String = { "值:${this}" } // `this` 指向接收者对象
println(100.receiverFunc()) // 输出: "值:100"(类似扩展函数)
- 本质:第一个参数是接收者(
Int),调用时可用this访问。 - 转换关系:
Int.() -> String == (Int) -> String Int.(String) -> Unit == (Int, String) -> Unit
为什么能赋值给 (Int) -> String?
Kotlin 允许带接收者的函数类型与非接收者类型隐式转换,因为它们的参数列表本质相同。
4. 代码执行流程
::lambdaImpl将函数转为引用。- 通过变量
r1、r2、r3多次转换类型(最终仍是(Int) -> String)。 showAction(r3)调用时,实际执行lambdaImpl(88),输出:我的值是:88
5. 关键知识点总结
| 概念 | 说明 |
|---|---|
函数引用 :: | 将命名函数转换为函数对象,可赋值给变量或传递。 |
| 函数类型转换 | Kotlin 支持不同表示法的函数类型隐式转换(如带接收者 ↔ 普通参数)。 |
Function1 | Kotlin 标准库中的函数接口,对应 (T) -> R 类型。 |
| 带接收者的函数 | 类似扩展函数,Int.() -> String 的 this 指向接收者对象。 |
何时使用这种写法?
- 需要复用已有函数时(如
::lambdaImpl)。 - 统一处理不同函数类型时(如将带接收者的函数传递给普通函数参数)。
- 反射或动态调用场景(通过引用获取函数元信息)。
Android开发中的常见应用
// 传统方式
button.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
// 处理点击
}
})
// 使用高阶函数简化
fun View.onClick(action: (View) -> Unit) {
setOnClickListener(action)
}
// 调用
button.onClick {
// 处理点击
}
定义扩展函数
fun View.onClick(action: (View) -> Unit) {
setOnClickListener(action)
}
解析:
- 为
View类添加扩展函数onClick - 参数
action是一个函数类型:(View) -> Unit(接收View参数,返回Unit) - 内部直接调用原生
setOnClickListener并传入这个lambda
优势:
- 将监听器设置抽象为更直观的"onClick"操作
- 隐藏了接口实现的细节
批量点击事件中的高阶函数
class AndroidClickListener<T> {
private val actions = arrayListOf<(T?) -> Unit>() // 存储事件回调函数
private val values = arrayListOf<T?>() // 存储关联数据:保存每个事件关联的数据。
// 添加监听器
fun addListener(value: T?, action: (T?) -> Unit) {
if (!actions.contains(action)) {
actions.add(action)
values.add(value)
}
}
// 触发所有监听事件
fun touchListeners() {
if (actions.isEmpty()) {
println("你还没有添加任何事件呀")
return
}
actions.forEachIndexed { index, item ->
item.invoke(values[index]) // 执行回调并传入关联值
}
}
}
fun main() {
// 使用示例
val clickHandler = AndroidClickListener<String>()
// 第一次触发(无监听器)
clickHandler.touchListeners() // 输出: "你还没有添加任何事件呀"
// 添加3个监听器
clickHandler.addListener("事件1") {
println("触发事件1,值: $it")
}
clickHandler.addListener("事件2") {
println("触发事件2,值: $it")
}
clickHandler.addListener(null) {
println("触发事件3,值为null")
}
// 第二次触发
clickHandler.touchListeners()
/* 输出:
触发事件1,值: 事件1
触发事件2,值: 事件2
触发事件3,值为null
*/
}
forEachIndexed
actions.forEachIndexed { index, item ->
item.invoke(values[index]) // 执行回调并传入关联值
}
actions:一个存储着 Lambda 表达式(类型为(T?) -> Unit)的集合。forEachIndexed:Kotlin 标准库提供的扩展函数,用于遍历集合并同时获取索引和元素。index:当前元素的索引(从 0 开始)。item:当前元素(即actions中的 Lambda 表达式)。item.invoke(values[index]):调用 Lambda 并传入values集合中对应索引的值。
2. 关键概念解析
(1) forEachIndexed 函数
-
作用:遍历集合,提供索引 + 元素的双参数 Lambda。
-
等价于:
for ((index, item) in actions.withIndex()) { item.invoke(values[index]) } -
优势:函数式风格,避免显式索引管理。
(2) invoke 方法
- 本质:
item.invoke(values[index])等价于直接调用item(values[index])。 - 意义:显式调用 Lambda 表达式,并传入参数(这里是
values[index])。
(3) 闭包访问
- Lambda 内部通过
index访问外部的values集合,这是 Kotlin 的闭包特性。
| 操作符 | 含义 | 示例 | 范围 |
|---|---|---|---|
.. | 闭区间(包含两端) | 1..3 | 1, 2, 3 |
until | 半开区间(不含末端) | 1 until 3 | 1, 2 |
downTo | 递减区间 | 3 downTo 1 | 3, 2, 1 |
step | 步长控制 | 1..5 step 2 | 1, 3, 5 |
扩展函数takeIf 函数详解
takeIf 是 Kotlin 标准库中的一个非常有用的扩展函数,它允许你根据条件来决定是否接收一个值。下面我将详细介绍它的用法和实际应用场景。
基本语法
fun <T> T.takeIf(predicate: (T) -> Boolean): T?
工作原理
takeIf 函数会:
- 接收一个谓词(条件判断函数)
- 如果谓词对接收者对象(调用
takeIf的对象)返回true,则返回该对象 - 如果谓词返回
false,则返回null
简单示例
val number = 10
val result = number.takeIf { it > 5 } // 返回10,因为10>5
println(result) // 输出:10
val anotherResult = number.takeIf { it > 15 } // 返回null,因为10不大于15
println(anotherResult) // 输出:null
与 let 结合使用
takeIf 经常与 let 函数结合使用,形成强大的条件处理链:
val input = "Kotlin"
input.takeIf { it.length > 3 }?.let {
println("字符串长度大于3: $it")
}
// 输出:字符串长度大于3: Kotlin
实际应用场景
1. 条件性对象处理
// 只有当用户不为null且是活跃用户时才发送消息
user?.takeIf { it.isActive }?.sendMessage()
2. 验证输入
fun processInput(input: String?) {
input?.takeIf { it.isNotBlank() }?.let {
println("处理输入: $it")
} ?: println("输入无效")
}
3. 替代if语句
// 传统写法
if (file.exists() && file.canRead()) {
file.readText()
}
// 使用takeIf的写法
file.takeIf { it.exists() && it.canRead() }?.readText()
与 takeUnless 对比
takeIf 有一个相反的函数 takeUnless,它在条件为false时返回对象:
val number = 10
val result = number.takeUnless { it > 5 } // 返回null,因为10>5
性能考虑
takeIf 是内联函数(inline),不会带来额外的运行时开销,可以放心使用。
高阶
val names = listOf("Zhangsan", "Lisi", "Wangwu") // 名字集合
val ages = listOf(20, 21, 22) // 年龄集合
names.zip(ages).map { (name, age) -> // 直接解构 Pair
"you name:$name, you age:$age"
}
zip:合并两个集合为 Pair 列表
names.zip(ages) // 输出: [("Zhangsan", 20), ("Lisi", 21), ("Wangwu", 22)]
- 将
names和ages按索引位置配对,生成List<Pair<String, Int>>。
Kotlin 标准库常用函数详解
1. 转换操作
map:一对一转换,保持结构。flatMap:一对多展开,扁平化结果
val numbers = listOf(1, 2, 3)
// map - 转换每个元素
val doubled = numbers.map { it * 2 } // [2, 4, 6]
// flatMap - 转换并平铺
val flatResult = numbers.flatMap { listOf(it, it + 1) } // [1, 2, 2, 3, 3, 4]
// groupBy - 按条件分组
val grouped = numbers.groupBy { if (it % 2 == 0) "even" else "odd" }
// {"odd": [1, 3], "even": [2]}
如何优化大数据集的链式调用(如map().filter().map())?
1. 减少中间集合创建(核心优化)
每次链式调用(如 map、filter)都会生成一个临时集合,大数据集时会导致内存和性能开销。
方案:使用 asSequence() 转换为序列
dataSet.asSequence() // 转换为序列
.map { transformA(it) } // 惰性操作
.filter { predicate(it) } // 惰性操作
.map { transformB(it) } // 惰性操作
.toList() // 终端操作(最终生成结果)
-
原理:序列(
Sequence)是惰性求值的,仅在终端操作(如toList())时执行计算,避免中间集合分配。 -
性能对比:
// 列表操作:创建3个临时集合 list.map{...}.filter{...}.map{...} // 序列操作:仅1个最终集合 list.asSequence().map{...}.filter{...}.map{...}.toList() -
适用场景:数据量 ≥ 1000 时效果显著。
注意:
- 小数据集(如 < 100 元素)直接使用集合操作更高效(序列有初始化开销)。
- 避免在
asSequence()后频繁调用toList(),失去惰性优势。
2. 过滤操作
val numbers = listOf(1, 2, 3)
// filter - 条件过滤
val evens = numbers.filter { it % 2 == 0 } // [2]
// filterNot - 反向过滤
val notEvens = numbers.filterNot { it % 2 == 0 } // [1, 3]
// take/drop - 获取/丢弃前N个
val firstTwo = numbers.take(2) // [1, 2]
val afterFirst = numbers.drop(1) // [2, 3]
3. 聚合操作
val numbers = listOf(1, 2, 3)
// reduce - 累积操作
val sum = numbers.reduce { acc, num -> acc + num } // 6
// fold - 带初始值的累积
val sumWith10 = numbers.fold(10) { acc, num -> acc + num } // 16
// maxBy/minBy - 按条件找最值
val maxOdd = numbers.maxBy { it % 2 * it } // 3
-
reduce:简洁但要求非空集合,结果类型固定。 -
fold:灵活安全,支持初始值和类型转换。 -
选择依据:
- 确定集合非空且结果类型相同 →
reduce - 需要初始值、处理空集合或类型转换 →
fold
- 确定集合非空且结果类型相同 →
二、作用域函数(Scope Functions)
Kotlin 作用域函数详解
Kotlin 标准库提供了几个作用域函数(Scope Functions),它们能在对象的上下文中执行代码块,主要区别在于:
- 如何访问上下文对象(
this或it) - 返回值是什么(对象本身或 lambda 结果)
五大作用域函数对比
| 函数 | 上下文对象引用 | 返回值 | 是否扩展函数 | 典型使用场景 |
|---|---|---|---|---|
let | it | lambda 结果 | 是 | 非空检查、数据转换 |
run | this | lambda 结果 | 是 | 对象配置、计算值 |
with | this | lambda 结果 | 否 | 对非空对象操作 |
apply | this | 对象本身 | 是 | 对象初始化 |
also | it | 对象本身 | 是 | 附加效果、链式调用 |
1. let 函数
特点:
- 通过
it访问对象 - 返回 lambda 表达式结果
- 常用于空安全检查和数据转换
// 示例1:空安全检查
val name: String? = getName()
name?.let {
println(it.length) // it 是非空的name
}
// 示例2:数据转换
val length = user?.let {
it.name.length
} ?: 0
2. run 函数
特点:
- 通过
this访问对象(可省略) - 返回 lambda 表达式结果
- 两种形式:扩展函数和非扩展函数
// 扩展函数形式
val result = user.run {
println(name) // 直接访问属性
calculateAge() // 最后一行作为返回值
}
// 非扩展函数形式(创建作用域)
val hexRegex = run {
val digits = "0-9a-f"
Regex("[$digits]+")
}
3. with 函数
特点:
- 非扩展函数
- 通过
this访问对象 - 返回 lambda 表达式结果
- 适合对已知非空对象操作
val user = getUser()
val description = with(user) {
"Name: $name, Age: $age" // 直接访问属性
}
4. apply 函数
特点:
- 通过
this访问对象 - 返回对象本身
- 主要用于对象初始化配置
val textView = TextView(context).apply {
text = "Hello"
textSize = 16f
setTextColor(Color.BLACK)
}
5. also 函数
特点:
- 通过
it访问对象 - 返回对象本身
- 适合执行附加操作
val file = File("data.txt").also {
println("创建文件: ${it.path}")
// 可以执行其他副作用操作
}.also {
it.setReadable(true)
}
使用场景选择指南
-
初始化对象 →
applyIntent().apply { action = "ACTION_VIEW" putExtra("key", value) } -
数据转换 →
let或run// 使用let val length = string?.let { it.length } ?: 0 // 使用run val description = user.run { "$name-$age" } -
需要作用域 →
run(非扩展形式)val result = run { val x = calculateX() val y = calculateY() x + y } -
链式调用添加操作 →
alsouserList .filter { it.active } .also { log("找到${it.size}个活跃用户") } .map { it.name } -
对已知非空对象操作 →
withwith(recyclerView) { layoutManager = LinearLayoutManager(context) adapter = MyAdapter() }
性能注意事项
- 所有作用域函数都是
inline的,不会产生运行时开销 - 过度嵌套会降低可读性:
// 不推荐 user?.run { address?.let { street?.also { print(it.length) } } }
Android 开发实用示例
1. View 初始化
val button = Button(context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
text = "Submit"
setOnClickListener { /* ... */ }
}
2. Intent 创建
val intent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, message)
}
3. 资源释放
resource.use {
// 自动关闭资源
}.also {
logger.log("资源已处理")
}
4. SharedPreferences 编辑
preferences.edit().apply {
putString("key", value)
putInt("count", 10)
apply()
}
三、字符串处理
1. 模板与分割
// 字符串模板
val message = "User $name has ${items.size} items"
// 多行字符串
val sql = """
SELECT * FROM users
WHERE id = ?
LIMIT 1
""".trimIndent()
// 分割操作
val parts = "a,b,c".split(",") // ["a", "b", "c"]
2. 常用扩展
// 判空/空白
val isBlank = string.isBlank()
// 截取与移除
val withoutPrefix = string.removePrefix("http://")
val substring = string.substring(1..3)
// 正则匹配
val hasDigits = string.contains(Regex("\\d+"))
四、IO与文件操作
1. 文件读写
// 读取文件
val content = File("path.txt").readText()
// 写入文件
File("output.txt").writeText("Hello")
// 按行处理
File("data.csv").forEachLine { line ->
process(line)
}
2. 流处理
// 自动关闭资源
FileInputStream("file").use { stream ->
// 操作stream
}
// 缓冲读取
File("large.txt").bufferedReader().use { reader ->
reader.readLine()
}
六、其他实用函数
1. 空安全操作
// 空合并操作符
val name = user?.name ?: "Unknown"
// 安全转换
val number = input as? Int // 失败返回null
2. 区间与序列
// 区间迭代
for (i in 1..10 step 2) { ... }
// 序列生成
val sequence = generateSequence(1) { it * 2 }
sequence.take(5).toList() // [1, 2, 4, 8, 16]
3. 类型检查
when (obj) {
is String -> obj.length
is Int -> obj + 1
else -> 0
}
Android开发特别推荐
1. 视图绑定简化
fun <T : View> View.bindView(@IdRes id: Int): Lazy<T> = lazy {
findViewById(id) as T
}
// 使用
private val button by bindView<Button>(R.id.btn_submit)
协程
协程和线程的区别
线程:
- 抢占式调度:操作系统决定何时切换线程
- 并行执行:多核CPU上真正同时运行
- 阻塞操作会占用整个线程资源
协程:
- 协作式调度:协程主动让出执行权
- 挂起(suspend)而非阻塞:遇到IO操作时挂起,释放底层线程
- 单线程上可并发执行多个协程