Edit Page

函数

最终更新: 2024/03/21

在 Kotlin 中, 你可以使用 fun 关键字声明你自己的函数.

fun hello() {
    return println("Hello, world!")
}

fun main() {
    hello()
    // 输出结果为 Hello, world!
}

在 Kotlin 中:

  • 函数参数写在小括号 () 之内.
  • 每个参数必须指定类型, 多个参数必须用逗号 , 隔开.
  • 返回值类型写在函数的小括号 () 之后, 用冒号 : 隔开.
  • 函数的 body 部写在大括号 {} 之内.
  • return 关键字用来退出函数, 或从函数返回某个值.

如果函数不返回任何有用的值, 那么可以省略返回值类型和 return 关键字. 关于这个问题, 详情请参见 没有返回值的函数.

在下面的示例中:

  • xy 是函数参数.
  • xy 类型为 Int.
  • 函数的返回值类型为 Int.
  • 函数被调用时返回 xy 的和.
fun sum(x: Int, y: Int): Int {
    return x + y
}

fun main() {
    println(sum(1, 2))
    // 输出结果为 3
}

在我们的 编码规约 中, 我们建议函数名称以小写字母开头, 并使用驼峰式大小写(Camel case), 不使用下划线.

命名参数

为了让代码更简洁, 调用函数时, 你不必指定参数名称. 但是, 指定参数名称可以让你的代码更易于阅读. 这种方式称为 命名参数(named argument). 如果你指定了参数名称, 那么可以用任意的顺序来写这些参数.

在下面的示例中, 使用了 字符串模板 ($) 来访问参数值, 并将它们转换为 String 类型, 然后拼接到一个字符串中, 用于打印输出.

fun printMessageWithPrefix(message: String, prefix: String) {
    println("[$prefix] $message")
}

fun main() {
    // 使用命名参数, 交换了参数的顺序
    printMessageWithPrefix(prefix = "Log", message = "Hello")
    // 输出结果为 [Log] Hello
}

默认的参数值

你可以为函数参数定义默认值. 调用你的函数时, 有默认值的参数可以省略. 要声明默认值, 请在参数类型之后使用赋值操作符 =:

fun printMessageWithPrefix(message: String, prefix: String = "Info") {
    println("[$prefix] $message")
}

fun main() {
    // 使用两个参数调用函数
    printMessageWithPrefix("Hello", "Log")
    // 输出结果为 [Log] Hello

    // 只使用 message 参数调用函数
    printMessageWithPrefix("Hello")
    // 输出结果为 [Info] Hello

    printMessageWithPrefix(prefix = "Log", message = "Hello")
    // 输出结果为 [Log] Hello
}

你可以跳过某个有默认值的参数, 而不是省略所有参数. 但是, 在第一个跳过的参数之后, 你必须对后续的所有参数指定名称.

没有返回值的函数

如果你的函数不返回任何有用的值, 那么它的返回值类型为 Unit. Unit 类型只有唯一的一个值 – Unit. 你不必在你的函数 body 部明确的声明返回值为 Unit. 因此你不必使用 return 关键字, 也不必声明返回值类型:

fun printMessage(message: String) {
    println(message)
    // `return Unit` 或 `return` 都是可选的
}

fun main() {
    printMessage("Hello")
    // 输出结果为 Hello
}

单一表达式函数

为了让代码更加简洁, 你可以使用单一表达式函数. 例如, sum() 函数可以写得更短一些:

fun sum(x: Int, y: Int): Int {
    return x + y
}

fun main() {
    println(sum(1, 2))
    // 输出结果为 3
}

你可以删除大括号 {}, 使用赋值操作符 = 来声明函数的 body 部. 由于 Kotlin 的类型推断能力, 你还可以省略返回值类型. 这样, sum() 函数就变成只有 1 行:

fun sum(x: Int, y: Int) = x + y

fun main() {
    println(sum(1, 2))
    // 输出结果为 3
}

只有在你的函数没有 body 部({}) 时, 才能够省略返回值类型. 否则你的函数的返回值类型将是 Unit.

函数的实际练习

习题 1

写一个名为 circleArea 的函数, 接受一个整数参数, 表示圆的半径, 输出圆的面积大小.

在这个习题中, 你会导入一个包, 以便通过 PI 来访问 pi 值. 关于包的导入, 更多详情请参见 包与导入.

import kotlin.math.PI

fun circleArea() {
    // 在这里编写你的代码
}
fun main() {
    println(circleArea(2))
}

参考答案

import kotlin.math.PI

fun circleArea(radius: Int): Double {
    return PI * radius * radius
}

fun main() {
    println(circleArea(2)) // 输出结果为 12.566370614359172
}

习题 2

将前一个习题中的 circleArea 函数重写为单一表达式函数.

import kotlin.math.PI

