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 的voidNothing表示函数永远不会正常结束(总是抛异常或无限循环)
函数参数
默认参数
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 函数的核心内容:
- 函数定义:基本语法、单表达式函数、返回类型
- 函数参数:默认参数、命名参数、可变参数
- infix 函数:中缀表示法调用
- 函数类型:语法、带接收者的函数类型、类型别名
- 高阶函数:函数作为参数和返回值
- Lambda 表达式:语法、it 参数、尾随 Lambda
- 匿名函数:与 Lambda 的区别、返回行为
- 闭包:访问外部变量
- 内联函数:性能优化、reified 类型参数
- 尾递归:避免栈溢出
- 扩展函数:为现有类添加功能
- 函数作用域:顶层函数、局部函数
Kotlin 的函数设计融合了函数式编程和面向对象编程的优点,既简洁又强大。掌握函数是编写高质量 Kotlin 代码的基础。
练习
- 编写一个计算两个数最大公约数的函数
- 使用默认参数和命名参数设计一个灵活的连接函数
- 实现一个高阶函数
compose,用于组合两个函数 - 使用
reified实现类型安全的 JSON 解析函数 - 使用尾递归计算斐波那契数列的第 n 项