跳到主要内容

Kotlin 函数

函数是组织代码的基本单元。Kotlin 的函数设计简洁而强大,支持默认参数、命名参数、可变参数、高阶函数、内联函数等丰富特性。理解函数是掌握 Kotlin 编程的关键。

函数定义

基本语法

Kotlin 使用 fun 关键字声明函数:

// 标准函数定义
fun greet(name: String): String {
return "Hello, $name!"
}

// 调用函数
fun main() {
val message = greet("Kotlin")
println(message) // Hello, Kotlin!
}

函数的各个组成部分:

// fun        - 函数声明关键字
// greet - 函数名称
// name: String - 参数(参数名: 参数类型)
// : String - 返回值类型
// { ... } - 函数体

单表达式函数

当函数体只包含一个表达式时,可以简化语法:

// 完整写法
fun double(x: Int): Int {
return x * 2
}

// 单表达式写法(省略大括号和 return)
fun double2(x: Int): Int = x * 2

// 进一步简化:类型推断
fun double3(x: Int) = x * 2

fun main() {
println(double(5)) // 10
println(double2(5)) // 10
println(double3(5)) // 10
}

理解要点:单表达式函数省略了花括号和 return 关键字,编译器会自动推断返回类型。这种方式让简单函数更加简洁。

返回类型

Kotlin 中函数的返回类型有几种情况:

// 显式声明返回类型
fun add(a: Int, b: Int): Int {
return a + b
}

// Unit 返回类型(无返回值)
fun printHello(): Unit {
println("Hello")
}

// Unit 可以省略
fun printHello2() {
println("Hello")
}

// Nothing 类型:函数永不正常返回
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}

// 无限循环也返回 Nothing
fun infiniteLoop(): Nothing {
while (true) {
// 永不返回
}
}

Unit vs Nothing 的区别

  • Unit 表示函数执行完毕但不返回有意义的值,类似 Java 的 void
  • Nothing 表示函数永远不会正常结束(总是抛异常或无限循环)

函数参数

默认参数

Kotlin 支持为函数参数设置默认值,这可以减少函数重载的数量:

fun greet(name: String, greeting: String = "Hello"): String {
return "$greeting, $name!"
}

fun main() {
println(greet("Kotlin")) // Hello, Kotlin!
println(greet("Kotlin", "Hi")) // Hi, Kotlin!
println(greet("World", "Welcome")) // Welcome, World!
}

默认参数的优势在于可以灵活地调用函数,无需为每种组合都定义一个重载版本。这在 Java 中通常需要多个重载方法来实现,而 Kotlin 只需要一个函数定义。

命名参数

调用函数时可以指定参数名称,这在参数较多或使用默认参数时特别有用:

fun createUser(
name: String,
age: Int,
email: String = "[email protected]",
active: Boolean = true
) {
println("Name: $name, Age: $age, Email: $email, Active: $active")
}

fun main() {
// 位置参数:按顺序传递
createUser("Tom", 25)

// 命名参数:顺序可以任意
createUser(age = 25, name = "Alice")

// 混合使用:位置参数在前,命名参数在后
createUser("Bob", email = "[email protected]", age = 30)

// 跳过中间的默认参数
createUser("Charlie", 35, active = false)
}

使用场景

  • 函数有多个参数时,命名参数让代码更易读
  • 只想修改某些默认参数时,命名参数避免传递所有参数

可变参数

使用 vararg 关键字声明可变数量的参数:

fun sum(vararg numbers: Int): Int {
return numbers.sum()
}

fun main() {
println(sum(1, 2, 3)) // 6
println(sum(1, 2, 3, 4, 5)) // 15
println(sum()) // 0

// 传递数组:使用展开操作符 *
val arr = intArrayOf(1, 2, 3)
println(sum(*arr)) // 6

// 展开并添加更多元素
println(sum(*arr, 4, 5)) // 15
}

