主题
第 4 章 函数与 Lambda(高阶函数 · 扩展函数)
学习目标:能写出"参数有默认值 / 命名参数 / 单表达式函数 / vararg / 中缀函数 / 扩展函数 / 高阶函数 / Lambda" 各种现代函数;理解函数式编程的"基本词汇",开始写出"像 Kotlin 的代码"。
4.1 函数定义:Java 写不出的优雅
4.1.1 基本形式
kotlin
fun greet(name: String): String {
return "Hello, $name!"
}📌 关键字
fun比 Java 的public static String简洁多了。
4.1.2 单表达式函数(Single-expression Function)
如果函数体只有一个表达式,可以省略花括号、return 和返回类型:
kotlin
fun greet(name: String) = "Hello, $name!"
fun max(a: Int, b: Int) = if (a > b) a else b
fun isAdult(age: Int) = age >= 18💡 Kotlin 风格:能写成单表达式就写成单表达式,更紧凑也更易读。
4.1.3 默认参数(Default Arguments)
Java 没有,Kotlin 有,这是 Kotlin 最实用的特性之一。
kotlin
fun connect(host: String = "localhost", port: Int = 8080, timeout: Int = 5000) {
println("Connecting to $host:$port (timeout=${timeout}ms)")
}
connect() // localhost:8080, 5000ms
connect("example.com") // example.com:8080, 5000ms
connect("example.com", 9090) // example.com:9090, 5000ms📌 解决了什么问题:Java 里要支持"可选参数"得写一堆重载(5 个参数 = 32 个重载),Kotlin 一行搞定。
4.1.4 命名参数(Named Arguments)
kotlin
connect(timeout = 10000) // 只指定 timeout,其它用默认
connect(host = "example.com", timeout = 10000) // 跳过 port
connect(port = 9090, host = "example.com") // 顺序无所谓💡 生活化类比:传统调用是「按位填表」(必须按顺序),命名参数是「贴标签」(哪个先填都行)。
真实场景:替代 Builder 模式
Java 里复杂对象创建常用 Builder 模式:
java
new Request.Builder()
.url("http://example.com")
.method("POST")
.timeout(5000)
.build();Kotlin 用默认参数 + 命名参数直接搞定:
kotlin
data class Request(
val url: String,
val method: String = "GET",
val timeout: Int = 5000,
val headers: Map<String, String> = emptyMap(),
)
Request(url = "http://example.com", method = "POST", timeout = 5000)4.1.5 可变参数 vararg
kotlin
fun sum(vararg numbers: Int): Int = numbers.sum()
sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5, 6) // 21
// 把数组展开成 vararg
val nums = intArrayOf(1, 2, 3)
sum(*nums) // 注意 * 号(spread 操作符)4.1.6 中缀函数 infix
kotlin
infix fun Int.times(s: String) = s.repeat(this)
3 times "Hi " // "Hi Hi Hi "
3.times("Hi ") // 等价
// 标准库里大量使用:
val map = mapOf("a" to 1, "b" to 2) // "a" to 1 就是中缀调用,相当于 "a".to(1)📌 infix 限制:必须是成员函数或扩展函数;只能有一个参数;参数不能是 vararg。
4.2 扩展函数:Kotlin 的"魔法"
4.2.1 痛点:Java 给别人的类加方法很麻烦
想给 String 加一个 toCamelCase() 方法?Java 里只能写工具类:
java
public class StringUtils {
public static String toCamelCase(String s) { /* ... */ }
}
StringUtils.toCamelCase("hello_world");调用起来又长又丑。
4.2.2 Kotlin:一行搞定
kotlin
fun String.toCamelCase(): String =
this.split("_").joinToString("") {
it.replaceFirstChar { c -> c.uppercase() }
}
"hello_kotlin_world".toCamelCase() // "HelloKotlinWorld"调用起来跟内置方法一模一样。
4.2.3 扩展函数的本质:编译后是静态方法
kotlin
// 你写的代码
fun String.shout() = this.uppercase() + "!"
// 编译后等价的 Java
public static String shout(String $this$shout) {
return $this$shout.toUpperCase() + "!";
}📌 重要事实:扩展函数不是真的把方法加到 String 类里,只是语法糖。
String类本身没变。
4.2.4 扩展函数的"陷阱"
kotlin
open class A
class B : A()
fun A.hello() = "I am A"
fun B.hello() = "I am B"
val a: A = B()
println(a.hello()) // "I am A" ← 注意!📌 结论:扩展函数静态分派(按编译期类型决定调哪个),不像类的方法是动态分派(按运行时类型)。这是面试常考点。
4.2.5 实战:标准库里的扩展函数
Kotlin 标准库本身就大量用扩展函数:
kotlin
// String 上扩展的(Java String 没有的)
"hello".reversed() // "olleh"
"hello".toCharArray() // ['h','e','l','l','o']
"hello".take(3) // "hel"
"hello".chunked(2) // ["he", "ll", "o"]
// List 上扩展的
listOf(1,2,3).sum()
listOf(1,2,3).average()
listOf("a","b","c").joinToString(",") // "a,b,c"4.3 高阶函数与 Lambda
4.3.1 函数也是值(First-class Function)
Kotlin 里函数可以当变量传、当参数收、当返回值返:
kotlin
val greet: (String) -> String = { name -> "Hello, $name!" }
println(greet("Alice")) // Hello, Alice!
// 函数参数
fun apply(x: Int, op: (Int) -> Int): Int = op(x)
apply(5, { it * 2 }) // 10
apply(5) { it * 2 } // 10(lambda 在最后可以拖出括号外)
// 函数返回函数
fun makeAdder(n: Int): (Int) -> Int = { x -> x + n }
val add5 = makeAdder(5)
add5(3) // 84.3.2 Lambda 语法
{ 参数列表 -> 函数体 }kotlin
val sum = { a: Int, b: Int -> a + b }
sum(1, 2) // 3
// 类型可以推断
val nums = listOf(1, 2, 3)
nums.map { x -> x * 2 } // [2, 4, 6]
// 单参数可省略,用 it 代替
nums.map { it * 2 } // 推荐
// 多参数 lambda
mapOf("a" to 1, "b" to 2).forEach { (k, v) ->
println("$k = $v")
}📌
it的来源:当 lambda 只有一个参数时,编译器自动注入一个名为it的变量。不用就别命名。
4.3.3 函数类型
kotlin
() -> Unit // 无参数无返回值
(Int) -> String // 收 Int 返 String
(Int, Int) -> Int // 二元函数
(String) -> Unit // 无返回值
suspend (Int) -> String // suspend 函数(第 9 章)
String.(Int) -> Char // String 上的扩展函数类型(receiver type)4.3.4 高阶函数实战:自己实现 forEach
kotlin
fun <T> List<T>.myForEach(action: (T) -> Unit) {
for (item in this) action(item)
}
listOf(1, 2, 3).myForEach { println("got: $it") }4.3.5 inline:高阶函数的"零成本抽象"
每次调用 lambda 都要创建一个 Function 对象,频繁调用会有 GC 压力。Kotlin 用 inline 解决:
kotlin
inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
for (item in this) action(item)
}inline 让编译器把高阶函数和它的 lambda 直接嵌入到调用点,不再生成中间对象。
📌 何时用 inline:高阶函数 + lambda 频繁被调用的场景。Kotlin 标准库的
map/filter/forEach都是 inline 的。
4.4 闭包
Lambda 可以捕获外部变量:
kotlin
fun makeCounter(): () -> Int {
var count = 0
return { count++ }
}
val counter = makeCounter()
counter() // 0
counter() // 1
counter() // 2每次调用 counter() 都返回当前 count 值,然后 count 自增。Lambda "记住" 了 count 这个变量。
📌 跟 Java 的区别:Java Lambda 只能捕获
final或 effectively final 变量;Kotlin 可以修改捕获的var变量。
4.5 一个综合例子:自定义 retry 函数
实现一个"出错就重试 N 次"的高阶函数:
kotlin
inline fun <T> retry(times: Int = 3, block: (attempt: Int) -> T): T {
var lastError: Throwable? = null
repeat(times) { attempt ->
try {
return block(attempt + 1)
} catch (e: Exception) {
lastError = e
println("第 ${attempt + 1} 次尝试失败: ${e.message}")
}
}
throw lastError ?: IllegalStateException("retry failed")
}
fun main() {
val result = retry(times = 5) { attempt ->
if (attempt < 3) throw RuntimeException("fake error")
"成功!第 $attempt 次"
}
println(result)
}输出:
第 1 次尝试失败: fake error
第 2 次尝试失败: fake error
成功!第 3 次4.6 章末小结
★ 第 4 章核心知识图谱 ★
│
┌──────────────┼──────────────┐
│ │ │
┌──▼──┐ ┌──▼───┐ ┌──▼───┐
│ 函数 │ │ 扩展 │ │ Lambda│
├─────┤ ├──────┤ ├──────┤
│默认参│ │给已有 │ │() -> X│
│命名参│ │类加方 │ │it 参数│
│vararg│ │静态分 │ │闭包 │
│infix │ │派 │ │inline │
└─────┘ └──────┘ └──────┘🎤 4.7 章末面试题(10 道)
Q1. Kotlin 的"单表达式函数"是什么?
答:当函数体只有一个表达式时,可以省略花括号、return 和(通常)返回类型:
kotlin
fun max(a: Int, b: Int) = if (a > b) a else b等价于:
kotlin
fun max(a: Int, b: Int): Int { return if (a > b) a else b }适合一行能搞定的工具函数。
Q2. 默认参数能解决什么问题?编译后是怎样的?
答:解决"可选参数"问题。Java 没有默认参数,需要写一堆重载:
kotlin
// Kotlin
fun connect(host: String = "localhost", port: Int = 8080) { ... }
// 编译后产生:
// 1. 主方法 connect(host, port)
// 2. 一个合成方法 connect$default(host, port, mask, ignored)
// 根据 mask 决定哪些参数用默认值注解 @JvmOverloads 可以让 Kotlin 编译器生成所有重载,方便 Java 调用。
Q3. 扩展函数是真的"加方法到类里"了吗?
答:不是。扩展函数本质是静态方法,只是 IDE 和编译器让你"看起来像方法调用"。
- 编译后:
fun String.shout() = ...→public static String shout(String $this$shout) - 不会修改 String 类,所以 String 的字段、private 方法都拿不到
- 静态分派:按编译期类型决定调哪个扩展函数
Q4. 扩展函数能访问私有成员吗?为什么?
答:不能访问 private / protected 成员,因为它本质是外部静态方法,只能调到 public / internal 的部分。
kotlin
class User(private val secret: String)
fun User.exposeSecret() = this.secret // ❌ 编译报错:Cannot access 'secret'如果想访问 private 字段,要么放在 User 类里写,要么把字段改成 internal。
Q5. 扩展函数和成员函数同名时调谁?
答:成员函数优先。这是为了防止"扩展函数被滥用,导致原作者修改类的行为"。
kotlin
class A { fun hello() = "member" }
fun A.hello() = "extension"
A().hello() // "member"
// IDE 会有警告:扩展函数被遮蔽了Q6. Lambda 里的 it 是怎么来的?
答:当 lambda 只有一个参数时,Kotlin 编译器自动注入一个名为 it 的变量代表这个参数。多参数时必须显式命名:
kotlin
list.map { it * 2 } // 单参,自动用 it
list.mapIndexed { i, v -> "$i: $v" } // 多参,必须显式如果觉得 it 不够清楚,也可以显式命名:list.map { x -> x * 2 }。
Q7. inline 函数是干嘛的?
答:让编译器把函数体直接插入调用处,避免高阶函数每次调用都创建 Function 对象。
kotlin
inline fun <T> List<T>.myForEach(action: (T) -> Unit) {
for (item in this) action(item)
}调用 list.myForEach { println(it) } 编译后等价于:
kotlin
for (item in list) { println(item) } // lambda 直接展开好处:消除 lambda 创建开销 + 允许在 lambda 里 return 跳出外层函数(non-local return)。
代价:编译产物变大;不能用于递归。
Q8. Java 里 lambda 能修改外部变量吗?Kotlin 呢?
答:
- Java:lambda 只能捕获
final或 effectively final 的变量。想修改要用 AtomicInteger 等容器。 - Kotlin:lambda 可以读写捕获的
var变量(编译器把它包成一个 Ref 对象)。
kotlin
fun makeCounter(): () -> Int {
var count = 0
return { count++ }
}
// Kotlin 编译会把 count 包成 Ref.IntRef 对象,lambda 通过引用访问Q9. vararg 和 Java 的可变参数有啥区别?
答:基本一样,都是接受多个参数 + 内部当数组用。两个差异:
- Kotlin 用
vararg关键字声明,类型是Array<T>或基本类型IntArray/LongArray等 - 传递展开:Kotlin 用
*args传数组当 vararg,Java 直接传数组也行
kotlin
fun sum(vararg nums: Int) = nums.sum()
val arr = intArrayOf(1, 2, 3)
sum(*arr) // 必须 * 号Q10. 高阶函数频繁创建 Lambda 对象会不会有性能问题?怎么优化?
答:会有。每个 lambda 都要:
- 创建 Function 接口的实现类
- 实例化(lambda 闭包变量越多,对象越大)
- 调用
invoke()有虚方法调度
优化办法:
inline:编译期把函数体展开,零运行时开销(标准库forEach/map/filter都是)crossinline:lambda 不能 non-local return(用于嵌套场景)noinline:某个 lambda 参数不内联(用于把 lambda 当对象返回)- 使用对象池 / 复用:避免 hot loop 里创建 lambda
📌 实际项目里,先用普通高阶函数,profile 发现 hot path 后再加
inline。
下一章 → 第 5 章 · Null 安全 →
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗
💻 示例代码
kotlin
/**
* 第 4 章 · 函数与 Lambda — 综合示例
*
* 涵盖:
* - 单表达式函数
* - 默认参数 / 命名参数
* - vararg / spread (*)
* - infix 中缀函数
* - 扩展函数
* - 高阶函数 + Lambda
* - 闭包
* - inline 函数
*/
// ========== 1. 默认参数 + 命名参数 ==========
fun connect(
host: String = "localhost",
port: Int = 8080,
timeout: Int = 5000,
useTls: Boolean = false
) {
println("Connecting → $host:$port (timeout=${timeout}ms, tls=$useTls)")
}
// ========== 2. 单表达式函数 ==========
fun max(a: Int, b: Int) = if (a > b) a else b
fun isAdult(age: Int) = age >= 18
// ========== 3. vararg ==========
fun sum(vararg numbers: Int) = numbers.sum()
// ========== 4. infix 中缀函数 ==========
infix fun Int.repeatStr(s: String) = s.repeat(this)
// ========== 5. 扩展函数 ==========
fun String.toCamelCase(): String =
this.split("_", "-", " ").joinToString("") { word ->
word.replaceFirstChar { it.uppercase() }
}
fun List<Int>.average2(): Double = if (isEmpty()) 0.0 else sum().toDouble() / size
// ========== 6. 高阶函数 ==========
fun applyOperation(x: Int, y: Int, op: (Int, Int) -> Int): Int = op(x, y)
inline fun <T> retry(times: Int = 3, block: (attempt: Int) -> T): T {
var lastError: Throwable? = null
repeat(times) { attempt ->
try {
return block(attempt + 1)
} catch (e: Exception) {
lastError = e
println(" ⚠️ 第 ${attempt + 1} 次失败: ${e.message}")
}
}
throw lastError ?: IllegalStateException("retry exhausted")
}
// ========== 7. 闭包 ==========
fun makeCounter(): () -> Int {
var count = 0
return {
count++
count
}
}
// ========== main ==========
fun main() {
println("=== 1. 默认参数 + 命名参数 ===")
connect()
connect("example.com")
connect(port = 9090) // 命名参数跳过 host
connect(host = "example.com", useTls = true) // 顺序无所谓
println("\n=== 2. 单表达式函数 ===")
println("max(3, 7) = ${max(3, 7)}")
println("isAdult(20) = ${isAdult(20)}")
println("\n=== 3. vararg ===")
println("sum(1,2,3) = ${sum(1, 2, 3)}")
val nums = intArrayOf(10, 20, 30)
println("sum(*nums) = ${sum(*nums)}") // spread 展开
println("\n=== 4. infix 中缀 ===")
println(3 repeatStr "Hi ") // "Hi Hi Hi "
println("a" to 1) // Pair("a", 1),to 也是 infix
println("\n=== 5. 扩展函数 ===")
println("hello_kotlin_world".toCamelCase()) // "HelloKotlinWorld"
println("my-app-name".toCamelCase()) // "MyAppName"
println(listOf(1, 2, 3, 4, 5).average2()) // 3.0
println("\n=== 6. 高阶函数 + Lambda ===")
println(applyOperation(5, 3) { a, b -> a + b }) // 8 (加法 lambda)
println(applyOperation(5, 3) { a, b -> a * b }) // 15 (乘法 lambda)
val sub: (Int, Int) -> Int = { a, b -> a - b }
println(applyOperation(10, 4, sub)) // 6
println("\n=== 7. retry 高阶函数 ===")
val result = retry(times = 5) { attempt ->
if (attempt < 3) throw RuntimeException("假装失败")
"✅ 成功!第 $attempt 次"
}
println(result)
println("\n=== 8. 闭包:counter 记住 count 变量 ===")
val counter = makeCounter()
println(counter()) // 1
println(counter()) // 2
println(counter()) // 3
println("\n=== 9. 集合的高阶函数 ===")
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
println("偶数: ${list.filter { it % 2 == 0 }}")
println("平方: ${list.map { it * it }}")
println("总和: ${list.sum()}")
println("第一个 > 5: ${list.first { it > 5 }}")
println("分组奇偶: ${list.groupBy { if (it % 2 == 0) "偶" else "奇" }}")
}