三、函数

关于此页面

由 Google Developers Training Team 编写(上次更新:2021.02.19)

由 KiritaniAyaka 翻译(上次更新:2021.03.27)

1.欢迎

这个页面是 Kotlin程序员训练营课程 的一部分。如果你想最大化利用这些课程,你最好按照顺序学习课程,但这不是一个必要的条件。根据你的知识,你可能能够读懂其中一部分。这门课程是面向那些熟悉面向对象语言并且想学习Kotlin的程序员的。

介绍

在本课程中,你将学学习 Kotlin 编程语言的基础。数据类型、运算符、变量、控制结构以及可空变量与不可空变量。这门课程是面向那些熟悉面向对象语言并且想学习Kotlin的程序员的。

本课程中的课程不是为了构建一个单一的应用程序,而是被设计用来构建你的知识,但它们之间是半独立的,这样你就可以浏览你熟悉的部分。为了把它们联系在一起,大部分的例子都是用了水族馆主题。如果你想看完整个水族馆的故事,可以看看 Kotlin 程序员训练营的 Udacity 课程

你应该已经知道的

  • 一个现代的、面向对象的、静态的编程语言的稽基础。
  • 使用至少一种语言的类、方法和异常处理进行编程。
  • 如何在 IntelliJ IDEA 中使用 Kotlin REPL(Read-Eval-Print-Loop)。
  • Kotlin 的类型、操作符和循环等基础。

这个课程是面向于那些会至少一种面向对象编程语言基础并且向学习更多 Kotlin 的程序员的。

你将学习的

  • 如何在 IntelliJ IDEA 中创建一个带有 main() 函数和参数的程序。
  • 如何定义紧凑形式的函数。
  • 如何为列表申请过滤器。
  • 如何创建基础 lambda 函数和高阶函数。

你将会做的

  • 使用 REPL 尝试运行一些代码。
  • 使用 IntelliJ IDEA 来创建基础的 Kotlin 程序。

2.探索 main() 函数

咱本节中,你将会创建一个 Kotlin 程序并且学习 main() 函数以及如何通过命令行传递程序参数。

你也许还记得在之前的 REPL 教程中的 printHello() 函数。

1
2
3
4
5
fun printHello() {
println ("Hello World")
}

printHello()
1
⇒ Hello World

你使用 fun 关键字定义了一个函数,在后面紧跟着函数的名字。与其他编程语言一样,如果有需要的话可以在圆括号 () 中写上函数的参数。花括号 {} 中的是函数的代码。这个函数没有返回值类型,因为它没有返回任何东西。

1:创建一个 Kotlin 文件

  1. 打开 IntelliJ IDEA。
  2. IntelliJ IDEA 左边的项目面板显示了你的项目文件和目录列表。右键点击 src 目录下的 Hello Kotlin 。(通过之前的教程,你应该已经有一个 Hello Kotlin 项目了)
  3. 选择 New > Kotlin File / Class
  4. 保持文件的类型不变,将文件命名为 Hello
  5. 点击 OK

现在在 src 目录下有一个名为 Hello.kt 的文件了。

2:添加代码并运行你的程序

  1. 同其他编程语言一样(谷歌是真的喜欢这个句子),Kotlin 中的 main() 是执行的入口点。任何命令行参数都将以字符串数组的形式传递到这里。

Hello.kt 文件中输入或粘贴下面的代码。

1
2
3
fun main(args: Array<String>) {
println("Hello, world!")
}

小芝士:从 Kotlin 1.3 开始,如果你的 main() 函数不使用任何参数,那么你用不需要定义任何参数。

  1. 就像你之前的 printHello() 函数一样,这个函数没有 return 语句。即使你没有显式指定,Kotlin 中的每个函数都会返回一些东西。所以所有函数和这个 main() 函数一样返回了一个叫做 kotlin.Unit 的类型,这是 Kotlin 中表示没有值的方式。

小芝士:当一个函数返回的是 kotlin.Unit 时,你并不需要显式地指定,这与其他编程语言需要显式指定函数没有返回值不同。

  1. 要运行你的程序,点击 main() 函数左边的这个绿色小三角,然后从菜单中选择 Run ‘HelloKt’。
  2. IntelliJ IDEA 会编译程序并运行。程序运行的结果会出现在下面的log面板中,就像下图显示的那样。

