二、Kotlin 基础

关于此页面

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

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

1.欢迎

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

介绍

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

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

你应该已经知道的

  • 如何在 IntelliJ IDEA 中创建一个项目。
  • 如何在 IntelliJ IDEA 中的 Kotlin RRPL(Read-Eval-Print Loop)中打开和执行代码。

你将学习的

  • 如何使用 Kotlin 的数据类型、操作符和变量。
  • 如何使用布尔类型数据和条件。
  • 可空变量和不可空变量的区别。
  • 数组、列表和循环如何在 Kotlin 中工作。

你将会做的

  • 使用 Kotlin REPL 学习 Kotlin 基础。

2.学习操作符和类型

在本节中,你讲学习 Kotlin 中有关操作符和类型的知识。

步骤1:探索数值运算符

  1. 打开 IntelliJ IDEA。
  2. 选择 Tools > Kotlin > Kotlin REPL 打开Kotlin REPL。

与其他编程语言一样,Kotlin 使用+-*/来分别表示加、减、乘、除。Kotlin 也支持不同的数值类型,如IntLongDoubleFloat

  1. 输入下面的表达式,并按Control+Enter(在Mac上为Command+Enter)来查看结果。

注意:在此课程中,表示代码的输出。在最新版本的REPL中,输出包含了结果编号和结果的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1+1
⇒ res8: kotlin.Int = 2

53-3
⇒ res9: kotlin.Int = 50

50/10
⇒ res10: kotlin.Int = 5

1.0/2.0
⇒ res11: kotlin.Double = 0.5

2.0*3.5
⇒ res12: kotlin.Double = 7.0

请注意,操作符的结果与操作数的类型保持一致。所以 1/2=0,但是1.0/2.0=0.5。

  1. 尝试一些不同整数和小数组合的表达式。
1
2
3
4
5
6
7
8
6*50
⇒ res13: kotlin.Int = 300

6.0*50.0
⇒ res14: kotlin.Double = 300.0

6.0*50
⇒ res15: kotlin.Double = 300.0
  1. 尝试在数字上调用一些方法。Kotlin 将数字作为基元,但是你仍然可以像对对象一样在数字上调用方法。
