3.2.5 函数

原文链接: https://kotlinlang.org/docs/kotlin-tour-functions.html

3.2.5 函数

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

1
2
3
4
5
6
7
8
fun hello() {
    return println("Hello, world!")
}

fun main() {
    hello()
    // Hello, world!
}

在 Kotlin 中:

  • 函数参数写在圆括号 () 中。
  • 每个参数都必须有类型,多个参数之间必须用逗号 , 分隔。
  • 返回类型写在函数圆括号 () 之后,用冒号 : 分隔。
  • 函数体写在花括号 {} 中。
  • return 关键字用于从函数中退出或返回某个值。

注意: 如果函数不返回任何有用的值,返回类型和 return 关键字都可以省略。更多内容请参阅没有返回值的函数。

在下面的示例中:

  • x 和 y 是函数参数。
  • x 和 y 的类型是 Int。
  • 函数的返回类型是 Int。
  • 调用该函数时,它返回 x 与 y 的和。
1
2
3
4
5
6
7
8
fun sum(x: Int, y: Int): Int {
    return x + y
}

fun main() {
    println(sum(1, 2))
    // 3
}

注意: 我们在编码规范中建议,函数名以小写字母开头,使用驼峰命名法,并且不含下划线。

具名参数

为了让代码更简洁,调用函数时你不必写出参数名。不过,写上参数名确实能让代码更易读。这称为使用具名参数。如果写了参数名,你还可以按任意顺序书写参数。

提示: 在下面的示例中,使用字符串模板($)访问参数值,把它们转换成 String 类型,再拼接成一个字符串用于打印。

1
2
3
4
5
6
7
8
9
fun printMessageWithPrefix(message: String, prefix: String) {
    println("[$prefix] $message")
}

fun main() {
    // Uses named arguments with swapped parameter order
    printMessageWithPrefix(prefix = "Log", message = "Hello")
    // [Log] Hello
}

参数默认值

你可以为函数参数定义默认值。调用函数时,任何带默认值的参数都可以省略。要声明默认值,请在类型后面使用赋值运算符 =:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fun printMessageWithPrefix(message: String, prefix: String = "Info") {
    println("[$prefix] $message")
}

fun main() {
    // Function called with both parameters
    printMessageWithPrefix("Hello", "Log")
    // [Log] Hello

    // Function called only with message parameter
    printMessageWithPrefix("Hello")
    // [Info] Hello

    printMessageWithPrefix(prefix = "Log", message = "Hello")
    // [Log] Hello
}

注意: 你可以跳过某些带默认值的参数,而不必把它们全部省略。不过,从第一个被跳过的参数之后,你必须为其余所有参数写出参数名。

没有返回值的函数

如果你的函数不返回有用的值,那么它的返回类型是 Unit。Unit 是只有一个值——Unit——的类型。你不必在函数体中显式声明返回 Unit。这意味着你不必使用 return 关键字,也不必声明返回类型:

1
2
3
4
5
6
7
8
9
fun printMessage(message: String) {
    println(message)
    // `return Unit` or `return` is optional
}

fun main() {
    printMessage("Hello")
    // Hello
}

单表达式函数

为了让代码更简洁,你可以使用单表达式函数。例如,sum() 函数可以缩短成:

1
2
3
4
5
6
7
8
fun sum(x: Int, y: Int): Int {
    return x + y
}

fun main() {
    println(sum(1, 2))
    // 3
}

你可以去掉花括号 {},改用赋值运算符 = 来声明函数体。使用赋值运算符 = 时,Kotlin 会进行类型推断,因此你还可以省略返回类型。这样 sum() 函数就变成了一行:

1
2
3
4
5
6
fun sum(x: Int, y: Int) = x + y

fun main() {
    println(sum(1, 2))
    // 3
}

不过,如果你希望其他开发者能快速理解你的代码,那么即使使用赋值运算符 =,也最好显式写出返回类型。