理解可变参数

  • vararg 参数在函数内部会被视为数组
  • 一个函数最多只能有一个 vararg 参数
  • 如果 vararg 不是最后一个参数,后续参数必须使用命名参数
// vararg 不在最后时,后续参数需要命名
fun printAll(prefix: String, vararg items: String, suffix: String) {
print(prefix)
items.forEach { print("$it ") }
println(suffix)
}

fun main() {
printAll("Start: ", "a", "b", "c", suffix = " :End")
// Start: a b c :End
}

尾随逗号

Kotlin 支持在参数列表最后添加逗号,这在多行参数时便于维护:

fun createUser(
name: String,
age: Int,
email: String, // 尾随逗号
) {
// ...
}

fun main() {
createUser(
name = "Tom",
age = 25,
email = "[email protected]", // 尾随逗号
)
}

infix 函数

使用 infix 关键字标记的函数可以使用中缀表示法调用,让代码更加自然:

// 定义 infix 函数
infix fun Int.times(str: String) = str.repeat(this)

infix fun String.contains(substring: String) = this.contains(substring)

class Person(val name: String) {
infix fun likes(other: Person) = "$name likes ${other.name}"
}

fun main() {
// 中缀调用
println(2 times "Hello") // HelloHello

// 等价于普通调用
println(2.times("Hello")) // HelloHello

// 实际应用:范围操作
val range = 1 until 10 // until 就是 infix 函数

// 实际应用:Map 创建
val map = mapOf("name" to "Tom") // to 就是 infix 函数

// 自定义类型的中缀函数
val alice = Person("Alice")
val bob = Person("Bob")
println(alice likes bob) // Alice likes Bob
}

infix 函数的要求

  • 必须是成员函数或扩展函数
  • 必须只有一个参数
  • 参数不能是可变参数且不能有默认值

函数类型

Kotlin 是一门静态类型语言,函数也有对应的类型。函数类型用于声明函数参数、返回值或变量。

函数类型语法

fun main() {
// 基本语法:(参数类型) -> 返回类型

// 无参数,无返回值
val sayHello: () -> Unit = { println("Hello") }

// 无参数,有返回值
val getNumber: () -> Int = { 42 }

// 有参数,有返回值
val add: (Int, Int) -> Int = { a, b -> a + b }

// 带参数名的函数类型(文档用途)
val multiply: (x: Int, y: Int) -> Int = { a, b -> a * b }

// 可空函数类型
var nullableFunc: ((Int) -> Int)? = null
nullableFunc = { it * 2 }

println(sayHello()) // Hello
println(getNumber()) // 42
println(add(1, 2)) // 3
println(multiply(3, 4)) // 12
}

带接收者的函数类型

函数类型可以指定一个接收者类型,这使得函数可以像扩展函数一样调用:

fun main() {
// 普通函数类型
val concat: (String, String) -> String = { a, b -> a + b }

// 带接收者的函数类型
// 在函数体内,this 指向接收者
val buildString: String.(Int) -> String = {
this.repeat(it)
}

// 调用方式一:接收者.函数(参数)
println("ab".buildString(3)) // ababab

// 调用方式二:函数(接收者, 参数)
println(buildString("cd", 2)) // cdcd

// 两种类型可以互换
val normalFunc: (String, Int) -> String = buildString
val receiverFunc: String.(Int) -> String = concat
}

带接收者的函数类型在 DSL(领域特定语言)构建中非常重要:

class StringBuilder {
private val content = StringBuilder()

fun append(str: String) {
content.append(str)
}

fun build(): String = content.toString()
}

// 带接收者的 lambda
fun buildString(block: StringBuilder.() -> Unit): String {
val builder = StringBuilder()
builder.block() // 在 builder 上执行 block
return builder.build()
}

fun main() {
val result = buildString {
append("Hello")
append(" ")
append("World")
}
println(result) // Hello World
}

函数类型的实例化

获取函数类型实例的几种方式:

fun isOdd(x: Int) = x % 2 != 0