// 在这里编写你的代码

fun main() {
    println(circleArea(2))
}

参考答案

import kotlin.math.PI

fun circleArea(radius: Int): Double = PI * radius * radius

fun main() {
    println(circleArea(2)) // 输出结果为 12.566370614359172
}

习题 3

你有一个函数, 它接受一个时/分/秒单位给定的时间间隔, 然后翻译为秒单位. 大多数情况下, 你只需要传递 1 个或 2 个参数, 而其它参数为 0. 改进这个函数以及调用它的代码, 使用默认参数值和命名参数, 让代码更加易于阅读.

fun intervalInSeconds(hours: Int, minutes: Int, seconds: Int) =
    ((hours * 60) + minutes) * 60 + seconds

fun main() {
    println(intervalInSeconds(1, 20, 15))
    println(intervalInSeconds(0, 1, 25))
    println(intervalInSeconds(2, 0, 0))
    println(intervalInSeconds(0, 10, 0))
    println(intervalInSeconds(1, 0, 1))
}

参考答案

fun intervalInSeconds(hours: Int = 0, minutes: Int = 0, seconds: Int = 0) =
    ((hours * 60) + minutes) * 60 + seconds

fun main() {
    println(intervalInSeconds(1, 20, 15))
    println(intervalInSeconds(minutes = 1, seconds = 25))
    println(intervalInSeconds(hours = 2))
    println(intervalInSeconds(minutes = 10))
    println(intervalInSeconds(hours = 1, seconds = 1))
}

Lambda 表达式

Kotlin 允许你使用 Lambda 表达式, 为函数编写更加简洁的代码.

例如, 下面的 uppercaseString() 函数:

fun uppercaseString(string: String): String {
    return string.uppercase()
}
fun main() {
    println(uppercaseString("hello"))
    // 输出结果为 HELLO
}

可以写成一个 Lambda 表达式:

fun main() {
    println({ string: String -> string.uppercase() }("hello"))
    // 输出结果为 HELLO
}

Lambda 表达式初看起来可能难于理解, 所以我们将它分解成各个部分. Lambda 表达式写在大括号 {} 之内.

在 Lambda 表达式之内, 你会写以下内容:

  • 参数, 在 -> 之前.
  • 函数 body 部, 在 -> 之后.

在上面的示例中:

  • string 是函数参数.
  • string 类型为 String.
  • 函数返回对 string 调用 .uppercase() 函数的结果.

如果你声明没有参数的 Lambda 表达式, 那么不必使用 ->. 例如:

{ println("Log message") }

可以用很多方式使用 Lambda 表达式. 你可以:

赋值给变量

要将 Lambda 表达式赋值给一个变量, 请使用赋值操作符 =:

fun main() {
    val upperCaseString = { string: String -> string.uppercase() }
    println(upperCaseString("hello"))
    // 输出结果为 HELLO
}

传递给另一个函数

将 Lambda 表达式传递给另一个函数, 这个功能是很有用的, 一个很好的例子是对集合(Collection)使用 .filter() 函数:

fun main() {
    //sampleStart
    val numbers = listOf(1, -2, 3, -4, 5, -6)
    val positives = numbers.filter { x -> x > 0 }
    val negatives = numbers.filter { x -> x < 0 }
    println(positives)
    // 输出结果为 [1, 3, 5]
    println(negatives)
    // 输出结果为 [-2, -4, -6]
    //sampleEnd
}

.filter() 函数接受一个 Lambda 表达式, 作为判定条件:

  • { x -> x > 0 } 接受 List 中的每个元素, 只返回正数.
  • { x -> x < 0 } 接受 List 中的每个元素, 只返回负数.

如果一个 Lambda 表达式是函数的唯一参数, 你可以去掉函数的小括号 (). 这是 尾缀 Lambda 表达式(Trailing Lambda) 的一个例子, 我们会在本章末尾详细介绍.

另一个好的例子是, 使用 .map() 函数, 对集合中的元素进行变换:

fun main() {
    //sampleStart
    val numbers = listOf(1, -2, 3, -4, 5, -6)
    val doubled = numbers.map { x -> x * 2 }
    val tripled = numbers.map { x -> x * 3 }
    println(doubled)
    // 输出结果为 [2, -4, 6, -8, 10, -12]
    println(tripled)
    // 输出结果为 [3, -6, 9, -12, 15, -18]
    //sampleEnd
}

.map() 函数接受一个 Lambda 表达式, 作为变换函数:

  • { x -> x * 2 } 接受 List 中的每个元素, 返回这个元素乘以 2 的结果.
  • { x -> x * 3 } 接受 List 中的每个元素, 返回这个元素乘以 3 的结果.

函数类型

在从一个函数返回一个 Lambda 表达式之前, 你首先需要理解 函数类型.

