引言:命令式vs函数式
假设你需要处理一个用户列表,筛选出成年用户并按年龄排序:
// 命令式编程:告诉计算机"怎么做"
fun filterAndSortUsers(users: List<User>): List<User> {
val adults = mutableListOf<User>()
// 第1步:筛选成年人
for (user in users) {
if (user.age >= 18) {
adults.add(user)
}
}
// 第2步:排序
for (i in 0 until adults.size - 1) {
for (j in i + 1 until adults.size) {
if (adults[i].age > adults[j].age) {
val temp = adults[i]
adults[i] = adults[j]
adults[j] = temp
}
}
}
return adults
}
函数式编程:告诉计算机"做什么"
// 函数式编程:声明式、链式调用
fun filterAndSortUsers(users: List<User>): List<User> =
users
.filter { it.age >= 18 }
.sortedBy { it.age }
对比结果:
- 代码量:从20行减少到3行
- 可读性:意图一目了然
- 可维护性:易于修改和扩展
- 错误率:减少手动循环带来的bug
本文将带你全面掌握Kotlin的函数式编程特性。
函数式编程核心概念
第一等公民(First-Class Functions)
在Kotlin中,函数是第一等公民,可以:
// 1. 赋值给变量
val greet: (String) -> String = { name -> "Hello, $name!" }
// 2. 作为参数传递
fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}
val sum = executeOperation(5, 3) { a, b -> a + b } // 8
val product = executeOperation(5, 3) { a, b -> a * b } // 15
// 3. 作为返回值
fun getMathOperation(type: String): (Int, Int) -> Int {
return when (type) {
"add" -> { a, b -> a + b }
"multiply" -> { a, b -> a * b }
else -> { a, b -> 0 }
}
}
val addOp = getMathOperation("add")
println(addOp(10, 5)) // 15
纯函数(Pure Functions)
纯函数特点:
- 相同输入总是产生相同输出
- 没有副作用(不修改外部状态)
// ✅ 纯函数
fun add(a: Int, b: Int): Int = a + b
// ✅ 纯函数
fun double(numbers: List<Int>): List<Int> =
numbers.map { it * 2 } // 不修改原列表
// ❌ 非纯函数(有副作用)
var count = 0
fun incrementAndReturn(): Int {
count++ // 修改外部状态
return count
}
// ❌ 非纯函数(随机结果)
fun randomInt(): Int = Random.nextInt()
不可变性(Immutability)
// 推荐:使用不可变集合
val numbers = listOf(1, 2, 3)
val doubled = numbers.map { it * 2 } // 返回新列表
// 避免:可变集合
val mutableNumbers = mutableListOf(1, 2, 3)
mutableNumbers.add(4) // 修改原列表
高阶函数
高阶函数:接收函数作为参数或返回函数的函数。
常见高阶函数
// 1. filter - 筛选
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evens = numbers.filter { it % 2 == 0 } // [2, 4, 6]
// 2. map - 转换
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10, 12]
// 3. reduce - 聚合
val sum = numbers.reduce { acc, n -> acc + n } // 21
// 4. fold - 带初始值的聚合
val product = numbers.fold(1) { acc, n -> acc * n } // 720
// 5. forEach - 遍历(有副作用)
numbers.forEach { println(it) }
// 6. takeWhile - 取满足条件的前缀
val prefix = numbers.takeWhile { it < 4 } // [1, 2, 3]
// 7. groupBy - 分组
val grouped = numbers.groupBy { it % 2 == 0 }
// {false=[1, 3, 5], true=[2, 4, 6]}
自定义高阶函数
// 示例1:repeat高阶函数
fun repeat(times: Int, action: (Int) -> Unit) {
for (i in 1..times) {
action(i)
}
}
repeat(3) { index ->
println("执行第 $index 次")
}
// 输出:
// 执行第 1 次
// 执行第 2 次
// 执行第 3 次
// 示例2:测量执行时间
fun <T> measureTime(block: () -> T): Pair<T, Long> {
val start = System.currentTimeMillis()
val result = block()
val duration = System.currentTimeMillis() - start
return result to duration
}
val (result, time) = measureTime {
(1..1000000).sum()
}
println("结果: $result, 耗时: ${time}ms")
Lambda表达式
Lambda语法
// 完整语法
val sum: (Int, Int) -> Int = { a: Int, b: Int -> a + b }
// 类型推断
val sum = { a: Int, b: Int -> a + b }
// 单参数可以用it
val double: (Int) -> Int = { it * 2 }
// 多行Lambda
val complex = { x: Int ->
val squared = x * x
val doubled = squared * 2
doubled // 最后一行是返回值
}
// 无参数Lambda
val greet = { println("Hello!") }
Lambda作为最后参数
当Lambda是最后一个参数时,可以移到括号外:
// 常规写法
numbers.filter({ it % 2 == 0 })
// Lambda在括号外(推荐)
numbers.filter { it % 2 == 0 }
// 多个参数
numbers.fold(0, { acc, n -> acc + n })
// 最后一个是Lambda,移出去
numbers.fold(0) { acc, n -> acc + n }
带接收者的Lambda
// StringBuilder的apply是带接收者的Lambda
val html = StringBuilder().apply {
append("<html>")
append("<body>")
append("Hello")
append("</body>")
append("</html>")
}.toString()
// 等价于
val html2 = buildString {
append("<html>")
append("<body>")
append("Hello")
append("</body>")
append("</html>")
}
扩展函数
基础用法
// 为String添加扩展函数
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
println("level".isPalindrome()) // true
println("hello".isPalindrome()) // false
// 为List添加扩展
fun <T> List<T>.second(): T? {
return if (this.size >= 2) this[1] else null
}
val numbers = listOf(1, 2, 3)
println(numbers.second()) // 2
扩展属性
// 只读扩展属性
val String.lastChar: Char
get() = this[length - 1]
println("Hello".lastChar) // o
// 可变扩展属性(必须有backing field)
var StringBuilder.lastChar: Char
get() = this[length - 1]
set(value) {
this.setCharAt(length - 1, value)
}
val sb = StringBuilder("Hello")
sb.lastChar = '!'
println(sb) // Hell!
扩展函数的作用域
class MyClass {
// 类内部扩展函数
private fun String.shout() = this.uppercase() + "!"
fun demo() {
println("hello".shout()) // HELLO!
}
}
// "hello".shout() // ❌ 外部不可见
标准库函数
let、run、with、apply、also
data class Person(var name: String, var age: Int)
// 1. let - 参数为it,返回Lambda结果
val nameLength = Person("Alice", 25).let {
println(it.name) // 使用it访问
it.name.length // 返回值
} // 5
// 2. run - 参数为this,返回Lambda结果
val ageAfter10Years = Person("Bob", 30).run {
age += 10 // 直接访问属性
age // 返回值
} // 40
// 3. with - 非扩展函数,参数为this
val description = with(Person("Charlie", 35)) {
"$name is $age years old" // 直接访问
} // "Charlie is 35 years old"
// 4. apply - 参数为this,返回接收者
val person = Person("David", 40).apply {
age = 41 // 修改属性
name = "Dave"
} // 返回修改后的person对象
// 5. also - 参数为it,返回接收者
val samePerson = Person("Eve", 45).also {
println("Created: ${it.name}") // 使用it
} // 返回原对象
选择建议:
| 函数 | 使用场景 |
|---|---|
| let | 对可空对象执行操作,变量作用域限定 |
| run | 对象配置+计算结果 |
| with | 对一个对象执行多个操作 |
| apply | 对象初始化、配置(Builder模式) |
| also | 附加操作(日志、验证) |
takeIf与takeUnless
// takeIf - 条件满足返回自己,否则返回null
val evenNumber = 42.takeIf { it % 2 == 0 } // 42
val oddNumber = 43.takeIf { it % 2 == 0 } // null
// takeUnless - 条件不满足返回自己
val notZero = 5.takeUnless { it == 0 } // 5
val zero = 0.takeUnless { it == 0 } // null
// 实用示例
fun processInput(input: String?) {
input
?.takeIf { it.isNotBlank() }
?.let { println("处理: $it") }
?: println("输入为空")
}
内联函数
inline关键字
// 没有inline
fun normalHigherOrder(block: () -> Unit) {
println("Before")
block()
println("After")
}
// 有inline
inline fun inlinedHigherOrder(block: () -> Unit) {
println("Before")
block()
println("After")
}
// 调用
normalHigherOrder { println("Body") }
inlinedHigherOrder { println("Body") }
编译后的代码:
// normalHigherOrder编译后(伪代码)
Function0 function = new Function0() {
public void invoke() {
println("Body");
}
};
normalHigherOrder(function); // 创建Function对象
// inlinedHigherOrder编译后(伪代码)
println("Before");
println("Body"); // Lambda内容直接内联
println("After"); // 无需创建Function对象
内联的好处
- 避免对象创建:不创建Function对象
- 支持非局部返回:可以从Lambda中直接return
- 性能提升:减少函数调用开销
// 非内联:不能从Lambda中return
fun normalForEach(list: List<Int>, action: (Int) -> Unit) {
for (item in list) {
action(item)
}
}
fun demo1() {
normalForEach(listOf(1, 2, 3)) {
if (it == 2) return // ❌ 编译错误
println(it)
}
}
// 内联:支持非局部返回
inline fun inlineForEach(list: List<Int>, action: (Int) -> Unit) {
for (item in list) {
action(item)
}
}
fun demo2() {
inlineForEach(listOf(1, 2, 3)) {
if (it == 2) return // ✅ 从demo2函数返回
println(it)
}
println("不会执行")
}
noinline与crossinline
// noinline - 阻止特定参数内联
inline fun mixed(
inlined: () -> Unit,
noinline notInlined: () -> Unit
) {
inlined()
notInlined()
saveForLater(notInlined) // 需要存储,不能内联
}
// crossinline - 禁止非局部返回
inline fun runInAnotherThread(crossinline block: () -> Unit) {
thread {
block() // 在另一个线程,不能非局部返回
}
}
函数组合
基础组合
// 函数组合:f(g(x))
infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C {
return { a -> this(g(a)) }
}
val addOne: (Int) -> Int = { it + 1 }
val double: (Int) -> Int = { it * 2 }
// 组合:先double,再addOne
val doubleThenAddOne = addOne compose double
println(doubleThenAddOne(5)) // 11 ((5 * 2) + 1)
// 管道:先addOne,再double
infix fun <A, B, C> ((A) -> B).pipe(g: (B) -> C): (A) -> C {
return { a -> g(this(a)) }
}
val addOneThenDouble = addOne pipe double
println(addOneThenDouble(5)) // 12 ((5 + 1) * 2)
柯里化(Currying)
// 普通函数
fun add(a: Int, b: Int, c: Int): Int = a + b + c
// 柯里化版本
fun curriedAdd(a: Int): (Int) -> (Int) -> Int {
return { b ->
{ c ->
a + b + c
}
}
}
val add5 = curriedAdd(5)
val add5And3 = add5(3)
val result = add5And3(2) // 10
// 简化写法
val result2 = curriedAdd(5)(3)(2) // 10
偏函数应用(Partial Application)
// 偏函数应用
fun <A, B, C> ((A, B) -> C).partial(a: A): (B) -> C {
return { b -> this(a, b) }
}
val multiply: (Int, Int) -> Int = { a, b -> a * b }
val multiplyBy5 = multiply.partial(5)
println(multiplyBy5(3)) // 15
println(multiplyBy5(7)) // 35
函数式集合操作
链式调用
data class User(val name: String, val age: Int, val city: String)
val users = listOf(
User("Alice", 28, "北京"),
User("Bob", 32, "上海"),
User("Charlie", 25, "北京"),
User("David", 35, "深圳"),
User("Eve", 30, "上海")
)
// 复杂的链式操作
val result = users
.filter { it.age >= 30 } // 筛选30岁以上
.map { it.name to it.city } // 转换为姓名-城市对
.groupBy { it.second } // 按城市分组
.mapValues { (_, pairs) -> // 提取姓名列表
pairs.map { it.first }
}
println(result)
// {上海=[Bob, Eve], 深圳=[David]}
Sequence惰性求值
// List:立即求值(Eager)
val list = listOf(1, 2, 3, 4, 5)
val result1 = list
.map {
println("map: $it")
it * 2
}
.filter {
println("filter: $it")
it > 5
}
// 输出:
// map: 1
// map: 2
// map: 3
// map: 4
// map: 5
// filter: 2
// filter: 4
// filter: 6
// filter: 8
// filter: 10
// Sequence:惰性求值(Lazy)
val sequence = sequenceOf(1, 2, 3, 4, 5)
val result2 = sequence
.map {
println("map: $it")
it * 2
}
.filter {
println("filter: $it")
it > 5
}
.toList() // 终端操作才执行
// 输出:
// map: 1
// filter: 2
// map: 2
// filter: 4
// map: 3
// filter: 6
// map: 4
// map: 5
// 性能对比
val hugeList = (1..1_000_000).toList()
// List:会创建中间集合
val time1 = measureTimeMillis {
hugeList
.map { it * 2 }
.filter { it > 1000 }
.take(10)
}
// Sequence:只处理需要的元素
val time2 = measureTimeMillis {
hugeList.asSequence()
.map { it * 2 }
.filter { it > 1000 }
.take(10)
.toList()
}
println("List: ${time1}ms, Sequence: ${time2}ms")
// List: 150ms, Sequence: 2ms
实战案例:DSL构建
HTML DSL
// DSL定义
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HTML {
private val elements = mutableListOf<String>()
fun head(block: Head.() -> Unit) {
val head = Head().apply(block)
elements.add("<head>${head.build()}</head>")
}
fun body(block: Body.() -> Unit) {
val body = Body().apply(block)
elements.add("<body>${body.build()}</body>")
}
fun build() = "<html>${elements.joinToString("")}</html>"
}
@HtmlDsl
class Head {
private val elements = mutableListOf<String>()
fun title(text: String) {
elements.add("<title>$text</title>")
}
fun build() = elements.joinToString("")
}
@HtmlDsl
class Body {
private val elements = mutableListOf<String>()
fun h1(text: String) {
elements.add("<h1>$text</h1>")
}
fun p(text: String) {
elements.add("<p>$text</p>")
}
fun build() = elements.joinToString("")
}
// DSL使用
fun html(block: HTML.() -> Unit): String {
return HTML().apply(block).build()
}
// 生成HTML
val page = html {
head {
title("我的页面")
}
body {
h1("欢迎")
p("这是一个段落")
}
}
println(page)
// <html><head><title>我的页面</title></head><body><h1>欢迎</h1><p>这是一个段落</p></body></html>
常见陷阱与最佳实践
❌ 陷阱1:过度使用函数式
// ❌ 过度函数式(可读性差)
val result = users
.filter { it.age > 18 }
.map { it.name }
.filter { it.startsWith("A") }
.map { it.uppercase() }
.filter { it.length > 5 }
// ✅ 合并filter
val result = users
.filter { it.age > 18 && it.name.startsWith("A") }
.map { it.name.uppercase() }
.filter { it.length > 5 }
❌ 陷阱2:忽略性能
// ❌ List:多次遍历
(1..1000000)
.map { it * 2 }
.filter { it > 1000 }
.take(10)
// ✅ Sequence:惰性求值
(1..1000000).asSequence()
.map { it * 2 }
.filter { it > 1000 }
.take(10)
.toList()
✅ 最佳实践1:优先使用标准库函数
// ❌ 手动实现
fun sumOfSquares(numbers: List<Int>): Int {
var sum = 0
for (n in numbers) {
sum += n * n
}
return sum
}
// ✅ 使用标准库
fun sumOfSquares(numbers: List<Int>): Int =
numbers.sumOf { it * it }
✅ 最佳实践2:函数命名清晰
// ❌ 命名不清
val f: (Int) -> Int = { it * 2 }
// ✅ 命名清晰
val double: (Int) -> Int = { it * 2 }
val isEven: (Int) -> Boolean = { it % 2 == 0 }
小结
本文全面介绍了Kotlin的函数式编程特性:
核心概念
- 第一等函数:函数可赋值、传递、返回
- 高阶函数:接收或返回函数的函数
- Lambda表达式:简洁的匿名函数语法
- 纯函数:无副作用,相同输入产生相同输出
关键技能
- 扩展函数:为已有类添加新功能
- 标准库函数:let、run、with、apply、also
- 内联函数:性能优化,支持非局部返回
- 函数组合:compose、pipe、柯里化
最佳实践
- ✅ 优先使用标准库函数
- ✅ 使用Sequence处理大数据
- ✅ 合理使用inline优化性能
- ✅ 函数命名清晰表达意图
- ✅ 避免过度函数式(保持可读性)
下期预告
下一篇文章将进入第三阶段"高级应用",讲解DSL设计:构建类型安全的领域语言!
练习题
基础练习
练习1:实现一个通用的retry函数
// TODO: 失败时重试指定次数
fun <T> retry(times: Int, block: () -> T): T
练习2:实现一个带缓存的函数
// TODO: 缓存函数计算结果
fun <A, R> ((A) -> R).memoize(): (A) -> R
进阶练习
练习3:实现一个简单的验证DSL
// 期望用法:
val validation = validate<User> {
User::name must notBeBlank()
User::age must beInRange(0, 150)
User::email must matchPattern(emailRegex)
}
练习4:实现函数管道运算符
// TODO: 实现管道运算符
infix fun <A, B> A.pipe(f: (A) -> B): B
// 使用:
val result = 5 pipe { it + 1 } pipe { it * 2 } // 12
参考资料
系列文章导航:
如果这篇文章对你有帮助,欢迎点赞、收藏、分享!有任何问题或建议,欢迎在评论区留言讨论。让我们一起学习,一起成长!
也欢迎访问我的个人主页发现更多宝藏资源