Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

列表

List 是一个容器,即一个可以容纳其他对象的对象。

容器也称为集合。在本书的示例中,当我们需要一个基本的容器时,通常会使用 List

List 是 Kotlin 标准库的一部分,因此不需要进行 import

以下示例通过调用标准库函数 listOf() 并提供初始化值,创建了一个包含 IntList

// 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 从上下文中知道 ifor 循环的标识符。
  • [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 可以容纳所有不同类型的元素。这里有一个 DoubleList 和一个 StringList

// 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 包含 IntList,而 strings 包含 StringList

numbers2strings2numbersstrings 的显式类型版本,通过添加类型声明 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 找到。