你已经学习了基本类型, 但函数本身也有它的类型. Kotlin 的类型推断功能能够通过参数类型推断一个函数的类型. 但有的时候你需要明确指定函数类型. 编译器需要函数类型, 然后才能知道对这个函数允许什么, 不允许什么.

函数类型的语法包括:

  • 每个参数的类型, 写在小括号 () 之内, 以逗号 , 分隔.
  • 返回值类型, 写在 -> 之后.

例如: (String) -> String, 或 (Int, Int) -> Int.

如果为 upperCaseString() 定义一个函数类型, 那么 Lambda 表达式如下:

val upperCaseString: (String) -> String = { string -> string.uppercase() }

fun main() {
    println(upperCaseString("hello"))
    // 输出结果为 HELLO
}

如果你的 Lambda 表达式没有参数, 那么小括号 () 保留为空. 例如: () -> Unit

你必须声明参数类型和返回值类型, 要么写在 Lambda 表达式内, 要么声明为函数类型. 否则, 编译器无法知道你的 Lambda 表达式的类型.

例如, 下面的代码无法工作:

val upperCaseString = { str -> str.uppercase() }

从函数中返回

可以从函数中返回 Lambda 表达式. 为了让编译器知道返回的 Lambda 表达式 的类型, 你必须声明一个函数类型.

在下面的示例中, toSeconds() 函数返回的函数类型是 (Int) -> Int, 因为它总是返回一个 Lambda 表达式, 这个 Lambda 表达式接受一个 Int 类型的参数, 并返回一个 Int 值.

这个示例使用 when 表达式, 来确定在调用 toSeconds() 时返回哪个 Lambda 表达式:

fun toSeconds(time: String): (Int) -> Int = when (time) {
    "hour" -> { value -> value * 60 * 60 }
    "minute" -> { value -> value * 60 }
    "second" -> { value -> value }
    else -> { value -> value }
}

fun main() {
    val timesInMinutes = listOf(2, 10, 15, 1)
    val min2sec = toSeconds("minute")
    val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
    println("Total time is $totalTimeInSeconds secs")
    // 输出结果为 Total time is 1680 secs
}

单独调用

Lambda 表达式可以单独调用, 方法是在大括号 {} 之后添加小括号 (), 并在小括号中加上参数:

fun main() {
    //sampleStart
    println({ string: String -> string.uppercase() }("hello"))
    // 输出结果为 HELLO
    //sampleEnd
}

尾缀 Lambda 表达式(Trailing Lambda)

你已经看到, 如果一个 Lambda 表达式是函数的唯一参数, 你可以去掉函数的小括号 (). 如果一个 Lambda 表达式是函数的最后一个参数, 那么 Lambda 表达式可以写在函数的小括号 () 之外. 对这两种情况, 这样的语法称为 尾缀 Lambda 表达式(Trailing Lambda).

例如, .fold() 函数接受一个初始值, 以及一个操作:

fun main() {
    //sampleStart
    // 初始值为 0.
    // 操作是对初始值累加 List 中的每个元素.
    println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // 输出结果为 6

    // 或者, 也可以写成 尾缀 Lambda 表达式的形式
    println(listOf(1, 2, 3).fold(0) { x, item -> x + item })  // 输出结果为 6
    //sampleEnd
}

关于 Lambda 表达式, 更多详情请参见 Lambda 表达式与匿名函数(Anonymous Function).

本教程的下一章是学习 Kotlin 中的 .

Lambda 表达式的实际练习

习题 1

你有一个 Web Service 支持的动作列表, 所有请求的一个共通前缀, 某个资源的一个 ID. 要对资源 ID 5 请求 title 动作, 你需要创建下面的 URL: https://example.com/book-info/5/title. 使用一个 Lambda 表达式, 从动作列表创建对应的 URL 列表.

fun main() {
    val actions = listOf("title", "year", "author")
    val prefix = "https://example.com/book-info"
    val id = 5
    val urls = // 在这里编写你的代码
    println(urls)
}

参考答案

fun main() {
    val actions = listOf("title", "year", "author")
    val prefix = "https://example.com/book-info"
    val id = 5
    val urls = actions.map { action -> "$prefix/$id/$action" }
    println(urls)
}

习题 2

编写一个函数, 接受一个 Int 值和一个动作 (一个 () -> Unit 类型的函数), 然后重复执行这个动作指定的次数. 然后使用这个函数打印 “Hello” 5 次.

fun repeatN(n: Int, action: () -> Unit) {
    // 在这里编写你的代码
}

fun main() {
    // 在这里编写你的代码
}

参考答案

fun repeatN(n: Int, action: () -> Unit) {
    for (i in 1..n) {
        action()
    }
}

fun main() {
    repeatN(5) {
        println("Hello")
    }
}

下一步