第7章:高阶函数(Higher-Order Functions)
一、导读
1.1 本章学习目标
第7章是 Kotlin 函数式编程的进阶内容。在第4章 Lambda 编程的基础上,本章深入讲解扩展函数、扩展属性、委托属性以及集合的函数式操作的高级用法。
本章的核心学习目标包括:
- 深入理解扩展函数和扩展属性的原理和限制
- 掌握委托属性的完整机制:
by关键字、getValue/setValue约定 - 学会使用标准委托:
lazy、observable、map委托 - 掌握集合操作的高级用法:
sequence、zip、windowed、chunked - 理解函数组合与偏函数应用
1.2 为什么本章如此重要
扩展函数是 Kotlin 最具威力的特性之一。它让你能够为不拥有修改权的类(如 JDK 类、第三方库类)添加新的方法,而不需要继承或使用工具类。Kotlin 标准库大量使用扩展函数来增强 Java 的类——String.length、List.first()、File.readText() 等都是扩展函数。
委托属性则是 Kotlin 将"组合优于继承"原则贯彻到底的体现。lazy、observable、自定义委托让你能够将属性的存储和验证逻辑从属性声明中分离出来,实现真正的关注点分离。
1.3 核心问题引导
扩展函数在底层是如何实现的?它有性能开销吗?
扩展函数和成员函数的优先级规则是什么?
委托属性的 getValue 和 setValue 是如何被编译器调用的?
lazy 委托的线程安全性是如何保证的?有哪些初始化模式?
什么时候应该使用 Sequence 而不是 List 的集合操作?
二、核心概念详解
2.1 扩展函数
2.1.1 基本语法
// 为 String 添加扩展函数
fun String.lastChar(): Char = this.get(this.length - 1)
// 使用
println("Kotlin".lastChar()) // n
// this 引用接收者对象
fun String.removeLastChar(): String = this.dropLast(1)2.1.2 底层实现
扩展函数在编译后被转换为静态方法,接收者对象作为第一个参数:
// Kotlin 源码
fun String.lastChar(): Char = this.get(this.length - 1)
// 编译后的 Java 字节码(等价)
public static char lastChar(String receiver) {
return receiver.charAt(receiver.length() - 1);
}这意味着扩展函数不能访问接收者的私有成员,它只是一个普通的静态方法。
2.1.3 扩展函数 vs 成员函数
当扩展函数和成员函数签名相同时,成员函数优先:
class MyClass {
fun greet() = "Hello from member"
}
fun MyClass.greet() = "Hello from extension"
val obj = MyClass()
println(obj.greet()) // "Hello from member"2.1.4 扩展属性
val String.lastChar: Char
get() = this.get(this.length - 1)
var StringBuilder.lastChar: Char
get() = this.get(this.length - 1)
set(value: Char) {
this.setCharAt(this.length - 1, value)
}
// 使用
println("Kotlin".lastChar) // n2.2 高阶函数实战
2.2.1 函数组合
// 函数组合:(f compose g)(x) = f(g(x))
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { a -> f(g(a)) }
}
// 使用
val double: (Int) -> Int = { it * 2 }
val addOne: (Int) -> Int = { it + 1 }
val doubleThenAddOne = compose(addOne, double)
println(doubleThenAddOne(3)) // 7: (3 * 2) + 12.2.2 偏函数应用
// 偏函数应用:固定部分参数
fun <A, B, C> partial(f: (A, B) -> C, a: A): (B) -> C {
return { b -> f(a, b) }
}
fun add(a: Int, b: Int): Int = a + b
val add5 = partial(::add, 5)
println(add5(3)) // 8
println(add5(10)) // 152.2.3 记忆化
fun <A, R> ((A) -> R).memoize(): (A) -> R {
val cache = mutableMapOf<A, R>()
return { a ->
cache.getOrPut(a) { this(a) }
}
}
val fibonacci: (Int) -> Long = { n ->
when {
n <= 1 -> n.toLong()
else -> fibonacci(n - 1) + fibonacci(n - 2)
}
}.memoize()
println(fibonacci(50)) // 快速计算,不会重复计算2.3 委托属性
2.3.1 基本语法
class Example {
var name: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "delegated value"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("Setting ${property.name} to $value")
}
}2.3.2 lazy 委托
class EmailService {
val connection: Connection by lazy {
println("Creating connection...")
Connection.create() // 只在第一次访问时执行
}
fun sendEmail(to: String, subject: String) {
connection.send(to, subject) // 第一次访问触发初始化
}
}
// lazy 的初始化模式
val singleThreaded by lazy(LazyThreadSafetyMode.NONE) { expensiveInit() }
val synchronized by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { expensiveInit() }
val publication by lazy(LazyThreadSafetyMode.PUBLICATION) { expensiveInit() }2.3.3 observable 委托
class User {
var name: String by Delegates.observable("default") { prop, old, new ->
println("Property ${prop.name} changed from $old to $new")
}
}
val user = User()
user.name = "Alice" // 输出: Property name changed from default to Alice
user.name = "Bob" // 输出: Property name changed from Alice to Bob2.3.4 Map 委托
class Config(map: Map<String, Any?>) {
val host: String by map
val port: Int by map
val debug: Boolean by map
}
val config = Config(mapOf(
"host" to "localhost",
"port" to 8080,
"debug" to true
))
println(config.host) // localhost
println(config.port) // 80802.3.5 自定义委托
class TrimmedString {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value.trim()
}
}
class UserProfile {
var name: String by TrimmedString()
var email: String by TrimmedString()
}
val profile = UserProfile()
profile.name = " Alice "
println("'${profile.name}'") // 'Alice'2.4 集合操作进阶
2.4.1 Sequence(惰性序列)
val numbers = (1..1000000).toList()
// 集合操作:每步都创建新集合(立即求值)
val result1 = numbers
.filter { println("filter: $it"); it % 2 == 0 }
.map { println("map: $it"); it * 2 }
.take(5)
// 序列操作:惰性求值,每个元素依次通过所有操作
val result2 = numbers.asSequence()
.filter { println("filter: $it"); it % 2 == 0 }
.map { println("map: $it"); it * 2 }
.take(5)
.toList()序列的优势在于避免了中间集合的创建,对于大数据集的性能提升显著。
2.4.2 高级集合操作
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// windowed: 滑动窗口
val windows = numbers.windowed(3)
// [[1,2,3], [2,3,4], [3,4,5], ...]
// chunked: 分块
val chunks = numbers.chunked(3)
// [[1,2,3], [4,5,6], [7,8,9], [10]]
// zip: 配对
val names = listOf("Alice", "Bob", "Charlie")
val ages = listOf(30, 25, 35)
val paired = names.zip(ages)
// [(Alice, 30), (Bob, 25), (Charlie, 35)]
// unzip: 解包
val (unzippedNames, unzippedAges) = paired.unzip()
// flatMap: 展平映射
val sentences = listOf("Hello World", "Kotlin is Fun")
val words = sentences.flatMap { it.split(" ") }
// [Hello, World, Kotlin, is, Fun]
// groupBy: 分组
val grouped = numbers.groupBy { it % 3 }
// {1=[1,4,7,10], 2=[2,5,8], 0=[3,6,9]}
// associate: 转为 Map
val nameMap = names.associateWith { it.length }
// {Alice=5, Bob=3, Charlie=7}三、实战案例
3.1 案例一:自定义属性验证委托
class ValidatedString(private val validator: (String) -> Boolean, private val message: String) {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
require(validator(value)) { "Validation failed for ${property.name}: $message" }
this.value = value
}
}
class EmailContact {
var email: String by ValidatedString(
validator = { it.contains("@") && it.contains(".") },
message = "Must be a valid email address"
)
var name: String by ValidatedString(
validator = { it.length >= 2 && it.length <= 50 },
message = "Name must be between 2 and 50 characters"
)
}
fun main() {
val contact = EmailContact()
contact.name = "Alice"
contact.email = "alice@example.com"
try {
contact.email = "invalid-email"
} catch (e: IllegalArgumentException) {
println(e.message)
}
}3.2 案例二:实现一个简单的依赖注入容器
class DIContainer {
private val factories = mutableMapOf<String, () -> Any>()
private val singletons = mutableMapOf<String, Any>()
inline fun <reified T : Any> register(name: String = T::class.java.simpleName, noinline factory: () -> T) {
factories[name] = factory
}
inline fun <reified T : Any> registerSingleton(name: String = T::class.java.simpleName, noinline factory: () -> T) {
factories[name] = factory
}
inline fun <reified T : Any> resolve(name: String = T::class.java.simpleName): T {
val singleton = singletons[name]
if (singleton != null) return singleton as T
val factory = factories[name] ?: throw IllegalArgumentException("No registration for $name")
val instance = factory()
singletons[name] = instance
return instance as T
}
}
// 使用
val container = DIContainer()
container.register<DatabaseService> { DatabaseService("jdbc:...") }
container.register<UserRepository> { UserRepository(container.resolve()) }
container.register<UserService> { UserService(container.resolve()) }
val userService = container.resolve<UserService>()3.3 案例三:使用 Sequence 处理大文件
fun processLargeFile(filePath: String) {
val result = File(filePath)
.useLines { lines ->
lines.asSequence()
.filter { it.isNotBlank() }
.map { it.trim() }
.map { it.split(",") }
.filter { it.size >= 3 }
.map { Record(it[0], it[1], it[2].toIntOrNull() ?: 0) }
.filter { it.value > 100 }
.sortedByDescending { it.value }
.take(10)
.toList()
}
result.forEach { println(it) }
}四、本章小结
第7章深入讲解了 Kotlin 的高阶特性:
扩展函数:在不修改原始类的情况下为其添加新方法。底层实现为静态方法,接收者作为第一个参数。
委托属性:通过 by 关键字将属性的存取逻辑委托给其他对象。标准委托包括 lazy、observable、map。
集合操作进阶:Sequence 实现惰性求值,windowed、chunked、zip 等提供了丰富的数据处理能力。
五、思考与练习
思考题
扩展函数为什么不能访问接收者的私有成员?这对设计有什么影响?
lazy 委托的三种线程安全模式有什么区别?在什么场景下选择哪种?
Sequence 和 List 的集合操作在性能上有什么区别?什么时候应该使用 Sequence?
委托属性如何帮助实现"关注点分离"?
编程练习
实现一个 LoggingDelegate,在属性值被读取和设置时打印日志。
为 List<Int> 编写扩展函数 median(),返回列表的中位数。
使用 Sequence 处理一个模拟的大数据集(100 万个数字),找出前 10 个满足特定条件的元素。