在此任务中,您将创建一个 Kotlin 程序并了解 main()
函数。此外,您还将了解如何从命令行向程序传递参数。
您可能还记得在上一个 Codelab 中输入到 REPL 中的 printHello()
函数:
fun printHello() {
println ("Hello World")
}
printHello()
⇒ Hello World
如需定义函数,请使用 fun
关键字,后跟函数名称。与其他编程语言一样,圆括号 ()
用于函数参数(如有)。大括号 {}
用于括住函数的代码块,并提供作用域限定上下文。printHello()
函数不存在任何返回值类型,因为此函数不会返回任何内容。
第 1 步:创建 Kotlin 文件
- 打开 IntelliJ IDEA。
- IntelliJ IDEA 左侧的 Project 窗口会显示项目文件和文件夹列表。查找并右键点击 Hello Kotlin 下的 src 文件夹(根据您的 IDE 版本,您可能需要右键点击
src/main
下的kotlin
文件夹)。您应当已从上一个 Codelab 获得 Hello Kotlin 项目。 - 依次选择 New > Kotlin File。
- 将文件命名为 Hello。
- 点击 OK。
现在,src 文件夹中有一个名为 Hello.kt 的文件。
第 2 步:添加代码并运行程序
- 与其他语言一样,Kotlin
main()
函数指定了执行的入口点。任何命令行参数都作为字符串数组传递。
将以下代码输入或粘贴到 Hello.kt 文件中:
fun main(args: Array<String>) {
println("Hello, world!")
}
与之前的 printHello() 函数一样,此函数没有 return 语句。在 Kotlin 中,即使没有明确指定,每个函数也会返回一些内容。当某个函数没有显式 return 语句时,该函数会返回 Unit(以前为 kotlin.Unit)。因此,没有显式 return 语句的 main() 函数会返回 Unit。
- 如需运行程序,请点击
main()
函数左侧的绿色三角形。从菜单中选择 Run 'HelloKt'。
IntelliJ IDEA 会编译并运行该程序。结果会显示在底部的日志中,如下所示。
第 3 步:向 main() 传递参数
由于您从 IntelliJ IDEA 而非命令行来运行程序,因此需要为该程序指定任何略有不同的参数。
- 依次选择 Run > Edit Configurations。系统会显示 Run/Debug Configurations 窗口。
- 在 Program arguments 字段中输入
Kotlin!
。 - 点击 OK。
第 4 步:更改代码以使用字符串模板
字符串模板会将变量或表达式插入字符串中,$
会将字符串的一部分指定为变量或表达式。大括号 {}
用于括住表达式(如有)。
- 在 Hello.kt 中,更改问候语以使用传递到程序的第一个参数
args[0]
,而不是"world"
。
fun main(args: Array<String>) {
println("Hello, ${args[0]}")
}
- 运行程序,输出结果将包含您指定的参数。
⇒ Hello, Kotlin!
在此任务中,您将了解 Kotlin 中的几乎所有内容都具有值的原因,及其具有重要意义的原因。
一些编程语言只有语句,这些语句是没有任何值的代码行。在 Kotlin 中,几乎所有内容都是表达式,并且都具有值(即使该值为 kotlin.Unit
)。
注意:与其他编程语言类似,Kotlin 函数的值是该函数返回的值。如果函数未返回值,默认值为 kotlin.Unit
。
- 在 Hello.kt 中,在
main()
中编写代码,为名为isUnit
的变量赋予返回值println()
并输出该值。(println()
未显示返回值,因此将返回kotlin.Unit
。)
// Will assign kotlin.Unit
val isUnit = println("This is an expression")
println(isUnit)
- 运行程序。第一个
println()
会输出字符串"This is an expression"
。第二个println()
会输出第一个println()
语句的值,即kotlin.Unit
。
This is an expression kotlin.Unit
- 在 Hello.kt 文件的
main
() 函数中,声明一个名为temperature
的val
并将其初始化为 10。 - 声明另一个名为
isHot
的val
,并为isHot
赋予if
/else
语句的返回值,如以下代码所示。由于它是一个表达式,因此您可以立即使用if
表达式的值。
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot)
输出结果将显示以下内容:
false
- 在字符串模板中使用表达式的值。在
main()
中添加某个代码以检查水温,从而确定水温是否适宜鱼类生存或过热,然后运行程序。
val temperature = 10
val message = "The water temperature is ${ if (temperature > 50) "too warm" else "OK" }."
println(message)
输出结果将显示以下内容:
The water temperature is OK.
在此任务中,您将详细了解 Kotlin 中的函数,并详细了解非常有用的 when
条件表达式。
第 1 步:创建一些函数
在此步骤中,您将利用学到的一些知识,并创建不同类型的函数。您可以使用以下新代码替换 Hello.kt 的内容。
- 编写一个名为
feedTheFish()
的函数,该函数调用randomDay()
以获取一周中的随机日期。使用字符串模板,输出鱼类当天吃的food
。目前,鱼类每天吃同一种食物。
fun feedTheFish() {
val day = randomDay()
val food = "pellets"
println ("Today is $day and the fish eat $food")
}
fun main(args: Array<String>) {
feedTheFish()
}
- 在 Hello.kt 中添加
randomDay()
函数,以从数组中选择随机日期并返回。
nextInt()
函数采用整数限制,这会将数字从 Random()
限制到 0 至 6,以与 week
数组匹配。
fun randomDay() : String {
val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
return week[Random().nextInt(week.size)]
}
Random()
和nextInt()
函数在java.util.*
中定义。在文件顶部添加所需导入:
import java.util.* // required import
- 运行程序并检查输出结果。
⇒ Today is Tuesday and the fish eat pellets
第 2 步:使用 when 表达式
进一步扩展,更改代码以使用 when
表达式为不同日期选择不同食物。在其他编程语言中,when
语句类似于 switch
,但 when
会在每个分支结束时自动中断。在您检查枚举的情况下,它还可确保您的代码覆盖所有分支。
- 在 Hello.kt 中,添加一个名为
fishFood()
的函数,该函数将某个日期作为String
,并以String
形式返回鱼类当天吃的食物。使用when()
,确保鱼类每天获得特定食物。运行程序几次,以查看不同的输出结果。
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")
}
⇒ Today is Thursday and the fish eat granules
- 使用
else
向when
表达式添加默认分支。对于测试,为确保有时在程序中采用默认值,请移除Tuesday
和Saturday
分支。
使用默认分支可确保 food
在返回之前获得一个值,因此不再需要对其进行初始化。由于代码现在仅向 food
分配字符串一次,因此您可以使用 val
而非 var
声明 food
。
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
}
- 由于每个表达式都具有值,因此您可以使此代码更简洁一些。直接返回
when
表达式的值,并清除food
变量。when
表达式的值是满足条件的分支的最后一个表达式的值。
fun fishFood (day : String) : String {
return when (day) {
"Monday" -> "flakes"
"Wednesday" -> "redworms"
"Thursday" -> "granules"
"Friday" -> "mosquitoes"
"Sunday" -> "plankton"
else -> "nothing"
}
}
程序的最终版本类似于下面的代码。
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()
}
在此任务中,您将了解函数参数的默认值。此外,您还将了解紧凑型函数,此类函数可以使您的代码更加简洁、可读性更高,并且可以减少测试用代码路径的数量。紧凑型函数也称为单表达式函数。
第 1 步:为参数创建默认值
在 Kotlin 中,您可以按参数名称传递参数。此外,您还可以为参数指定默认值:如果调用方未提供参数,系统会使用默认值。稍后,当您编写方法(成员函数)时,这意味着您可以避免编写同一方法的大量重载版本。
- 在 Hello.kt 中,编写一个
swim()
函数,其中包含一个名为speed
的String
参数,用于输出鱼类的速度。speed
参数的默认值为"fast"
。
fun swim(speed: String = "fast") {
println("swimming $speed")
}
- 从
main()
函数中,以三种方式调用swim()
函数。首先,使用默认值调用该函数。然后,调用该函数并传递未命名的speed
参数,然后命名speed
参数以调用该函数。
swim() // uses default speed
swim("slow") // positional argument
swim(speed="turtle-like") // named parameter
⇒ swimming fast swimming slow swimming turtle-like
第 2 步:添加必需参数
如果未为参数指定默认值,必须始终传递相应的参数。
- 在 Hello.kt 中,编写一个
shouldChangeWater()
函数,该函数采用三个参数:day
、temperature
和dirty
级别。如果应当更换水,该函数会返回true
,在星期天、温度过高或水过脏时,就会发生这种情况。一周当中的具体日期是必需参数,但默认温度为 22 度,默认脏污级别为 20。
使用不带参数的 when
表达式,在 Kotlin 中,该参数充当一系列 if/else if
检查。
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
return when {
temperature > 30 -> true
dirty > 30 -> true
day == "Sunday" -> true
else -> false
}
}
- 从
feedTheFish()
中调用shouldChangeWater()
并提供具体日期。day
参数未设默认值,因此您必须指定一个参数。shouldChangeWater()
的其他两个参数设有默认值,因此您无需为其传递参数。
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
println("Change water: ${shouldChangeWater(day)}")
}
=> Today is Thursday and the fish eat granules Change water: false
第 3 步:创建紧凑型函数
您在上一步中编写的 when
表达式将大量逻辑打包到少量代码中。如果您曾想稍微解压缩,或者要检查的条件更加复杂,您可以使用一些命名合理的局部变量。但是,Kotlin 会利用紧凑型函数实现这一点。
紧凑型函数(即单表达式函数)是 Kotlin 中的一种常见模式。当某个函数返回单个表达式的结果时,您可以在 =
符号后指定该函数的正文,省略大括号 {}
,并省略 return
。
- 在 Hello.kt 中,添加紧凑型函数来测试条件。
fun isTooHot(temperature: Int) = temperature > 30
fun isDirty(dirty: Int) = dirty > 30
fun isSunday(day: String) = day == "Sunday"
- 更改
shouldChangeWater()
以调用新函数。
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
return when {
isTooHot(temperature) -> true
isDirty(dirty) -> true
isSunday(day) -> true
else -> false
}
}
- 运行程序。包含
shouldChangeWater()
的println()
的输出结果应当与您切换到使用紧凑型函数之前的输出结果相同。
默认值
参数的默认值不必是一个值,可以是另一个函数,如以下部分示例所示:
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = getDirtySensorReading()): Boolean {
...
在此任务中,您将了解 Kotlin 中的过滤器。过滤器可以便捷地根据特定条件获取部分列表。
第 1 步:创建过滤器
- 在 Hello.kt 中,使用
listOf()
在顶层定义水族箱装饰列表。您可以替换 Hello.kt 的内容。
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
- 创建一个新的
main()
函数,其中一行仅输出以字母“p”开头的装饰。过滤条件代码包含在大括号{}
中;当过滤器循环遍历列表时,it
会隐式引用每一项。如果表达式返回true
,该项将会被包含在内。
fun main() {
println( decorations.filter {it[0] == 'p'})
}
- 运行程序,并在 Run 窗口中查看以下输出结果:
⇒ [pagoda, plastic plant]
第 2 步:比较即刻过滤器和延迟过滤器
如果您熟悉其他语言的过滤器,您可能会知道 Kotlin 中的过滤器是即刻过滤器还是延迟过滤器。结果列表是立即创建(即刻)还是在访问时创建(延迟)?在 Kotlin 中,可以根据需要确定是即刻过滤器还是延迟过滤器。默认情况下,filter
是即刻过滤器,并且在您每次使用该过滤器时,系统都会创建一个列表。
为使过滤器延迟显示,您可以使用 Sequence
,这是一种集合,每次仅支持查看一项内容,从开头开始,一直到其结尾。方便的是,这正是延迟过滤器所需的 API。
- 在 Hello.kt 中,更改代码以将过滤后的列表赋给一个名为
eager
的变量,然后输出该变量。
fun main() {
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
// eager, creates a new list
val eager = decorations.filter { it [0] == 'p' }
println("eager: $eager")
- 在该代码下面,使用包含
asSequence()
的Sequence
对过滤器执行求值。将序列赋给一个名为filtered
的变量,然后输出该变量。
// lazy, will wait until asked to evaluate
val filtered = decorations.asSequence().filter { it[0] == 'p' }
println("filtered: $filtered")
当您以 Sequence
形式返回过滤器结果时,filtered
变量不会保存新列表,而是保存列表元素的 Sequence
以及要应用于这些元素的过滤器信息。每当您访问 Sequence
的元素时,系统就会应用过滤器,并将结果返回给您。
- 使用
toList()
将序列转换为List
,以强制对该序列执行求值。输出结果。
// force evaluation of the lazy list
val newList = filtered.toList()
println("new list: $newList")
- 运行程序并观察输出结果。
⇒ eager: [pagoda, plastic plant] filtered: kotlin.sequences.FilteringSequence@386cc1c4 new list: [pagoda, plastic plant]
如需直观呈现 Sequence
和延迟求值的情况,请使用 map()
函数。map()
函数会对序列中的每个元素执行简单的转换。
- 仍使用上面的
decorations
列表,使用map()
执行转换,该函数不执行任何操作且仅返回传递的元素。添加println()
以在系统每次访问元素时显示,并将序列赋给一个名为lazyMap
的变量。
val lazyMap = decorations.asSequence().map {
println("access: $it")
it
}
- 输出
lazyMap
,使用first()
输出lazyMap
的第一个元素,并输出转换为List
的lazyMap
。
println("lazy: $lazyMap")
println("-----")
println("first: ${lazyMap.first()}")
println("-----")
println("all: ${lazyMap.toList()}")
- 运行程序并观察输出结果。输出
lazyMap
仅会输出对Sequence
的引用,系统不会调用内部println()
。输出第一个元素仅会访问第一个元素。将Sequence
转换为List
可访问所有元素。
⇒ lazy: kotlin.sequences.TransformingSequence@5ba23b66 ----- access: rock first: rock ----- access: rock access: pagoda access: plastic plant access: alligator access: flowerpot all: [rock, pagoda, plastic plant, alligator, flowerpot]
- 使用原始过滤器创建新的
Sequence
,然后应用map
。输出该结果。
val lazyMap2 = decorations.asSequence().filter {it[0] == 'p'}.map {
println("access: $it")
it
}
println("-----")
println("filtered: ${lazyMap2.toList()}")
- 运行程序并观察其他输出结果。与获取第一个元素一样,系统仅会对访问的元素调用内部
println()
。
⇒ ----- access: pagoda access: plastic plant filtered: [pagoda, plastic plant]
- Kotlin 集合的另一个有用的转换函数是
flatten
()。此函数从一系列数组或一系列列表等一系列集合创建列表。 - 创建一系列列表。然后,应用 flatten() 函数,将所有列表转换为一个列表。输出结果。
val mysports = listOf("basketball", "fishing", "running")
val myplayers = listOf("LeBron James", "Ernest Hemingway", "Usain Bolt")
val mycities = listOf("Los Angeles", "Chicago", "Jamaica")
val mylist = listOf(mysports, myplayers, mycities) // list of lists
println("-----")
println("Flat: ${mylist.flatten()}")
⇒ ----- Flat: [basketball, fishing, running, LeBron James, Ernest Hemingway, Usain Bolt, Los Angeles, Chicago, Jamaica]
在此任务中,您将了解 Kotlin 中的 lambda 和高阶函数。
lambda
除传统命名函数外,Kotlin 还支持与 Java 类似的 lambda。lambda 是描述函数的表达式。但是,您不必声明有名称的函数,只需声明没有名称的函数。这样一来,lambda 表达式现在可作为数据传递。在其他语言中,lambda 称为匿名函数、函数字面量或类似名称。
第 1 步:了解 lambda
- 与命名函数一样,lambda 可以具有参数。对于 lambda,参数(及其类型,如果需要)位于所谓的函数箭头
->
的左侧。要执行的代码位于该函数箭头的右侧。一旦将 lambda 赋给变量,就可像调用函数一样调用它。
使用 REPL (Tools > Kotlin > Kotlin REPL),试用以下代码:
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10
在此示例中,lambda 采用名为 dirty
的 Int
,并返回 dirty / 2
。()
- Kotlin 的函数类型语法与其 lambda 语法密切相关。使用以下语法清晰地声明一个包含函数的变量:
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
此代码的作用如下:
- 创建一个名为
waterFilter
的变量。 waterFilter
可以是任何采用Int
并返回Int
的函数。- 将 lambda 赋给
waterFilter
。 - lambda 会返回参数
dirty
除以 2 所得到的值。
请注意,您不再需要指定 lambda 参数的类型。此类型根据类型推断计算得出。
第 2 步:创建高阶函数
截至目前为止,在大多数情况下,lambda 的示例看起来与函数类似。lambda 的真正强大之处在于:它们用于创建高阶函数,其中,一个函数的参数是另一个函数。
高阶函数是将其他函数作为参数的函数,或者是返回不同函数的函数。您可以将 lambda 传递给将函数作为参数的高阶函数。在上一个任务中,您创建了一个名为过滤器的高阶函数。此外,您还传递了以下 lambda 表达式,以作为要检查的条件进行过滤:
{ it[0] == 'p' }
同样,map
是高阶函数,您曾向该函数传递的 lambda 是要应用的转换。
- 编写一个高阶函数。以下是一个基本示例,即一个采用两个参数的函数。第一个参数是一个整数。第二个参数是一个采用并返回整数的函数。在 REPL 中试用。
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int {
return operation(dirty)
}
代码的正文会调用作为第二个参数传递的函数,并向其传递第一个参数。
- 如需调用此函数,请向其传递一个整数和一个函数。
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
println(updateDirty(30, waterFilter))
⇒ 15
您传递的函数不必是 lambda;相反,此函数可以是一个常规命名函数。如需将该参数指定为常规函数,请使用 ::
运算符。这样,Kotlin 就能知道您将函数引用作为参数传递,而不是尝试调用该函数。
- 尝试将常规命名函数传递给
updateDirty()
。
fun increaseDirty( start: Int ) = start + 1
println(updateDirty(15, ::increaseDirty))
⇒ 16
var dirtyLevel = 19
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
println(dirtyLevel)
⇒ 42
- 如需在 IntelliJ IDEA 中创建新的 Kotlin 源文件,请点击“Project”窗格中的“src”,然后右键点击以打开菜单。依次选择“New->Kotlin File/Class”。
- 如需在 IntelliJ IDEA 中编译并运行程序,请点击
main()
函数旁边的绿色三角形。输出结果会显示在下面的窗口中。 - 在 IntelliJ IDEA 中,在 Run > Edit Configurations 中指定要传递给
main()
函数的命令行参数。 - Kotlin 中的几乎所有内容都具有值。通过将
if
或when
的值用作表达式或返回值,您可以利用这一点使代码更加简洁。 - 使用默认参数,便不再需要多个版本的函数或方法。例如:
fun swim(speed: String = "fast") { ... }
- 使用紧凑型函数(即单表达式函数)可以提高代码的可读性。例如:
fun isTooHot(temperature: Int) = temperature > 30
- 您已了解一些有关使用 lambda 表达式的过滤器的基础知识。例如:
val beginsWithP = decorations.filter { it [0] == 'p' }
- lambda 表达式是未绑定到标识符的函数,即匿名函数。lambda 表达式在大括号
{}
之间定义。 - 在高阶函数中,您可以将一个函数(如 lambda 表达式)作为数据传递给另一个函数。例如:
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
本课涵盖内容较广,对于不熟悉 lambda 的人来说存在一定难度。后续课程将详细介绍 lambda 和高阶函数。