跳到主要内容

Kotlin 函数与 Lambda

问题

Kotlin 函数式编程有哪些特性?扩展函数、高阶函数、Lambda 各有什么用途?

答案

1. 函数声明与调用

// 基本函数
fun sum(a: Int, b: Int): Int {
return a + b
}

// 单表达式函数(省略花括号和返回类型)
fun sum(a: Int, b: Int) = a + b

// 默认参数 + 命名参数 —— 减少方法重载
fun createUser(
name: String,
age: Int = 0,
email: String = ""
) = User(name, age, email)

createUser("Alice") // 使用默认值
createUser("Bob", email = "bob@test.com") // 命名参数,跳过 age
默认参数 vs Java 重载

Java 中为了提供多种调用方式需要写多个重载方法。Kotlin 用默认参数一个函数搞定。给 Java 调用时加 @JvmOverloads 注解即可自动生成重载方法。

2. 扩展函数

扩展函数可以为已有类添加新方法,无需继承或使用装饰器模式:

// 为 String 添加 isEmail 方法
fun String.isEmail(): Boolean {
return Regex("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$").matches(this)
}

"test@example.com".isEmail() // true
"not-email".isEmail() // false

// 实战:为 View 添加便捷方法
fun View.visible() { visibility = View.VISIBLE }
fun View.gone() { visibility = View.GONE }
fun View.invisible() { visibility = View.INVISIBLE }

// 带泛型的扩展函数
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
扩展函数的本质

扩展函数是静态分发的,编译后变成静态方法:

// fun String.isEmail() = ...
// 编译后等价于:
public static boolean isEmail(String $this) { ... }

因此扩展函数不能被子类重写(没有多态),调用哪个版本取决于编译时类型而非运行时类型。

3. 高阶函数

高阶函数是以函数作为参数或返回值的函数:

// 函数类型参数
fun performOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}

performOperation(5, 3) { a, b -> a + b } // 8
performOperation(5, 3) { a, b -> a * b } // 15

// 返回函数
fun multiplier(factor: Int): (Int) -> Int {
return { number -> number * factor }
}
val triple = multiplier(3)
triple(5) // 15

4. 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 —— 最后一个参数是函数时可放在括号外
listOf(1, 2, 3).map { it * 2 } // [2, 4, 6]
listOf(1, 2, 3).filter { it > 1 } // [2, 3]

// 多行 Lambda,最后一个表达式是返回值
val transform = { x: Int ->
val doubled = x * 2
val result = doubled + 1
result // 返回值
}

5. 作用域函数

Kotlin 标准库提供 5 个作用域函数,是面试高频考点:

函数对象引用返回值主要场景
letitLambda 结果空安全调用、转换
runthisLambda 结果对象配置 + 计算结果
withthisLambda 结果对已有对象执行操作
applythis对象本身对象初始化配置
alsoit对象本身附加操作(日志、验证)
// let —— 空安全 + 转换
val length = str?.let {
println("Processing: $it")
it.length // 返回长度
}

// apply —— 对象初始化(Builder 风格)
val textView = TextView(context).apply {
text = "Hello"
textSize = 16f
setTextColor(Color.BLACK)
}

// also —— 附加操作(不改变链式调用)
fun getUser() = User("Alice").also {
Log.d("TAG", "Created user: ${it.name}")
}

// run —— 配置 + 返回结果
val result = service.run {
port = 8080
connect() // 返回连接结果
}

// with —— 对已有对象执行多个操作
with(canvas) {
drawColor(Color.WHITE)
drawCircle(100f, 100f, 50f, paint)
drawText("Hello", 200f, 200f, paint)
}

6. inline 函数

高阶函数每次调用会创建 Lambda 对象,inline 关键字可消除这个开销:

// inline —— Lambda 代码内联到调用处,消除对象创建开销
inline fun <T> measureTime(block: () -> T): T {
val start = System.nanoTime()
val result = block()
println("Time: ${System.nanoTime() - start}ns")
return result
}

// noinline —— 不内联某个参数(需要作为对象传递时)
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// notInlined 可以赋值给变量,inlined 不行
}

// crossinline —— 禁止 Lambda 中非局部返回
inline fun runOnUiThread(crossinline action: () -> Unit) {
Handler(Looper.getMainLooper()).post { action() }
}
为什么标准库的 map、filter 都是 inline 的?

集合操作频繁创建 Lambda 对象会造成性能损耗。inline 将 Lambda 代码直接内联到调用处,零额外对象分配,这就是 Kotlin "零成本抽象" 的体现。


常见面试问题

Q1: 扩展函数是静态分发还是动态分发?

答案

静态分发。扩展函数编译后是静态方法,调用哪个版本由编译时类型决定:

open class Animal
class Dog : Animal()

fun Animal.name() = "Animal"
fun Dog.name() = "Dog"

val animal: Animal = Dog()
println(animal.name()) // "Animal" —— 不是 "Dog"!

因为 animal 的编译时类型是 Animal,所以调用了 Animal.name()。如果需要多态行为,应该使用成员函数而非扩展函数。

Q2: let、apply、run、with、also 怎么选?

答案

选择流程:

  1. 需要返回对象本身?apply(配置)或 also(副作用)
  2. 需要返回 Lambda 结果?let(用 it)或 run/with(用 this)
  3. 空安全调用?let?.let {}
  4. 对象初始化?apply
  5. 日志/验证等副作用?also

Q3: inline 函数中的非局部返回是什么?

答案

inline Lambda 中的 return返回外层函数(非局部返回):

inline fun forEach(list: List<Int>, action: (Int) -> Unit) {
for (item in list) action(item)
}

fun findFirst(): Int {
forEach(listOf(1, 2, 3)) {
if (it == 2) return it // ⚠️ 直接返回 findFirst 函数!
}
return -1
}

如果不想允许非局部返回,用 crossinline 标记参数。

Q4: Kotlin 中如何实现函数柯里化?

答案

// 返回函数的函数
fun add(a: Int): (Int) -> Int = { b -> a + b }

val add5 = add(5)
println(add5(3)) // 8

// 通用柯里化扩展
fun <A, B, C> ((A, B) -> C).curried(): (A) -> (B) -> C =
{ a -> { b -> this(a, b) } }

val curriedSum = { a: Int, b: Int -> a + b }.curried()
curriedSum(1)(2) // 3

Q5: Kotlin 的函数类型在 JVM 上是如何表示的?

答案

Kotlin 函数类型编译后对应 FunctionN 接口:

  • () -> UnitFunction0<Unit>
  • (Int) -> StringFunction1<Int, String>
  • (Int, String) -> BooleanFunction2<Int, String, Boolean>

Lambda 表达式编译后是实现了对应 FunctionN 接口的匿名内部类inline 函数通过将 Lambda 代码内联到调用处,避免了这个匿名类的创建。

相关链接