注意: 如果你用花括号 {} 声明函数体,就必须声明返回类型,除非它是 Unit 类型。

函数中的提前返回

要让函数中的代码在某个点之后不再继续处理,请使用 return 关键字。这个示例使用 if,在条件表达式为真时提前从函数返回:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// A list of registered usernames
val registeredUsernames = mutableListOf("john_doe", "jane_smith")

// A list of registered emails
val registeredEmails = mutableListOf("john@example.com", "jane@example.com")

fun registerUser(username: String, email: String): String {
    // Early return if the username is already taken
    if (username in registeredUsernames) {
        return "Username already taken. Please choose a different username."
    }

    // Early return if the email is already registered
    if (email in registeredEmails) {
        return "Email already registered. Please use a different email."
    }

    // Proceed with the registration if the username and email are not taken
    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 的函数,它接收以整数形式表示的圆的半径作为参数,并输出该圆的面积。

提示: 在这个练习中,你要导入一个包,以便通过 PI 访问 π 的值。关于导入包的更多信息,请参阅包与导入。

提示

计算圆面积的公式是 πr²,其中 r 是半径。

1
2
3
4
5
6
7
import kotlin.math.PI

// Write your code here

fun main() {
    println(circleArea(2))
}
1
2
3
4
5
6
7
8
9
import kotlin.math.PI

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

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

示例解答

习题 2

把上一题中的 circleArea 函数改写为单表达式函数。

1
2
3
4
5
6
7
import kotlin.math.PI

// Write your code here

fun main() {
    println(circleArea(2))
}
1
2
3
4
5
6
7
import kotlin.math.PI

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

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

示例解答

习题 3

你有一个函数,它把以小时、分钟和秒表示的时间间隔转换成秒。多数情况下你只需要传入一两个参数,其余参数都等于 0。请使用参数默认值和具名参数改进这个函数以及调用它的代码,让代码更易读。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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))
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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() 函数:

1
2
3
4
5
6
7
fun uppercaseString(text: String): String {
    return text.uppercase()
}
fun main() {
    println(uppercaseString("hello"))
    // HELLO
}

也可以写成 lambda 表达式:

1
2
3
4
5
fun main() {
    val upperCaseString = { text: String -> text.uppercase() }
    println(upperCaseString("hello"))
    // HELLO
}

lambda 表达式乍看之下可能不太好理解,所以我们来拆解一下。lambda 表达式写在花括号 {} 中。

在 lambda 表达式中,你要写:

  • 参数,后面跟一个 ->。
  • -> 之后的函数体。

在前面的示例中:

  • text 是一个函数参数。
  • text 的类型是 String。
  • 该函数返回在 text 上调用 .uppercase() 函数的结果。
  • 整个 lambda 表达式通过赋值运算符 = 赋给了 upperCaseString 变量。
  • 调用该 lambda 表达式时,把 upperCaseString 变量当作函数使用,并传入字符串 "hello" 作为参数。
  • println() 函数打印结果。

注意: 如果你声明的 lambda 没有参数,就不必使用 ->。例如:kotlin { println("Log message") }

lambda 表达式有多种用法。你可以:

作为参数传给另一个函数

把 lambda 表达式传给函数的一个很好的例子,是在集合上使用 .filter() 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun main() {
    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]
}

.filter() 函数接受一个 lambda 表达式作为判断条件,并把它应用到列表的每个元素上。只有当该条件返回 true 时,函数才会保留这个元素:

  • 如果元素为正数,{ x -> x > 0 } 返回 true。
  • 如果元素为负数,{ x -> x < 0 } 返回 true。

这个示例演示了把 lambda 表达式传给函数的两种方式:

  • 对于正数,示例把 lambda 表达式直接写在 .filter() 函数中。
  • 对于负数,示例把 lambda 表达式赋给 isNegative 变量,然后把 isNegative 变量作为函数参数传给 .filter() 函数。在这种情况下,你必须在 lambda 表达式中指定函数参数(x)的类型。