给Java程序员的芝士:如果你在使用以前安装的 IntelliJ IDEA 版本而不是最新的版本,在这里你可能会遇到无法访问 Kotlin 编译器的问题。请确保你的项目所指向的是正确的 JDK 版本。具体可以查看在 Stack Overflow 上的讨论以及 IntelliJ IDEA 的项目改动文档

3:给 main() 函数传递参数

由于你正在使用 IntelliJ IDEA 运行你的程序而不是使用命令行,所以你给程序制定参数的方式有所不同。

  1. 选择 Run > Edit Configurations 打开 Run/Debug Configurations 窗口。
  2. Program arguments 字段中输入 Kotlin!
  3. 点击 OK

4:修改代码为使用字符串模板

字符串模板能够插入一个变量或者表达式到一个字符串中。$ 指定了将会变成字符串的变量或数组。如果需要使用表达式的话,需要使用一对花括号 {} 括起来。

  1. Hello.kt 中,使用传递给程序的第一个参数来修改这句问候语,使用 args[0] 替换 world
1
2
3
fun main(args: Array<String>) {
println("Hello, ${args[0]}")
}
  1. 运行程序,输出的内容包含了你所指定的参数。
1
⇒ Hello, Kotlin!

3.为什么几乎所有东西都有值?

在本节中,你将会了解为什么在 Kotlin 中几乎所有的东西都有值,知道这个原因是非常有用的!

一些别的编程语言有某行的代码没有值的情况。在 Kotlin 中,几乎所有东西都是一个表达式,并且都有值,即使它的值是 kotlin.Unit

  1. Hello.ktmain() 函数中,尝试将一个函数 println() 分配个一个叫做 isUnit 的变量并打印它。(println() 没有返回值,所以它返回了一个 kotlin.Unit
1
2
3
// Will assign kotlin.Unit
val isUnit = println("This is an expression")
println(isUnit)
  1. 运行你的程序,第一个 println() 会打印字符串 "This is an expression" 。第二个 println() 会打印第一个 println() 函数的返回值,它就是 kotlin.Unit
1
2
⇒ This is an expression
kotlin.Unit
  1. 使用 val 声明一个变量 temperature 并给它初始化为10。
  2. 声明另一个 val 变量 isHot 并将一个 if/else 语句的返回值分配给它,就像下面的代码一样。因为这是一个表达式,所以你可以使用 if 语句的值。
1
2
3
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot)
1
false
  1. 在字符串模板中使用一个表达式的值。添加一些代码来判断温度对于鱼来说是安全的还是过热的,然后运行你的程序。
1
2
3
val temperature = 10
val message = "The water temperature is ${ if (temperature > 50) "too warm" else "OK" }."
println(message)
1
⇒ The water temperature is OK.

小芝士:循环是“所有东西都是值”的一个例外。因为没有合理的值能够让 for 循环和 while 循环来指定,所以他们并不是值。如果你尝试将循环分配给别的东西,编译器会毫不犹豫地往你脸上糊一个Error。

4.深入学习函数

在本节中,你将会学习 Kotlin 中更多有关函数的东西以及许多有关 when 条件表达式的有用的东西。

1:创建一些函数

在这一步中,你将会把你学过的东西结合起来,创建一些不同类型的函数。你可以将 Hello.kt 中的内容直接替换为新的代码。

  1. 编写一个叫做 feedTheFish() 的函数,通过调用 randomDay() 函数来获得一周中的随机一天。使用一个字符串模板来打印鱼那天吃的食物。现在,鱼每天吃的食物是一样的。
1
2
3
4
5
6
7
8
9
fun feedTheFish() {
val day = randomDay()
val food = "pellets"
println ("Today is $day and the fish eat $food")
}

fun main(args: Array<String>) {
feedTheFish()
}
  1. 编写 randomDay() 函数随机选择一天并且返回它。

nextInt() 函数需要接受一个整数的限制,这将会限制从 Random() 函数中返回的数字在0到6间来匹配 week 数组。

1
2
3
4
5
fun randomDay() : String {
val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
return week[Random().nextInt(week.size)]
}
  1. Random()nextInt() 函数定义在 java.util.* 中。所以在文件的头部需要导入这个包。
1
import java.util.*    // required import