fun main() {
// 方式一:Lambda 表达式
val add1: (Int, Int) -> Int = { a, b -> a + b }

// 方式二:匿名函数
val add2: (Int, Int) -> Int = fun(a: Int, b: Int): Int {
return a + b
}

// 方式三:函数引用
val isOddRef: (Int) -> Boolean = ::isOdd

// 方式四:成员函数引用
val strLength: (String) -> Int = String::length

// 方式五:构造函数引用
val createList: () -> List<Int> = ::listOf

// 方式六:绑定函数引用
val str = "Hello"
val lengthOf: () -> Int = str::length
println(lengthOf()) // 5
}

类型别名

使用 typealias 为函数类型创建别名,提高代码可读性:

// 定义函数类型别名
typealias ClickHandler = (Int, Int) -> Unit
typealias Predicate<T> = (T) -> Boolean
typealias Transformer<T, R> = (T) -> R

fun main() {
val handler: ClickHandler = { x, y -> println("Clicked at ($x, $y)") }
handler(100, 200)

val isEven: Predicate<Int> = { it % 2 == 0 }
println(isEven(4)) // true

val double: Transformer<Int, Int> = { it * 2 }
println(double(5)) // 10
}

高阶函数

高阶函数是以函数作为参数或返回值的函数。这是 Kotlin 函数式编程的核心特性。

函数作为参数

// 定义接受函数的参数
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}

fun main() {
// 传入 Lambda
val sum = calculate(10, 5) { a, b -> a + b }
println(sum) // 15

// 传入函数引用
fun multiply(a: Int, b: Int) = a * b
val product = calculate(10, 5, ::multiply)
println(product) // 50

// 多行 Lambda
val result = calculate(10, 5) { a, b ->
println("计算: $a$b")
val temp = a * b
temp + a - b // 最后一行是返回值
}
println(result) // 55
}

函数作为返回值

// 返回函数
fun multiplier(factor: Int): (Int) -> Int {
return { number -> number * factor }
}

// 更简洁的写法
fun multiplierSimple(factor: Int): (Int) -> Int = { it * factor }

fun main() {
val double = multiplier(2)
val triple = multiplier(3)

println(double(5)) // 10
println(triple(5)) // 15

// 直接调用
println(multiplier(4)(5)) // 20

// 实际应用:创建配置好的函数
fun createValidator(min: Int, max: Int): (Int) -> Boolean {
return { value -> value in min..max }
}

val validateAge = createValidator(0, 150)
println(validateAge(25)) // true
println(validateAge(200)) // false
}

经典高阶函数示例

fun main() {
val numbers = listOf(1, 2, 3, 4, 5)

// fold:累积操作
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum) // 15

// 自定义高阶函数
fun <T, R> List<T>.customMap(transform: (T) -> R): List<R> {
val result = mutableListOf<R>()
for (item in this) {
result.add(transform(item))
}
return result
}

val doubled = numbers.customMap { it * 2 }
println(doubled) // [2, 4, 6, 8, 10]

// 函数组合
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}

val addOne: (Int) -> Int = { it + 1 }
val double: (Int) -> Int = { it * 2 }
val addOneThenDouble = compose(double, addOne)

println(addOneThenDouble(5)) // 12 ((5 + 1) * 2)
}

Lambda 表达式

Lambda 表达式是匿名函数的一种简洁写法,是 Kotlin 函数式编程的核心语法。

Lambda 语法

fun main() {
// 完整语法:{ 参数列表 -> 函数体 }
val sum1 = { a: Int, b: Int -> a + b }

// 指定类型
val sum2: (Int, Int) -> Int = { a, b -> a + b }

// 多行 Lambda:最后一行是返回值
val process = { x: Int ->
println("Processing $x")
val doubled = x * 2
doubled + 1 // 这是返回值
}

println(sum1(1, 2)) // 3
println(process(5)) // 11
}

it 隐式参数

当 Lambda 只有一个参数时,可以省略参数声明,使用 it 作为隐式参数名:

