# 函数 在 Kotlin 中, 你可以使用 `fun` 关键字声明你自己的函数. ```KOTLIN fun hello() { return println("Hello, world!") } fun main() { hello() // 输出结果为 Hello, world! } ``` 在 Kotlin 中: * 函数参数写在小括号 `()` 之内. * 每个参数必须指定类型, 多个参数必须用逗号 `,` 隔开. * 返回值类型写在函数的小括号 `()` 之后, 用冒号 `:` 隔开. * 函数的 body 部写在大括号 `{}` 之内. * `return` 关键字用来退出函数, 或从函数返回某个值. > **Note:** > 如果函数不返回任何有用的值, 那么可以省略返回值类型和 `return` 关键字. 关于这个问题, 详情请参见 [没有返回值的函数](#functions-without-return). 在下面的示例中: * `x` 和 `y` 是函数参数. * `x` 和 `y` 类型为 `Int`. * 函数的返回值类型为 `Int`. * 函数被调用时返回 `x` 和 `y` 的和. ```KOTLIN fun sum(x: Int, y: Int): Int { return x + y } fun main() { println(sum(1, 2)) // 输出结果为 3 } ``` > **Note:** > 在我们的 [编码规约](coding-conventions.html#function-names) 中, 我们建议函数名称以小写字母开头, 并使用驼峰式大小写(Camel case), 不使用下划线. ## 命名参数 为了让代码更简洁, 调用函数时, 你不必指定参数名称. 但是, 指定参数名称可以让你的代码更易于阅读. 这种方式称为 命名参数(named argument). 如果你指定了参数名称, 那么可以用任意的顺序来写这些参数. > **Tip:** > 在下面的示例中, 使用了 [字符串模板](strings.html#string-templates) (`$`) 来访问参数值, 并将它们转换为 `String` 类型, 然后拼接到一个字符串中, 用于打印输出. ```KOTLIN fun printMessageWithPrefix(message: String, prefix: String) { println("[$prefix] $message") } fun main() { // 使用命名参数, 交换了参数的顺序 printMessageWithPrefix(prefix = "Log", message = "Hello") // 输出结果为 [Log] Hello } ``` ## 默认的参数值 你可以为函数参数定义默认值. 调用你的函数时, 有默认值的参数可以省略. 要声明默认值, 请在参数类型之后使用赋值操作符 `=`: ```KOTLIN 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 } ``` > **Note:** > 你可以跳过某个有默认值的参数, 而不是省略所有参数. 但是, 在第一个跳过的参数之后, 你必须对后续的所有参数指定名称. ## 没有返回值的函数 如果你的函数不返回任何有用的值, 那么它的返回值类型为 `Unit`. `Unit` 类型只有唯一的一个值 – `Unit`. 你不必在你的函数 body 部明确的声明返回值为 `Unit`. 因此你不必使用 `return` 关键字, 也不必声明返回值类型: ```KOTLIN fun printMessage(message: String) { println(message) // `return Unit` 或 `return` 都是可选的 } fun main() { printMessage("Hello") // 输出结果为 Hello } ``` ## 单一表达式函数 为了让代码更加简洁, 你可以使用单一表达式函数. 例如, `sum()` 函数可以写得更短一些: ```KOTLIN fun sum(x: Int, y: Int): Int { return x + y } fun main() { println(sum(1, 2)) // 输出结果为 3 } ``` 你可以删除大括号 `{}`, 使用赋值操作符 `=` 来声明函数的 body 部. 当你使用赋值操作符 `=` 时, Kotlin 会使用类型推断, 因此你也可以省略返回值类型. 这样, `sum()` 函数就变成只有 1 行: ```KOTLIN fun sum(x: Int, y: Int) = x + y fun main() { println(sum(1, 2)) // 输出结果为 3 } ``` 但是, 如果你想让你的代码能够被其他开发者快速理解, 那么即使使用赋值操作符 `=`, 也还是明确定义返回值类型更好一些. > **Note:** > 如果你使用大括号 `{}` 来声明函数的 body 部, 那么必须声明返回类型, 否则返回值类型将是 `Unit`. ## 函数中的提前返回 (Early Return) 如果想要你的函数中的代码在某个点之后不再进行后续处理, 请使用 `return` 关键字. 这个示例使用 `if` 判断, 如果条件表达式为真, 就从一个函数中提前返回: ```KOTLIN // 注册的用户名列表 val registeredUsernames = mutableListOf("john_doe", "jane_smith") // 注册 EMail 列表 val registeredEmails = mutableListOf("john@example.com", "jane@example.com") fun registerUser(username: String, email: String): String { // 如果用户名已被使用, 则提前返回 if (username in registeredUsernames) { return "Username already taken. Please choose a different username." } // 如果 EMail 已被注册, 则提前返回 if (email in registeredEmails) { return "Email already registered. Please use a different email." } // 如果用户名和 EMail 都没有被使用, 则进行注册处理 registeredUsernames.add(username) registeredEmails.add(email) return "User registered successfully: $username" } fun main() { println(registerUser("john_doe", "newjohn@example.com")) // 输出结果为: Username already taken. Please choose a different username. println(registerUser("new_user", "newuser@example.com")) // 输出结果为: User registered successfully: new_user } ``` ## 函数的实际练习 ### 习题 1 写一个名为 `circleArea` 的函数, 接受一个整数参数, 表示圆的半径, 输出圆的面积大小. > **Tip:** > 在这个习题中, 你会导入一个包, 以便通过 `PI` 来访问 $π$ 值. 关于包的导入, 更多详情请参见 [包与导入](packages.html). 提示 : 圆面积的计算公式是 $πr^2$, 其中 $r$ 是半径. ```KOTLIN import kotlin.math.PI // 在这里编写你的代码 fun main() { println(circleArea(2)) } ``` ```KOTLIN import kotlin.math.PI fun circleArea(radius: Int): Double { return PI * radius * radius } fun main() { println(circleArea(2)) // 输出结果为 12.566370614359172 } ``` ### 习题 2 将前一个习题中的 `circleArea` 函数重写为单一表达式函数. ```KOTLIN import kotlin.math.PI // 在这里编写你的代码 fun main() { println(circleArea(2)) } ``` ```KOTLIN import kotlin.math.PI fun circleArea(radius: Int): Double = PI * radius * radius fun main() { println(circleArea(2)) // 输出结果为 12.566370614359172 } ``` ### 习题 3 你有一个函数, 它接受一个时/分/秒单位给定的时间间隔, 然后翻译为秒单位. 大多数情况下, 你只需要传递 1 个或 2 个参数, 而其它参数为 0. 改进这个函数以及调用它的代码, 使用默认参数值和命名参数, 让代码更加易于阅读. ```KOTLIN 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)) } ``` ```KOTLIN 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()` 函数: ```KOTLIN fun uppercaseString(text: String): String { return text.uppercase() } fun main() { println(uppercaseString("hello")) // 输出结果为 HELLO } ``` 可以写成一个 Lambda 表达式: ```KOTLIN fun main() { val upperCaseString = { text: String -> text.uppercase() } println(upperCaseString("hello")) // 输出结果为 HELLO } ``` Lambda 表达式初看起来可能难于理解, 所以我们将它分解成各个部分. Lambda 表达式写在大括号 `{}` 之内. 在 Lambda 表达式之内, 你会写以下内容: * 参数, 在 `->` 之前. * 函数 body 部, 在 `->` 之后. 在上面的示例中: * `text` 是函数参数. * `text` 类型为 `String`. * 函数返回对 `text` 调用 [.uppercase()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/uppercase.html) 函数的结果. * 整个 Lambda 表达式通过赋值操作符 `=` 赋值给变量 `upperCaseString`. * 像函数一样使用 `upperCaseString` 变量, 字符串 `"hello"` 作为参数, 就会调用 Lambda 表达式. * `println()` 函数打印输出结果. > **Note:** > 如果你声明没有参数的 Lambda 表达式, 那么不必使用 `->`. 例如: > > > > > ```KOTLIN > { println("Log message") } > ``` 可以用很多方式使用 Lambda 表达式. 你可以: * [将 Lambda 表达式用作另一个函数的参数](#pass-to-another-function) * [从一个函数返回 Lambda 表达式](#return-from-a-function) * [单独调用一个 Lambda 表达式](#invoke-separately) ### 传递给另一个函数 将 Lambda 表达式传递给另一个函数, 这个功能是很有用的, 一个很好的例子是对集合(Collection)使用 [.filter()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/filter.html) 函数: ```KOTLIN fun main() { //sampleStart val numbers = listOf(1, -2, 3, -4, 5, -6) val positives = numbers.filter ({ x -> x > 0 }) val isNegative = { x: Int -> x < 0 } val negatives = numbers.filter(isNegative) println(positives) // 输出结果为 [1, 3, 5] println(negatives) // 输出结果为 [-2, -4, -6] //sampleEnd } ``` `.filter()` 函数接受一个 Lambda 表达式作为判定条件, 并将它应用于列表的每个元素. 只有在判定条件返回 `true` 时, 元素才会保留: * `{ x -> x > 0 }`, 如果元素为正数, 则返回 `true`. * `{ x -> x < 0 }`, 如果元素为负数, 则返回 `true`. 这个示例演示了将 Lambda 表达式传递给函数的两种方式: * 对于正数, 示例直接在 `.filter()` 函数中添加 Lambda 表达式. * 对于负数, 示例将 Lambda 表达式赋值给 `isNegative` 变量. 然后将 `isNegative` 变量用作 `.filter()` 函数的参数. 这种情况下, 你必须在 Lambda 表达式中指定函数参数 (`x`) 的类型. > **Note:** > 如果一个 Lambda 表达式是函数的唯一参数, 你可以去掉函数的小括号 `()`: > > > > > ```KOTLIN > val positives = numbers.filter { x -> x > 0 } > ``` > > > > 这是 [尾缀 Lambda 表达式(Trailing Lambda)](#trailing-lambdas) 的一个例子, 我们会在本章末尾详细介绍. 另一个很好的例子是, 使用 [.map()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map.html) 函数, 对集合中的元素进行变换: ```KOTLIN fun main() { //sampleStart val numbers = listOf(1, -2, 3, -4, 5, -6) val doubled = numbers.map { x -> x * 2 } val isTripled = { x: Int -> x * 3 } val tripled = numbers.map(isTripled) 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 表达式如下: ```KOTLIN val upperCaseString: (String) -> String = { text -> text.uppercase() } fun main() { println(upperCaseString("hello")) // 输出结果为 HELLO } ``` 如果你的 Lambda 表达式没有参数, 那么小括号 `()` 保留为空. 例如: `() -> Unit` > **Note:** > 你必须声明参数类型和返回值类型, 要么写在 Lambda 表达式内, 要么声明为函数类型. 否则, 编译器无法知道你的 Lambda 表达式的类型. > > > > 例如, 下面的代码无法工作: > > > > `val upperCaseString = { str -> str.uppercase() }` ### 从函数中返回 可以从函数中返回 Lambda 表达式. 为了让编译器知道返回的 Lambda 表达式 的类型, 你必须声明一个函数类型. 在下面的示例中, `toSeconds()` 函数返回的函数类型是 `(Int) -> Int`, 因为它总是返回一个 Lambda 表达式, 这个 Lambda 表达式接受一个 `Int` 类型的参数, 并返回一个 `Int` 值. 这个示例使用 `when` 表达式, 来确定在调用 `toSeconds()` 时返回哪个 Lambda 表达式: ```KOTLIN 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 表达式可以单独调用, 方法是在大括号 `{}` 之后添加小括号 `()`, 并在小括号中加上参数: ```KOTLIN fun main() { //sampleStart println({ text: String -> text.uppercase() }("hello")) // 输出结果为 HELLO //sampleEnd } ``` ### 尾缀 Lambda 表达式(Trailing Lambda) 你已经看到, 如果一个 Lambda 表达式是函数的唯一参数, 你可以去掉函数的小括号 `()`. 如果一个 Lambda 表达式是函数的最后一个参数, 那么 Lambda 表达式可以写在函数的小括号 `()` 之外. 对这两种情况, 这样的语法称为 尾缀 Lambda 表达式(Trailing Lambda). 例如, [.fold()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/fold.html) 函数接受一个初始值, 以及一个操作: ```KOTLIN 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)](lambdas.html#lambda-expressions-and-anonymous-functions). 本教程的下一章是学习 Kotlin 中的 [类](kotlin-tour-classes.html). ## Lambda 表达式的实际练习 ### 习题 1 你有一个 Web Service 支持的动作列表, 所有请求的一个共通前缀, 某个资源的一个 ID. 要对资源 ID 5 请求 `title` 动作, 你需要创建下面的 URL: `https://example.com/book-info/5/title`. 使用一个 Lambda 表达式, 从动作列表创建对应的 URL 列表. ```KOTLIN fun main() { val actions = listOf("title", "year", "author") val prefix = "https://example.com/book-info" val id = 5 val urls = // 在这里编写你的代码 println(urls) } ``` ```KOTLIN 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 次. ```KOTLIN fun repeatN(n: Int, action: () -> Unit) { // 在这里编写你的代码 } fun main() { // 在这里编写你的代码 } ``` ```KOTLIN fun repeatN(n: Int, action: () -> Unit) { for (i in 1..n) { action() } } fun main() { repeatN(5) { println("Hello") } } ``` ## See also * [上一步](kotlin-tour-control-flow.html) * [下一步](kotlin-tour-classes.html)