列表
List是一个容器,即一个可以容纳其他对象的对象。
容器也称为集合。在本书的示例中,当我们需要一个基本的容器时,通常会使用 List。
List 是 Kotlin 标准库的一部分,因此不需要进行 import。
以下示例通过调用标准库函数 listOf() 并提供初始化值,创建了一个包含 Int 的 List:
// Lists/Lists.kt
import atomictest.eq
fun main() {
val ints = listOf(99, 3, 5, 7, 11, 13)
ints eq "[99, 3, 5, 7, 11, 13]" // [1]
// 遍历 List 中的每个元素:
var result = ""
for (i in ints) { // [2]
result += "$i "
}
result eq "99 3 5 7 11 13"
// 对 List 进行 "索引":
ints[4] eq 11 // [3]
}
- [1]
List在显示自己时使用方括号。 - [2]
for循环与List配合使用很好:for(i in ints)表示i会依次接收ints中的每个值。您无需声明val i或为其指定类型;Kotlin 从上下文中知道i是for循环的标识符。 - [3] 方括号 索引 到
List中。List保持其元素的初始化顺序,并且您可以通过数字逐个选择它们。与大多数编程语言一样,Kotlin 从元素零开始索引,本例中产生值99。因此,索引为4产生值11。
忘记索引从零开始会产生所谓的差一错误。在像 Kotlin 这样的语言中,我们通常不会逐个选择元素,而是通过 in 迭代整个容器。这消除了差一错误。
如果在 List 中使用超出最后一个元素的索引,Kotlin 会抛出 ArrayIndexOutOfBoundsException:
// Lists/OutOfBounds.kt
import atomictest.*
fun main() {
val ints = listOf(1, 2, 3)
capture {
ints[3]
} contains
listOf("ArrayIndexOutOfBoundsException")
}
List 可以容纳所有不同类型的元素。这里有一个 Double 的 List 和一个 String 的 List:
// Lists/ListUsefulFunction.kt
import atomictest.eq
fun main() {
val doubles =
listOf(1.1, 2.2, 3.3, 4.4)
doubles.sum() eq 11.0
val strings = listOf("Twas", "Brillig",
"And", "Slithy", "Toves")
strings eq listOf("Twas", "Brillig",
"And", "Slithy", "Toves")
strings.sorted() eq listOf("And",
"Brillig", "Slithy", "Toves", "Twas")
strings.reversed() eq listOf("Toves",
"Slithy", "And", "Brillig", "Twas")
strings.first() eq "Twas"
strings.takeLast(2) eq
listOf("Slithy", "Toves")
}
这显示了 List 的一些操作。注意名称“sorted”而不是“sort”。当您调用 sorted() 时,它会生成一个包含相同元素的新 List,按排序顺序排列,但不会改变原始 List。将其命名为“sort”意味着直接更改原始 List(也称为原地排序)。在 Kotlin 中的整个过程中,您会看到“保留原始对象并生成新对象”的倾向。reversed() 也会生成一个新的 List。
参数化类型
我们认为使用类型推断是一种良好的实践——它倾向于使代码更清晰,更易于阅读。但是,有时 Kotlin 抱怨无法确定要使用的类型,在其他情况下,明确性会使代码更易于理解。以下是我们如何告诉 Kotlin List 中包含的类型:
// Lists/ParameterizedTypes.kt
import atomictest.eq
fun main() {
// 类型被推断:
val numbers = listOf(1, 2, 3)
val strings =
listOf("one", "two", "three")
// 完全相同,但是显式指定类型:
val numbers2: List<Int> = listOf(1, 2, 3)
val strings2: List<String> =
listOf("one", "two", "three")
numbers eq numbers2
strings eq strings2
}
Kotlin 使用初始化值推断 numbers 包含 Int 的 List,而 strings 包含 String 的 List。
numbers2 和 strings2 是 numbers 和 strings 的显式类型版本,通过添加类型声明 List<Int> 和 List<String> 创建。您之前还没有看到过尖括号——它们表示类型参数,允许您说,“这个容器包含‘参数’对象”。我们将 List<Int> 读作Int 类型的 List”。
类型参数不仅对于容器很有用,对于其他组件也很有用,但是您经常在类似容器的对象中看到它们。
返回值也可以具有类型参数:
// Lists/ParameterizedReturn.kt
package lists
import atomictest.eq
// 返回类型被推断:
fun inferred(p: Char, q: Char) =
listOf(p, q)
// 显式返回类型:
fun explicit(p: Char, q: Char): List<Char> =
listOf(p, q)
fun main() {
inferred('a', 'b')
eq "[a, b]"
explicit('y', 'z') eq "[y, z]"
}
Kotlin 为 inferred() 推断返回类型,而 explicit() 指定函数返回类型。您不能只说它返回一个 List;Kotlin 会抱怨,因此您必须同时提供类型参数。在指定函数的返回类型时,Kotlin 强制执行您的意图。
只读和可变列表
如果您不明确表示要可变的 List,则不会得到一个。listOf() 生成一个不具有变异函数的只读 List。
如果您正在逐步创建 List(即在创建时不具有所有元素),请使用 mutableListOf()。这会生成一个可以修改的 MutableList:
// Lists/MutableList.kt
import atomictest.eq
fun main() {
val list = mutableListOf<Int>()
list.add(1)
list.addAll(listOf(2, 3))
list += 4
list += listOf(5, 6)
list eq listOf(1, 2, 3, 4, 5, 6)
}
您可以使用 add() 和 addAll() 将元素添加到 MutableList,或使用快捷方式 +=,它会添加单个元素或另一个集合。由于 list 没有初始元素,因此我们必须通过在调用 mutableListOf() 中提供 <Int> 规范来告诉 Kotlin 它的类型。
MutableList 可以被视为 List,在这种情况下它无法被修改。但是,您不能将只读 List 视为 MutableList:
// Lists/MutListIsList.kt
package lists
import atomictest.eq
fun getList(): List<Int> {
return mutableListOf(1, 2, 3)
}
fun main() {
// getList() 生成一个只读 List:
val list = getList()
// list += 3 // Error
list eq listOf(1, 2, 3)
}
请注意,尽管 list 是在 getList() 内部使用 mutableListOf() 创建的可变对象的不可变引用(val),但在 return 期间,结果类型变为 List<Int>。原始对象仍然是 MutableList,但是它通过 List 的视角来查看。
List 是只读的 — 您可以读取其内容但不能写入。如果底层实现是一个 MutableList 并且您保留了对该实现的可变引用,您仍然可以通过该可变引用修改它,并且任何只读引用都将看到这些更改。这是别名的另一个示例,介绍在限制可见性中:
// Lists/MultipleListRefs.kt
import atomictest.eq
fun main() {
val first = mutableListOf(1)
val second: List<Int> = first
second eq listOf(1)
first += 2
// second 观察到了变化:
second eq listOf(1, 2)
}
first 是对 mutableListOf(1) 生成的可变对象的一个不可变引用(val)。然后将 second 别名设置为 first,因此它是对同一对象的视图。second 是只读的,因为 List<Int> 不包括修改函数。注意,如果没有显式的 List<Int> 类型声明,Kotlin 将推断 second 也是对可变对象的引用。
我们能够将元素(2)添加到对象中,因为 first 是对可变 List 的引用。请注意,second 观察到了这些更改 —— 它不能更改 List,尽管 List 通过 first 发生了更改。
练习和解答可以在 www.AtomicKotlin.com 找到。