fun main() {
val numbers = listOf(1, 2, 3, 4, 5)

// 完整写法
numbers.filter { x -> x > 3 }

// it 简写
numbers.filter { it > 3 }

// 更多 it 使用示例
numbers.map { it * 2 } // [2, 4, 6, 8, 10]
numbers.forEach { println(it) } // 打印每个元素
numbers.any { it > 3 } // true
numbers.all { it > 0 } // true
numbers.find { it > 3 } // 4
}

注意事项:当 Lambda 嵌套时,显式命名参数可以提高代码可读性:

fun main() {
val nested = listOf(listOf(1, 2), listOf(3, 4))

// 不推荐:it 嵌套难以理解
nested.forEach { it.forEach { println(it) } }

// 推荐:显式命名
nested.forEach { innerList ->
innerList.forEach { element ->
println(element)
}
}
}

下划线忽略未使用参数

当 Lambda 参数不使用时,可以用下划线 _ 代替:

fun main() {
val map = mapOf("a" to 1, "b" to 2)

// 不需要 key 时
map.forEach { (_, value) ->
println(value)
}

// 只需要索引时
listOf("a", "b", "c").forEachIndexed { index, _ ->
println("Index: $index")
}
}

尾随 Lambda

如果函数的最后一个参数是函数类型,Lambda 可以放在括号外面:

fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}

fun main() {
// 传统写法
operate(1, 2, { a, b -> a + b })

// 尾随 Lambda 写法
operate(1, 2) { a, b -> a + b }

// 如果 Lambda 是唯一参数,可以省略括号
val numbers = listOf(1, 2, 3)
numbers.filter { it > 2 } // 省略括号

// 多行 Lambda
val result = operate(10, 5) { a, b ->
println("计算: $a op $b")
a * b + a - b
}
}

这种语法在 DSL 和集合操作中非常常见,让代码更加清晰。

匿名函数

匿名函数与 Lambda 类似,但可以显式指定返回类型:

fun main() {
// Lambda 表达式
val lambda = { x: Int -> x * 2 }

// 匿名函数(表达式体)
val anon1 = fun(x: Int): Int = x * 2

// 匿名函数(代码块体)
val anon2 = fun(x: Int): Int {
println("Processing $x")
return x * 2
}

println(lambda(5)) // 10
println(anon1(5)) // 10
println(anon2(5)) // Processing 5 \n 10
}

Lambda vs 匿名函数的返回行为

这是两者最重要的区别:

fun main() {
// Lambda 中的 return 返回外层函数
fun testLambda() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // 返回 testLambda 函数
print("$it ")
}
println("这行不会执行")
}
testLambda() // 输出: 1 2
println()

// 匿名函数中的 return 返回匿名函数本身
fun testAnonymous() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return // 只返回匿名函数,继续 forEach
print("$value ")
})
println("\n这行会执行")
}
testAnonymous() // 输出: 1 2 4 5 \n 这行会执行
}

闭包

Lambda 和匿名函数可以访问并修改其外部作用域的变量:

fun main() {
// 读取外部变量
var factor = 2
val multiply: (Int) -> Int = { x -> x * factor }
println(multiply(5)) // 10

// 闭包捕获变量引用,不是值
factor = 3
println(multiply(5)) // 15

// 在 Lambda 中修改外部变量
var sum = 0
listOf(1, 2, 3, 4, 5).forEach { sum += it }
println(sum) // 15

// 计数器工厂:返回的函数共享同一个计数器
fun createCounter(): () -> Int {
var count = 0
return { ++count }
}

val counter = createCounter()
println(counter()) // 1
println(counter()) // 2
println(counter()) // 3

val anotherCounter = createCounter()
println(anotherCounter()) // 1(新的计数器)
}

闭包的工作原理:Lambda 捕获的是变量的引用而非值。这意味着外部变量的修改会影响 Lambda 的行为,反之亦然。

内联函数

使用 inline 关键字可以消除高阶函数的运行时开销。

为什么需要内联