1
2
3
4
5
6
7
8
2.times(3
⇒res5:kotlin.Int = 6

3.5.plus(4
⇒res8:kotlin.Double = 7.5

2.4.div(2
⇒res9:kotlin.Double = 1.2

注意:可以围绕数字创建实体对象包装器,这被称为 boxing。boxing 会自动发生,例如对于集合,装箱时会根据需要将数字装箱和拆箱。

警告:使用对象包装器比仅存储数字要占用更多的内存。如果不是必要请不要使用 boxing 。例如在一个集合中,这将在后面讨论。

步骤2:练习使用类型

Kotlin 不会在数值类型之间进行隐式转换,因此你不能把short直接赋值给long变量,也不能将Byte赋值给Int。这时因为隐式数值转换是程序中常见的错误源。你始终能使用强制类型转换来赋值不同类型的数据。

  1. 要查看一些可能的转换,请在REPL中定义一个Int类型的变量。
1
val i: Int = 6
  1. 创建一个新的变量,然后输入上面的变量名,然后输入.to.
1
val b1 = i.to

IntelliJ IDEA 显示了可能完成的列表。这种自动完成适用于任何变量类型和对象。

  1. 在列表中选择toByte(),然后打印变量。
1
2
val b1 = i.toByte()
println(b1)
1
6
  1. Byte赋值给不同类型的变量。
1
2
3
4
5
6
7
8
9
10
11
12
val b2: Byte = 1 // OK, literals are checked statically
println(b2)
1

val i1: Int = b2
⇒ error: type mismatch: inferred type is Byte but Int was expected

val i2: String = b2
⇒ error: type mismatch: inferred type is Byte but String was expected

val i3: Double = b2
⇒ error: type mismatch: inferred type is Byte but Double was expected
  1. 对于返回错误的赋值,请尝试转换它们。
1
2
3
4
5
6
7
8
9
10
11
val i4: Int = b2.toInt() // OK!
println(i4)
1

val i5: String = b2.toString()
println(i5)
1

val i6: Double = b2.toDouble()
println(i6)
1.0
  1. 为了使长数值常量更具可读性,Kotlin 允许在数字中添加下划线,这对你来说是有意义的。尝试输入不同的数值常量。
1
2
3
4
val oneMillion = 1_000_000
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

注意:由于 Kotlin 是强类型的,编译器可以判断变量的类型,所以你不需要显式声明变量。

步骤3:学习变量类型的值

Kotlin 支持两种变量类型,可变和不可变。使用val你可以指定一个值一次。如果你再尝试赋值,将会得到一个错误。使用var你可以指定一个值,然后稍后在程序运行时更改这个值。

  1. 使用valvar定义变量,并为它们赋一个新值。
1
2
3
4
var fish = 1
fish = 2
val aquarium = 1
aquarium = 2
1
⇒ error: val cannot be reassigned

你可以对fish赋值,然后给它赋予一个新值,因为它是使用var定义的。尝试对aquarium赋予一个新值会得到一个错误,因为它是使用val定义的。

当编译器可以从上下文中找出变量时,将推断出存储在变量中的类型。如果你想的话,可以使用冒号来显式指定变量的类型。

  1. 定义一些变量并明确指定它们的类型。
1
2
var fish: Int = 12
var lakes: Double = 2.5

一旦你或者编译器分配了类型,你将不能再改变类型,否则会得到一个错误。

步骤4:学习字符串

Kotlin 中的字符串和其他编程语言类似,使用"来指定字符串'来指定单个字符,并且能使用+操作符来;连接字符串。你可以将字符串模板与值组合来创建字符串模板;$variable name将被替换为表示该值的文本。这称之为变量插值。

  1. 创建字符串模板。
1
2
3
val numberOfFish = 5
val numberOfPlants = 12
"I have $numberOfFish fish" + " and $numberOfPlants plants"
1
⇒ res20: kotlin.String = I have 5 fish and 12 plants
  1. 创建包含表达式的字符串模板。与其他语言一样,值可以是表达式的结果。使用大括号{}来定义表达式。
1
"I have ${numberOfFish + numberOfPlants} fish and plants"
1
⇒ res21: kotlin.String = I have 17 fish and plants

3.比较条件和布尔值

在本节里,你将了解布尔值和 Kotlin 语言中的检查条件。和其他语言一样,Kotlin 也有布尔值和布尔运算符,比如<==!=<=>=

  1. 编写一个if/else语句。
1
2
3
4
5
6
7
val numberOfFish = 50
val numberOfPlants = 23
if (numberOfFish > numberOfPlants) {
println("Good ratio!")
} else {
println("Unhealthy ratio")
}
1
⇒ Good ratio!
  1. 尝试使用 if 语句判断一个范围。在 Kotlin 中,你也能使用范围来判断。
1
2
3
4
val fish = 50
if (fish in 1..100) {
println(fish)
}
1
50
  1. 编写带有多个分支的 if 语句,对于一些复杂的条件,可以在其中使用 &&|| 逻辑运算符。如其他语言一样,你也可以在分支中使用 else if
1
2
3
4
5
6
7
if (numberOfFish == 0) {
println("Empty tank")
} else if (numberOfFish < 40) {
println("Got fish!")
} else {
println("That's a lot of fish!")
}
1
⇒ That's a lot of fish!
  1. 尝试编写一个 when 语句。在 Kotlin 里,这与你使用 if / else if else 一样。Kotlin 中的 when 语句更像是其他语言中的 switch 语句。当然,when 语句中的条件也支持使用范围。
1
2
3
4
5
when (numberOfFish) {
0 -> println("Empty tank")
in 1..39 -> println("Got fish!")
else -> println("That's a lot of fish!")
}
1
⇒ That's a lot of fish!

4.学习nullability

在本节中,你将会学习可空和不可空的变量。许多有关空指针的程序错误都将被解决。(Programming errors involving nulls have been the source of countless bugs)。Kotlin 可以通过不可空的变量来减少bug。

第一步:学习可空性

默认情况下变量是不能为 null 的。

  1. 声明一个 Int 变量并给它初始化为 null
1
var rocks: Int = null
1
⇒ error: null can not be a value of a non-null type Int
  1. 在你声明的变量类型后使用问号操作符 ? 来表示该变量可以为空。声明一个 Int? 变量并给它初始化为 null
1
var marbles: Int? = null

当你使用的是复杂数据类型的时候,参考下面的表:

  • 你可以允许列表的元素为空。
  • 你可以允许列表为空,但如果列表不为空,则其元素不能为空。
  • 你可以允许列表或元素都为空。

列表和一些其他的复杂数据类型将在后面的章节中介绍。

第二步:学习 ??: 操作符

你可以使用 ? 操作符判断 null 来挽救你写一大堆 if / else 语句的痛苦。

  1. 先来看看比较长的用于判断变量 fishFoodTreats 是否非空的代码,然后让变量自减。
1
2
3
4
var fishFoodTreats = 6
if (fishFoodTreats != null) {
fishFoodTreats = fishFoodTreats.dec()
}
  1. 现在再看看在 Kotlin 中上面这段代码使用 ? 操作符的写法。
1
2
var fishFoodTreats = 6
fishFoodTreats = fishFoodTreats?.dec()
  1. 你也可以使用 ?: 操作符来连接以上操作:
1
fishFoodTreats = fishFoodTreats?.dec() ?: 0

这是“如果 fishFoodTreats 不为空,让它自减并使用它;否则使用 ?: 后面的值:0”的缩写。如果 fishFoodTreats 为空,表达式会终止,并且 dec() 方法不会被调用。

小芝士:?: 操作符有时也被称为 Elvis 操作符(猫王操作符),因为它看起来很像一个庞毕度发型的笑脸,就像猫王的发型一样。在这里阅读更多关于 Kotlin 中猫王操作符的文档。

一个关于空指针的小芝士

如果你真的爱死了 空指针异常 ,Kotlin 也为你准备好了。非空断言操作符(!!)可以将任何值转换为一个可空类型,如果你的值是 null 的话还会抛出一个异常。

1
val len = s!!.length   // 如果值是 null 就会抛出空指针异常

小芝士:在编程术语中,感叹号通常被称为“bang”,所以非空断言操作符有时也被称为 double-bang 操作符或者 bang bang 操作符。

注意:一般情况下使用 !! 操作符不是一个好主意。这也正是语言设计者让你输入两个而不是一个感叹号的原因。不过,你在处理Java的遗留代码的时候仍然需要用到这个操作符。

5.探索数组,列表和循环

在这本节中,你将会学习有关数组和列表以及在 Kotlin 中创建循环的不同方式的知识。

第一步:生成列表

Kotlin 中的列表是一种基础类型,并且和其他语言中的列表很相似。

  1. 使用 listOf 声明一个列表并且输出它们。这个列表是不可变的。
1
2
val school = listOf("mackerel", "trout", "halibut")
println(school)
1
⇒ [mackerel, trout, halibut]
  1. 使用 mutableListOf 声明一个可变的列表,并移除其中的一项。
1
2
val myList = mutableListOf("tuna", "salmon", "shark")
myList.remove("shark")
1
⇒ res36: kotlin.Boolean = true

remove() 方法成功移除元素后会返回 true 值。

小芝士:对于使用 val 定义的列表,你不能修改变量的引用,但你仍然可以改变列表的内容。

第二步:创建数组

如其他语言一样,Kotlin 中也有数组。但与 Kotlin 中的列表不同的是,数组没有可变和不可变的版本,而是只有不可变的版本。一旦你创建了一个数组,数组大小就是固定的。你不能新增或者删除其中的元素,除非你把它们复制到一个新的数组中。

使用 valvar 的规则同样适用于数组和列表。

小芝士:对于使用 val 定义的数组,你不能改变数组的引用,但你仍然可以改变数组的内容。

  1. 使用 arrayOf 声明一个字符串数组。使用 java.util.Arrays.toString() 数组工具来输出它。
1
2
val school = arrayOf("shark", "salmon", "minnow")
println(java.util.Arrays.toString(school))
1
⇒ [shark, salmon, minnow]
  1. 使用 arrayOf 声明的数组元素没有类型关联,所以你可以混合多种类型,这是十分有用的。声明一个不同类型的数组。
1
val mix = arrayOf("fish", 2)
  1. 你也可以使用全部相同类型的元素来声明数组。使用 intArrayOf() 声明一个整数数组。对于其他类型的数组,有对应的构造器或者实例化函数。
1
val numbers = intArrayOf(1,2,3)

小芝士:使用例如 Int 或者 Byte 等基础类型的数组可以避免装箱的额外开销。

  1. 使用 + 操作符合并两个数组。
1
2
3
4
val numbers = intArrayOf(1,2,3)
val numbers3 = intArrayOf(4,5,6)
val foo2 = numbers3 + numbers
println(foo2[5])
1
=> 3
  1. 尝试嵌套合并不同类型的数组和列表。与其他语言一样,你可以嵌套数组和列表。也就是说,当你将一个数组放入一个数组中时,你将会得到一个二维数组而不是一个包含它们两个内容的一维数组。数组的元素可以是列表,列表的元素也可以是数组。
1
2
3
4
val numbers = intArrayOf(1, 2, 3)
val oceans = listOf("Atlantic", "Pacific")
val oddList = listOf(numbers, oceans, "salmon")
println(oddList)
1
⇒ [[[email protected]89178b4, [Atlantic, Pacific], salmon]

第一个元素 numbers 是一个数组。当你不使用数组工具而直接打印它时,Kotlin 会打印内存地址而不是数组的元素。

  1. Kotlin 的一个很好的特性就是可以使用代码来初始化一个数组而不是全部初始化为0,就像下面的栗子:
1
2
val array = Array (5) { it * 2 }
println(java.util.Arrays.toString(array))
1
⇒ [0, 2, 4, 6, 8]

初始化代码写在花括号中间,it 参考的是数组的索引,它从0开始。

第三步:创建循环

现在你有了列表和数组,你可以像你期望的那样遍历它们的元素。

  1. 创建一个数组。使用 for 循环迭代数组中的每一个元素并打印它们。
1
2
3
4
val school = arrayOf("shark", "salmon", "minnow")
for (element in school) {
print(element + " ")
}
1
⇒ shark salmon minnow
  1. 在 Kotlin 中,你可以同时遍历元素和索引。尝试下面的栗子:
1
2
3
for ((index, element) in school.withIndex()) {
println("Item at $index is $element\n")
}
1
2
3
⇒ Item at 0 is shark
Item at 1 is salmon
Item at 2 is minnow
  1. 尝试不同的大小和范围。你可以指定数字、字符的范围按照顺序遍历。并且如同在其他语言中一样,你不必每次都向目标前进1步,你可以使用 downTo 来倒序遍历。
1
2
3
4
5
6
7
8
9
10
11
for (i in 1..5) print(i)
12345

for (i in 5 downTo 1) print(i)
54321

for (i in 3..6 step 2) print(i)
35

for (i in 'd'..'g') print (i)
⇒ defg
  1. 尝试编写一些循环。像其他语言一样,Kotlin 也有 while 循环、do...while 循环以及 ++-- 操作符。Kotlin 也有 repeat 循环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var bubbles = 0
while (bubbles < 50) {
bubbles++
}
println("$bubbles bubbles in the water\n")

do {
bubbles--
} while (bubbles > 50)
println("$bubbles bubbles in the water\n")

repeat(2) {
println("A fish is swimming")
}
1
2
3
50 bubbles in the water
49 bubbles in the water
A fish is swimmingA fish is swimming

6.总结

Kotlin 在基础的操作符、列表和循环等方面与其他编程语言很像,但也有一些重要的不同点。

下面这些 Kotlin 中的特性可能与你以前使用的语言不同:

  • Kotlin 的类型不能隐式转换。
  • 使用 val 声明的变量只能分配一次值。
  • Kotlin 变量默认是不可空的,使用 ? 来使变量可空。
  • 在 Kotlin 中,你可以在同一个 for 循环中遍历数组的索引和元素。

下面这些 Kotlin 中的结构与其他语言是一样的。

  • 数组和列表可以是单一类型或者混合类型。
  • 数组和列表可以嵌套。
  • 你可以使用 forwhiledo / whilerepeat 来创建循环。
  • when 语句是 Kotlin 版本的 switch 语句,不过 when 语句更加灵活。

7.了解更多

Kotlin 文档

如果你想获得更多关于这个课程的信息或者你有任何困难,https://kotlinlang.org/ 是你最好的起点。

Kotlin 教程

这个网站 有丰富的Kotlin教程,基于web的解释器和一套有示例的参考文档。

Udacity 课程

如果要在 Udacity 上浏览关于该主题的课程,请看 Kotlin Bootcamp for Programmers

IntelliJ IDEA

你可以在 JetBrains 的网站上找到有关 IntelliJ IDEA 的文档。

8.作业

这一节列举了在导员带领下参与代码实验室的同学们所需要做的家庭作业。这是导员需要做的:

  • 如果需要的话分配作业。
  • 与学生沟通如何提交作业。
  • 给作业评分。

教师可以根据自己的意愿来采用这些建议,并且可以随意布置任何和时的其他作业。

如果你是自己学习本课程,请随意使用这些作业来测试你的知识。

回答下列问题

问题1

以下哪个声明了一个不可变的字符串列表

  • val school = arrayOf(“shark”, “salmon”, “minnow”)
  • var school = arrayOf(“shark”, “salmon”, “minnow”)
  • val school = listOf(“shark”, “salmon”, “minnow”)
  • val school = mutableListOf(“shark”, “salmon”, “minnow”)

问题2

以下代码会输出什么结果? for (i in 3..8 step 2) print(i)

  • 345678
  • 468
  • 38
  • 357

问题3

下面这句代码中的问号是干啥的? var rocks: Int? = 3

  • 声明变量 rocks 的了类型不是固定的
  • 声明变量 rocks 可以为空
  • 声明变量 rocks 不能为空
  • 使变量 rocks 不能立即初始化



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