函数
你可以使用 fun
关键字在 Kotlin 中声明自己的函数。
fun hello() {
return println("Hello, world!")
}
fun main() {
hello()
// Hello, world!
}
在 Kotlin 中:
- 函数形参写在圆括号
()
内。 - 每个形参都必须有类型,多个形参之间必须用逗号
,
分隔。 - 返回类型写在函数圆括号
()
之后,并用冒号:
分隔。 - 函数体写在花括号
{}
内。 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
}
我们在编码约定中建议,函数名称以小写字母开头,并使用驼峰式命名法,不带下划线。
具名实参
为了简洁的代码,调用函数时不必包含形参名称。但是,包含形参名称确实可以使代码更易读。这称为使用具名实参。如果包含形参名称,则可以按任意顺序书写形参。
) 用于访问形参值,将其转换为
String
类型,然后将它们连接成一个字符串以供打印。
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
}
默认形参值
你可以为函数形参定义默认值。任何具有默认值的形参在调用函数时都可以省略。要声明默认值,请在类型后使用赋值操作符 =
:
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
关键字或声明返回类型:
fun printMessage(message: String) {
println(message)
// `return Unit` or `return` is optional
}
fun main() {
printMessage("Hello")
// Hello
}
单表达式函数
为了使代码更简洁,你可以使用单表达式函数。例如,sum()
函数可以缩短为:
fun sum(x: Int, y: Int): Int {
return x + y
}
fun main() {
println(sum(1, 2))
// 3
}
你可以移除花括号 {}
,并使用赋值操作符 =
声明函数体。当你使用赋值操作符 =
时,Kotlin 会使用类型推断,因此你也可以省略返回类型。这样,sum()
函数就变成了一行:
fun sum(x: Int, y: Int) = x + y
fun main() {
println(sum(1, 2))
// 3
}
然而,如果你希望其他开发者能快速理解你的代码,即使使用赋值操作符 =
,也最好显式定义返回类型。
如果你使用花括号
{}
声明函数体,则必须声明返回类型,除非它是Unit
类型。
函数中的提前返回
要阻止函数中的代码在某一点之后继续处理,请使用 return
关键字。此示例使用 if
在条件表达式为 true
时提前从函数返回:
// A list of registered usernames
val registeredUsernames = mutableListOf("john_doe", "jane_smith")
// A list of registered emails
val registeredEmails = mutableListOf("[email protected]", "[email protected]")
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", "[email protected]"))
// Username already taken. Please choose a different username.
println(registerUser("new_user", "[email protected]"))
// User registered successfully: new_user
}
函数练习
练习 1
编写一个名为 circleArea
的函数,该函数接收以整数格式表示的圆半径作为形参,并输出该圆的面积。
TIP
在此练习中,你导入了一个包,以便可以通过 PI
访问 pi 的值。有关导入包的更多信息,请参阅包和导入。
import kotlin.math.PI
// Write your code here
fun main() {
println(circleArea(2))
}
Example solution
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
// Write your code here
fun main() {
println(circleArea(2))
}
Example solution
import kotlin.math.PI
fun circleArea(radius: Int): Double = PI * radius * radius
fun main() {
println(circleArea(2)) // 12.566370614359172
}
练习 3
你有一个函数,可以将以小时、分钟和秒给定的时间间隔转换为秒。在大多数情况下,你只需传递一到两个函数形参,而其余形参则等于 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))
}
Example solution
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 表达式传递给函数的好例子是,在集合上使用 .filter()
函数:
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 表达式传递给函数的两种方式:
- 对于正数,示例直接在
.filter()
函数中添加 lambda 表达式。 - 对于负数,示例将 lambda 表达式赋值给
isNegative
变量。然后,isNegative
变量作为函数形参在.filter()
函数中使用。在这种情况下,你必须在 lambda 表达式中指定函数形参 (x
) 的类型。
如果 lambda 表达式是唯一的函数形参,你可以省略函数圆括号
()
:kotlinval positives = numbers.filter { x -> x > 0 }
这是尾随 lambda 表达式的一个示例,本章末尾将更详细地讨论它。
另一个好例子是使用 .map()
函数来转换集合中的项:
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 表达式看起来像这样:
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
,因为它总是返回一个 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() {
println({ text: String -> text.uppercase() }("hello"))
// HELLO
}
尾随 lambda 表达式
如你所见,如果 lambda 表达式是唯一的函数形参,你可以省略函数圆括号 ()
。如果 lambda 表达式作为函数的最后一个形参传递,则该表达式可以写在函数圆括号 ()
之外。在这两种情况下,此语法都称为尾随 lambda 表达式。
例如,.fold()
函数接受一个初始值和一个操作:
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 列表。
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)
}
Example solution
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
的函数),然后将该动作重复给定的次数。然后使用此函数打印 5 次“Hello”。
fun repeatN(n: Int, action: () -> Unit) {
// Write your code here
}
fun main() {
// Write your code here
}
Example solution
fun repeatN(n: Int, action: () -> Unit) {
for (i in 1..n) {
action()
}
}
fun main() {
repeatN(5) {
println("Hello")
}
}