函数
最终更新: 2024/03/21
在 Kotlin 中, 你可以使用 fun
关键字声明你自己的函数.
fun hello() {
return println("Hello, world!")
}
fun main() {
hello()
// 输出结果为 Hello, world!
}
在 Kotlin 中:
- 函数参数写在小括号
()
之内. - 每个参数必须指定类型, 多个参数必须用逗号
,
隔开. - 返回值类型写在函数的小括号
()
之后, 用冒号:
隔开. - 函数的 body 部写在大括号
{}
之内. return
关键字用来退出函数, 或从函数返回某个值.
如果函数不返回任何有用的值, 那么可以省略返回值类型和
return
关键字. 关于这个问题, 详情请参见 没有返回值的函数.
在下面的示例中:
x
和y
是函数参数.x
和y
类型为Int
.- 函数的返回值类型为
Int
. - 函数被调用时返回
x
和y
的和.
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")
}
}