小芝士:在你导入包之前,在 IntelliJ IDEA 中 Random() 会给出一个错误,要自动导入,请在 Random() 上单击,然后按 Alt+Enter (在 Mac 系统上是 Option+Enter),然后选择 Import > java.util.Random

  1. 运行你的程序并检查输出。
1
⇒ Today is Tuesday and the fish eat pellets

2:使用 when 表达式

在这个基础上使用 when 表达式修改代码为每天吃不同的食物。when 表达式更像是其他编程语言中的 switch ,但是 when 会在每个分支末尾自动 break。如果你检查枚举,它还会确保覆盖代码的每个分支。

  1. Hello.kt 中添加一个函数 fishFood() ,这个函数接收一个 String 类型的 day 并且以 String 类型返回鱼这天吃的食物。使用 when() 让鱼每天吃到特定的食物。运行几次你的程序来观察不同的输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun fishFood (day : String) : String {
var food = ""
when (day) {
"Monday" -> food = "flakes"
"Tuesday" -> food = "pellets"
"Wednesday" -> food = "redworms"
"Thursday" -> food = "granules"
"Friday" -> food = "mosquitoes"
"Saturday" -> food = "lettuce"
"Sunday" -> food = "plankton"
}
return food
}

fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)

println ("Today is $day and the fish eat $food")
}
1
⇒ Today is Thursday and the fish eat granules
  1. 使用 elsewhen() 表达式添加一个默认分支。为了测试,确保程序有时可以走到默认分支,删除 TuesdaySaturdat 分支。

有了默认分支可以确保 food 在返回之前一定会得到一个值,所以不需要在给它初始化。由于现在代码只为 food 分配了一次值,所以你可以使用 val 代替 var 来声明 food

1
2
3
4
5
6
7
8
9
10
11
12
fun fishFood (day : String) : String {
val food : String
when (day) {
"Monday" -> food = "flakes"
"Wednesday" -> food = "redworms"
"Thursday" -> food = "granules"
"Friday" -> food = "mosquitoes"
"Sunday" -> food = "plankton"
else -> food = "nothing"
}
return food
}
  1. 由于每个表达式都有一个值,你可以让这段代码变得更加简洁。直接返回 when 表达式的值并且删除 food 变量。when 表达式的值就是满足条件的分支的最后一个表达式的值。
1
2
3
4
5
6
7
8
9
10
fun fishFood (day : String) : String {
return when (day) {
"Monday" -> "flakes"
"Wednesday" -> "redworms"
"Thursday" -> "granules"
"Friday" -> "mosquitoes"
"Sunday" -> "plankton"
else -> "nothing"
}
}

你的程序的最终版本就像下面的代码一样。

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
import java.util.*    // required import

fun randomDay() : String {
val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
return week[Random().nextInt(week.size)]
}

fun fishFood (day : String) : String {
return when (day) {
"Monday" -> "flakes"
"Wednesday" -> "redworms"
"Thursday" -> "granules"
"Friday" -> "mosquitoes"
"Sunday" -> "plankton"
else -> "nothing"
}
}

fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
}

fun main(args: Array<String>) {
feedTheFish()
}

5.探索默认值和紧凑函数

在本节中,你将会学习函数和方法的默认值以及能够让你的代码更加简洁和可读并减少代码测试路径的紧凑函数(Compact functions)。紧凑函数也被称为单表达式函数(Single-expression functions)。

1:为参数创建一个默认值

在 Kotlin 中,你可以通过形参名传递实参。你也可以为形参指定默认值。当函数的调用方没有提供值的时候,就会采用默认值。当你编写方法(成员方法)的时候,这可以避免编写大量重复功能的重载方法。

  1. Hello.kt 中,编写一个带有一个 String 参数的 swim() 函数来打印鱼的速度。速度参数 speed 有默认值 "fast"
1
2
3
fun swim(speed: String = "fast") {
println("swimming $speed")
}
  1. main() 函数中调用 swim() 函数三次。第一次调用函数使用默认值,然后通过不指定参数名称的方法调用这个函数并传递给 speed 一个参数,再通过指定 speed 参数调用这个函数。
1
2
3
swim()   // uses default speed
swim("slow") // positional argument
swim(speed="turtle-like") // named parameter
1
2
3
⇒ swimming fast
swimming slow
swimming turtle-like



如果翻译有误或想补充翻译未翻译的部分欢迎反馈。