高阶函数会为每个 Lambda 创建对象,产生内存和性能开销。内联函数在编译时将函数代码"复制"到调用处:

// 不使用内联:每次调用创建函数对象
fun <T> measureTimeNotInlined(block: () -> T): T {
val start = System.currentTimeMillis()
val result = block()
val end = System.currentTimeMillis()
println("Time: ${end - start}ms")
return result
}

// 使用内联:代码被直接插入调用处
inline fun <T> measureTime(block: () -> T): T {
val start = System.currentTimeMillis()
val result = block()
val end = System.currentTimeMillis()
println("Time: ${end - start}ms")
return result
}

fun main() {
val result = measureTime {
Thread.sleep(100)
42
}
println("Result: $result")
}

编译后的代码等价于:

fun main() {
val start = System.currentTimeMillis()
Thread.sleep(100)
val result = 42
val end = System.currentTimeMillis()
println("Time: ${end - start}ms")
println("Result: $result")
}

noinline

当不希望某个 Lambda 参数被内联时,使用 noinline

// noinline 阻止特定参数被内联
inline fun example(
inlined: () -> Unit,
noinline notInlined: () -> Unit
) {
inlined() // 被内联
notInlined() // 不被内联

// noinline 的函数可以被存储或传递
val stored = notInlined
stored()
}

crossinline

当内联 Lambda 不能包含非局部返回时,使用 crossinline

// crossinline 禁止非局部返回
inline fun runInThread(crossinline action: () -> Unit) {
Thread { action() }.start()
}

fun main() {
runInThread {
// 不能使用 return,因为是在另一个线程执行
// return // 编译错误

println("在后台线程执行")
}
}

非局部返回

内联函数中的 Lambda 可以使用 return 直接返回外层函数:

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

fun main() {
val numbers = listOf(1, 2, 3, 4, 5)

forEach(numbers) {
if (it == 3) return // 非局部返回:直接退出 main 函数
print("$it ")
}
println("这行不会执行")
}
// 输出: 1 2

reified 类型参数

由于 JVM 的类型擦除,泛型类型在运行时不可用。使用 inline 配合 reified 可以保留类型信息:

// 普通泛型函数无法检查类型
fun <T> isType1(value: Any): Boolean {
// return value is T // 编译错误!
return false
}

// reified 使类型参数在运行时可访问
inline fun <reified T> isType(value: Any): Boolean {
return value is T
}

inline fun <reified T> getClassName(): String {
return T::class.simpleName ?: "Unknown"
}

fun main() {
println(isType<String>("Hello")) // true
println(isType<Int>("Hello")) // false
println(getClassName<String>()) // String
println(getClassName<List<*>>()) // List
}

reified 的实际应用

// 过滤特定类型的元素
inline fun <reified T> List<*>.filterIsInstance(): List<T> {
return filter { it is T } as List<T>
}

// JSON 解析示例
inline fun <reified T> parseJson(json: String): T {
// 实际实现需要 JSON 库
throw NotImplementedError("需要 JSON 库支持")
}

fun main() {
val mixed = listOf(1, "two", 3, "four", 5)
val strings = mixed.filterIsInstance<String>()
println(strings) // [two, four]

val ints = mixed.filterIsInstance<Int>()
println(ints) // [1, 3, 5]
}

尾递归函数

使用 tailrec 关键字优化递归函数,避免栈溢出:

// 普通递归 - 可能栈溢出
fun factorial(n: Int): Long {
return if (n <= 1) 1 else n * factorial(n - 1)
}

// 尾递归 - 编译器优化为循环
tailrec fun factorialTailrec(n: Int, acc: Long = 1): Long {
return if (n <= 1) acc else factorialTailrec(n - 1, n * acc)
}

// 斐波那契数列
tailrec fun fibonacci(n: Int, a: Long = 0, b: Long = 1): Long {
return if (n == 0) a else fibonacci(n - 1, b, a + b)
}

