函数

Kotlin 中函数是一等语言结构。函数可以定义在顶层、类中、对象中,也可以作为值传递。对 Java 开发者来说,最大的变化是:很多以前必须放进工具类或静态方法里的逻辑,在 Kotlin 中可以直接用顶层函数、扩展函数和高阶函数表达。

基本声明

fun sum(a: Int, b: Int): Int {
    return a + b
}

函数声明由几部分组成:

  • fun 关键字。
  • 函数名。
  • 参数列表。
  • 返回类型。
  • 函数体。

表达式函数体

当函数体只有一个表达式时,可以简写:

fun sum(a: Int, b: Int) = a + b

返回类型通常可以推断出来。公共 API 中,为了可读性和二进制兼容考虑,复杂函数建议显式写返回类型。

Unit

没有有意义返回值时,返回类型是 Unit

fun log(message: String): Unit {
    println(message)
}

通常省略:

fun log(message: String) {
    println(message)
}

Java 的 void 不是类型;Kotlin 的 Unit 是类型。这让 Kotlin 的泛型和函数类型更统一。

默认参数

fun connect(host: String, port: Int = 443, secure: Boolean = true) {
    println("$host:$port secure=$secure")
}

connect("example.com")
connect("example.com", 80, false)

Java 通常通过重载实现同样效果:

void connect(String host) {
    connect(host, 443, true);
}

Kotlin 默认参数能减少大量重载方法。不过如果函数要被 Java 调用,默认参数不会天然变成 Java 重载;需要时可以使用 @JvmOverloads

命名参数

connect(
    host = "example.com",
    secure = false,
    port = 80
)

命名参数可以提高可读性,尤其适合多个同类型参数:

fun resize(width: Int, height: Int)

resize(width = 1920, height = 1080)

对 Java 方法调用命名参数通常不可用,因为 Java 字节码不总是保留稳定的参数名契约。

可变参数:vararg

fun printAll(vararg messages: String) {
    for (message in messages) {
        println(message)
    }
}

printAll("A", "B", "C")

如果已有数组,需要展开传入:

val values = arrayOf("A", "B")
printAll(*values)

* 是展开运算符。Java 中数组可以直接传给 varargs;Kotlin 需要显式展开,是为了让调用意图更清楚。

局部函数

函数可以定义在函数内部:

fun saveUser(name: String, email: String) {
    fun requireNotBlank(value: String, field: String) {
        require(value.isNotBlank()) { "$field 不能为空" }
    }

    requireNotBlank(name, "name")
    requireNotBlank(email, "email")

    println("保存用户")
}

局部函数适合封装只在当前函数中使用的校验逻辑,避免污染类或文件级 API。

函数类型

函数可以作为值:

val operation: (Int, Int) -> Int = { a, b -> a + b }

println(operation(1, 2))

(Int, Int) -> Int 表示接收两个 Int,返回一个 Int 的函数。

高阶函数

接收函数作为参数,或返回函数的函数,叫高阶函数:

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

val result = calculate(10, 20) { x, y -> x + y }

Java 8 以后可以用函数式接口和 Lambda 做类似事情,但 Kotlin 的函数类型是语言内建概念,写法更直接。

尾随 Lambda

如果函数的最后一个参数是函数类型,调用时可以把 Lambda 放到括号外:

val names = listOf("Ada", "Grace", "Linus")

names.filter { it.length > 3 }
    .map { it.uppercase() }
    .forEach { println(it) }

这也是 Kotlin 集合 API 读起来像流水线的原因。

单表达式 Lambda 与 it

只有一个参数时,可以用隐式参数 it

val longNames = names.filter { it.length > 3 }

如果 Lambda 较长,建议显式命名参数:

val longNames = names.filter { name ->
    name.length > 3 && name.startsWith("G")
}

扩展函数预告

Kotlin 可以给已有类型“增加”函数:

fun String.firstCharOrNull(): Char? =
    if (isEmpty()) null else this[0]

println("Kotlin".firstCharOrNull())

扩展函数不会真的修改原类,也不会突破封装访问私有成员。它更像一种静态解析的语法增强,适合组织工具方法。

实践建议

  • 简单函数优先使用表达式函数体。
  • 公共 API 的复杂返回类型建议显式写出。
  • 默认参数可以减少重载,但 Java 调用方需要额外考虑。
  • Lambda 很长时不要过度使用 it
  • 工具方法可以优先考虑顶层函数或扩展函数,而不是创建 XxxUtils 类。

参考

  • 官方函数文档:https://kotlinlang.org/docs/functions.html