注意: 如果 lambda 表达式是唯一的函数参数,你可以去掉函数圆括号 ():kotlin val positives = numbers.filter { x -> x > 0 } 这是尾随 lambda 的一个例子,本章末尾会更详细地讨论它。

另一个很好的例子是用 .map() 函数转换集合中的项目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fun main() {
    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]
}

.map() 函数接受一个 lambda 表达式作为转换函数:

  • { x -> x * 2 } 取出列表中的每个元素,并返回该元素乘以 2 的结果。
  • { x -> x * 3 } 取出列表中的每个元素,并返回该元素乘以 3 的结果。

函数类型

在从函数中返回 lambda 表达式之前,你需要先理解函数类型。

你已经学过基本类型,但函数本身也有类型。Kotlin 的类型推断可以根据参数类型推断出函数的类型。但有时你需要显式指定函数类型。编译器需要函数类型,才能知道对这个函数来说什么是允许的、什么是不允许的。

函数类型的语法包含:

  • 每个参数的类型写在圆括号 () 中,并用逗号 , 分隔。
  • 返回类型写在 -> 之后。

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

下面是为 upperCaseString() 定义了函数类型之后,lambda 表达式的写法:

1
2
3
4
5
6
val upperCaseString: (String) -> String = { text -> text.uppercase() }

fun main() {
    println(upperCaseString("hello"))
    // HELLO
}

如果你的 lambda 表达式没有参数,那么圆括号 () 留空即可。例如:() -> Unit

注意: 你必须在 lambda 表达式中或在函数类型中声明参数类型和返回类型。否则编译器无法知道你的 lambda 表达式是什么类型。例如,下面的写法是不行的:val upperCaseString = { str -> str.uppercase() }

从函数中返回

lambda 表达式可以从函数中返回。为了让编译器理解返回的 lambda 表达式是什么类型,你必须声明函数类型。

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

这个示例使用 when 表达式来决定调用 toSeconds() 时返回哪个 lambda 表达式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 表达式也可以单独调用:在花括号 {} 之后加上圆括号 (),并把任何参数写在圆括号中:

1
2
3
4
fun main() {
    println({ text: String -> text.uppercase() }("hello"))
    // HELLO
}

尾随 lambda

如你所见,如果 lambda 表达式是唯一的函数参数,你可以去掉函数圆括号 ()。如果 lambda 表达式作为函数的最后一个参数传入,那么这个表达式可以写在函数圆括号 () 之外。这两种情况下的语法都称为尾随 lambda。

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

1
2
3
4
5
6
7
8
fun main() {
    // The initial value is zero.
    // The operation sums the initial value with every item in the list cumulatively.
    println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // 6

    // Alternatively, in the form of a trailing lambda
    println(listOf(1, 2, 3).fold(0) { x, item -> x + item })  // 6
}

关于 lambda 表达式的更多信息,请参阅 Lambda 表达式与匿名函数。

我们导览的下一步是了解 Kotlin 中的类。

Lambda 表达式练习

习题 1

你有一个 Web 服务支持的动作列表、所有请求共用的前缀,以及某个特定资源的 ID。要通过 ID 为 5 的资源请求动作 title,你需要构造下面这个 URL:https://example.com/book-info/5/title。请用 lambda 表达式从动作列表创建 URL 列表。

1
2
3
4
5
6
7
fun main() {
    val actions = listOf("title", "year", "author")
    val prefix = "https://example.com/book-info"
    val id = 5
    val urls = // Write your code here
    println(urls)
}
1
2
3
4
5
6
7
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 次。

1
2
3
4
5
6
7
fun repeatN(n: Int, action: () -> Unit) {
    // Write your code here
}

fun main() {
    // Write your code here
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun repeatN(n: Int, action: () -> Unit) {
    for (i in 1..n) {
        action()
    }
}

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

示例解答

另请参阅

上一步

下一步