fun main() {
println(factorialTailrec(10)) // 3628800
println(factorialTailrec(20)) // 2432902008176640000

println(fibonacci(10)) // 55
println(fibonacci(50)) // 12586269025

// 普通递归在大数时会栈溢出
// factorial(100000) // StackOverflowError

// 尾递归可以处理大数
println(factorialTailrec(1000)) // 正常执行
}

尾递归的要求

  • 递归调用必须是函数的最后一个操作
  • 不能在 try/catch/finally 块中使用
  • 只能在 JVM 和 Native 平台使用

扩展函数

扩展函数允许为现有类添加新方法:

// 为 String 添加扩展函数
fun String.addExclamation(): String = "$this!"

// 为 Int 添加扩展函数
fun Int.isEven(): Boolean = this % 2 == 0

// 泛型扩展函数
fun <T> T.print(): T {
println(this)
return this
}

fun main() {
println("Hello".addExclamation()) // Hello!
println(4.isEven()) // true
42.print() // 42
}

函数作用域

顶层函数

Kotlin 支持在文件顶层定义函数,无需放在类中:

// File: Utils.kt
package com.example.util

fun isValidEmail(email: String): Boolean {
return email.contains("@")
}

// 在其他文件中使用
// import com.example.util.isValidEmail
// isValidEmail("[email protected]")

局部函数

在函数内部定义的函数:

fun outer() {
println("Outer function")

// 局部函数:只能在外层函数内调用
fun inner() {
println("Inner function")
}

inner()

// 局部函数可以访问外层函数的变量
val message = "Hello"
fun printMessage() {
println(message) // 访问外层变量
}
printMessage()
}

fun main() {
outer()
// inner() // 编译错误!无法访问局部函数
}

局部函数适用于需要复用但不想暴露给外部的逻辑。

最佳实践

1. 优先使用 val 和单表达式函数

// 推荐:简洁明了
fun double(x: Int) = x * 2

// 避免:不必要的冗长
fun double2(x: Int): Int {
return x * 2
}

2. 使用命名参数提高可读性

// 推荐:参数含义清晰
createUser(name = "Tom", age = 25, active = true)

// 避免:布尔值含义不明
createUser("Tom", 25, true) // true 是什么意思?

3. 合理使用默认参数

// 推荐:合理的默认值
fun connect(
host: String,
port: Int = 8080,
timeout: Int = 5000
) { /* ... */ }

// 可以简化调用
connect("localhost")
connect("localhost", port = 3000)

4. 使用内联函数优化性能

// 对于频繁调用的高阶函数,使用 inline
inline fun <T> measureTime(block: () -> T): T { /* ... */ }

5. 使用函数类型别名提高可读性

typealias ClickHandler = (Int, Int) -> Unit
typealias Validator<T> = (T) -> Boolean

小结

本章我们深入学习了 Kotlin 函数的核心内容:

  1. 函数定义:基本语法、单表达式函数、返回类型
  2. 函数参数:默认参数、命名参数、可变参数
  3. infix 函数:中缀表示法调用
  4. 函数类型:语法、带接收者的函数类型、类型别名
  5. 高阶函数:函数作为参数和返回值
  6. Lambda 表达式:语法、it 参数、尾随 Lambda
  7. 匿名函数:与 Lambda 的区别、返回行为
  8. 闭包:访问外部变量
  9. 内联函数:性能优化、reified 类型参数
  10. 尾递归:避免栈溢出
  11. 扩展函数:为现有类添加功能
  12. 函数作用域:顶层函数、局部函数

Kotlin 的函数设计融合了函数式编程和面向对象编程的优点,既简洁又强大。掌握函数是编写高质量 Kotlin 代码的基础。

练习

  1. 编写一个计算两个数最大公约数的函数
  2. 使用默认参数和命名参数设计一个灵活的连接函数
  3. 实现一个高阶函数 compose,用于组合两个函数
  4. 使用 reified 实现类型安全的 JSON 解析函数
  5. 使用尾递归计算斐波那契数列的第 n 项

参考资料