5.4.7 数组

原文链接: https://kotlinlang.org/docs/arrays.html

5.4.7 数组

数组是一种数据结构,用来保存固定数量的、类型相同或为其子类型的值。数组元素是有序的,并通过索引访问。

Kotlin 提供了 Array<T> 类以及基本类型数组。

何时使用数组

在与 Java API 互操作或满足底层需求时使用数组。例如,你的性能要求超出普通应用所需,或者你需要构建自定义数据结构。

在大多数使用场景中,请优先使用集合。

| 功能 | 数组 | 集合 | | 大小 | 固定 | 取决于类型 | | 只读变体 | 没有,始终可变 | 有(List 和 Set) | | 添加和移除元素 | 没有原生支持。
需要分配新数组并复制 | 有(可变集合) | | 使用 == 进行结构相等性比较 | 不行,比较的是引用。
请改用 .contentEquals() | 可以 | | 基本类型值 | 基本类型数组直接存储值,
无需装箱 | 通常会被装箱 | | Java 互操作性 | 映射为 T[] | 映射为 java.util.List 和
java.util.Set | | 函数式风格的过滤与
转换 | 有限 | 非常丰富 |

了解如何把数组转换为集合。

创建数组

要创建数组,你可以使用:

注意: 用 val 声明数组只是阻止重新赋值该变量,并不会让内容变成只读。无论是 val 数组还是 var 数组,元素都是可变的。这一区别只影响引用,不影响内容。如果需要只读视图,请改用集合。

带值的数组

要从一组已知的值创建带类型的数组,请使用 arrayOf() 函数。Kotlin 会自动推断类型:

1
2
3
4
5
fun main() {
    val simpleArray = arrayOf(1, 2, 3) // Array<Int>
    println(simpleArray.joinToString())
    // 1, 2, 3
}

空数组

要创建不含任何元素的数组,请使用 emptyArray() 函数。你可以在赋值的左侧或右侧指定元素类型:

1
2
val emptyArrayRight = emptyArray<String>()
val emptyArrayLeft: Array<String> = emptyArray()

了解如何向数组添加元素。

带 null 的数组

要创建指定大小、全部填充 null 元素的数组,请使用 arrayOfNulls() 函数:

1
2
3
4
5
fun main() {
    val nullArray: Array<Int?> = arrayOfNulls(3)
    println(nullArray.joinToString())
    // null, null, null
}

数组构造器

Array 构造器接收数组大小,以及一个返回数组元素值的函数:

1
2
3
4
5
6
7
8
9
fun main() {
    val zeroes = Array<Int>(3) { 0 }
    println(zeroes.joinToString())
    // 0, 0, 0

    val squares = Array(5) { i -> i * i }
    println(squares.joinToString())
    // 0, 1, 4, 9, 16
}

嵌套数组

要创建嵌套数组或多维数组,请使用数组的数组。嵌套数组不必具有相同的类型或相同的大小。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun main() {
    // Creates a two-dimensional array
    val twoDArray = Array(2) { Array<Int>(2) { 0 } }
    println(twoDArray.contentDeepToString())
    // [[0, 0], [0, 0]]

    // Creates a three-dimensional array
    val threeDArray = Array(3) { Array(3) { Array<Int>(3) { 0 } } }
    println(threeDArray.contentDeepToString())
    // [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]
}

基本类型数组

如果你把 Array 类用于基本类型值,编译器会把这些值装箱成对象。要避免装箱开销,你可以使用专门的基本类型数组。它们不是 Array<T> 类的子类,但提供了相似的函数和属性。

| Kotlin 类型 | 对应的 Java 类型 | | BooleanArray | boolean[] | | ByteArray | byte[] | | CharArray | char[] | | DoubleArray | double[] | | FloatArray | float[] | | IntArray | int[] | | LongArray | long[] | | ShortArray | short[] |

注意: Kotlin 没有专门的 StringArray 类型。String 不是基本类型,因此请改用推断类型的 arrayOf() 或 arrayOf<String>() 函数。

要创建基本类型数组,请使用以下方式之一:

  • 构造器函数:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  fun main() {
      // Creates an Int array of size 5 with the values initialized to zero
      val primitiveTypeArray = IntArray(5)
      println(primitiveTypeArray.joinToString())
      // 0, 0, 0, 0, 0

      // Creates an Int array and takes an initializer function
      val squares = IntArray(5) { i -> i * i }
      println(squares.joinToString())
      // 0, 1, 4, 9, 16
  }
  • 工厂函数:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  fun main() {
      // Creates an Int array with 5 elements
      val numbers = intArrayOf(1, 2, 3, 4, 5)
      println(numbers.joinToString())
      // 1, 2, 3, 4, 5

      // Creates a Char array with 3 elements
      val characters = charArrayOf('K', 't', 'l')
      println(characters.joinToString())
      // K, t, l

      // Creates a Double array with 3 elements
      val doubles = doubleArrayOf(0.22, 4.16, 0.5)
      println(doubles.joinToString())
      // 0.22, 4.16, 0.5
  }

注意: 要把基本类型数组转换为对象类型数组,请使用 .toTypedArray() 函数。要把对象类型数组转换为基本类型数组,请使用 .toBooleanArray()、.toByteArray()、.toCharArray() 等等。

使用数组

数组支持与集合相同的许多操作,包括遍历、查找、排序和转换。在 Kotlin 中,你既可以把数组用于向函数传入数量可变的实参,也可以对数组本身执行操作。下表列出了最常用的属性和函数:

| 成员 | 返回 | | size | 元素数量 | | indices | 有效索引的区间 | | lastIndex | 最后一个有效索引 | | first() 和 last() | 第一个和最后一个元素 | | isEmpty() 和 isNotEmpty() | 数组为空或非空时返回 true | | contains() | 数组包含该元素时返回 true |

提示: 关于数组属性和函数的更多内容,请参阅 API 参考。

本节介绍其中最常用的一些操作。

访问和修改元素

要访问和修改数组中的元素,请使用索引访问运算符([]):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fun main() {
    val simpleArray = arrayOf(1, 2, 3)
    val twoDArray = Array(2) { Array<Int>(2) { 0 } }

    // Accesses the element and modifies it
    simpleArray[0] = 10
    twoDArray[0][0] = 2

    // Prints the modified element
    println(simpleArray[0])
    // 10
    println(twoDArray[0][0])
    // 2
}

注意: 如果你尝试访问超出数组边界的索引,Kotlin 会在运行期抛出 ArrayIndexOutOfBoundsException。

你也可以用 fill(element, fromIndex, toIndex) 函数就地替换某个范围内的元素。fromIndex 是包含的,toIndex 是不包含的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun main() {
    val arr = IntArray(3)
    println(arr.joinToString())
    // 0, 0, 0

   arr.fill(1)
   println(arr.joinToString())
   // 1, 1, 1

   arr.fill(0, 0, 2)
   println(arr.joinToString())
   // 0, 0, 1
}

在 Kotlin 中,数组是不变的。这意味着 Array<String> 不是 Array<Any> 的子类型。这能防止可能出现的运行期类型错误。要表达协变,请使用 Array<out Any> 类型投影:

1
2
3
4
5
6
7
8
fun main() {
    fun printArr(arr: Array<out Any>) {
        for (item in arr) print("$item, ")
    }

    printArr(arrayOf("k", "t", "n"))
    // k, t, n,
}

添加和移除元素

由于数组大小固定,它们不支持 .add() 和 .remove() 函数。要执行这些操作,你需要创建一个新数组。为此,你可以使用以下方式之一:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  fun main() {
      var arr = intArrayOf(0, 1, 2)

      arr = arr.copyOf(arr.size + 1)
      println(arr.joinToString())
      // 0, 1, 2, 0

      arr[arr.lastIndex] = 3
      println(arr.joinToString())
      // 0, 1, 2, 3
  }
  • 使用 + 或 += 运算符:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  fun main() {
      var arr = intArrayOf(0, 1, 2)

      arr += 3
      println(arr.joinToString())
      // 0, 1, 2, 3

      arr = arr + intArrayOf(4, 5)
      println(arr.joinToString())
      // 0, 1, 2, 3, 4, 5
  }

提示: 如果你需要频繁添加或移除元素,请改用可变集合。

比较数组

要比较两个数组是否以相同顺序包含相同元素,请使用 .contentEquals() 和 .contentDeepEquals() 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fun main() {
    val simpleArray = arrayOf(1, 2, 3)
    val anotherArray = arrayOf(1, 2, 3)

    // Compares contents of arrays
    println(simpleArray.contentEquals(anotherArray))
    // true

    // Using infix notation, compares contents of arrays after an element
    // is changed
    simpleArray[0] = 10
    println(simpleArray contentEquals anotherArray)
    // false
}

警告: 不要用相等(==)和不相等(!=)运算符比较数组的内容。这些运算符检查的是被赋值的变量是否指向同一个对象。想了解 Kotlin 中数组为何如此表现,请参阅我们的博客文章。

转换数组

Kotlin 提供了许多用于转换数组的实用函数。本节重点介绍其中一部分,完整列表请参阅我们的 API 参考。

求和

要返回数组中所有元素的和,请使用 .sum() 函数:

1
2
3
4
5
fun main() {
    val sumArray = arrayOf(1, 2, 3)
    println(sumArray.sum())
    // 6
}

注意: .sum() 函数只能用于数值数据类型的数组,例如 Int。

排序与打乱

你可以用 .sort() 函数按自然顺序对数组元素排序,或用 .shuffle() 函数随机打乱它们:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fun main() {
    val simpleArray = arrayOf(1, 2, 3)

    // Randomly shuffles elements
    simpleArray.shuffle()
    println(simpleArray.joinToString())

    // Sorts elements
    simpleArray.sort()
    println(simpleArray.joinToString())
    // 1, 2, 3
}

要获得一个排序后的新数组而不修改原数组,请改用 .sortedArray() 函数。

向函数传入数量可变的实参

在 Kotlin 中,你可以通过 vararg 参数向函数传入数量可变的实参。当你事先不知道实参数量时,这会很有用,例如格式化一条消息或构造一条 SQL 查询时。

要把包含数量可变实参的数组传给函数,请使用展开运算符(*)。展开运算符会把数组的每个元素作为单独实参传给所选函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun main() {
    val lettersArray = arrayOf("c", "d")
    printAllStrings("a", "b", *lettersArray)
    // abcd
}

fun printAllStrings(vararg strings: String) {
    for (string in strings) {
        print(string)
    }
}

更多信息请参阅数量可变的实参(varargs)。

转换为集合

如果你要在有的接口使用数组、有的接口使用集合之间协作,可以把数组转换为集合,反之亦然。为此请使用 .toList()、.toSet() 和 .toMap() 函数。这些函数会把数组中的内容复制到一份独立副本中,不会反映之后对数组所做的修改。

转换为 List 或 Set

要把数组转换为 List 或 Set,请使用 .toList() 和 .toSet() 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun main() {
    val simpleArray = arrayOf("a", "b", "c", "c")

    // Converts to a Set
    println(simpleArray.toSet())
    // [a, b, c]

    // Converts to a List
    println(simpleArray.toList())
    // [a, b, c, c]
}

除非你完全确定原数组不会被修改、也不会在其他地方共享,否则不要使用 .asList() 以及相关的 as* 函数。这些函数包装原数组而不是复制它。因此对数组的修改会反映到列表中,反之亦然。

1
2
3
4
5
6
7
8
fun main() {
    val simpleArray = arrayOf("a", "b", "c")

    val list = simpleArray.asList()
    simpleArray[0] = "d"
    println(list)
    // [d, b, c]
}

转换为 Map

要把数组转换为 Map,请使用 .toMap() 函数。

你只能把 Pair<K,V> 的数组转换为 Map。Pair 实例的第一个值成为键,第二个值成为值。如果同一个键出现多次,则使用最后一个值。

这个示例使用中缀表示法调用 .to 函数来创建 Pair 元组:

1
2
3
4
5
6
7
8
9
fun main() {
    val pairArray = arrayOf("apple" to 120, "banana" to 150, "cherry" to 90, "apple" to 140)

    // Converts to a Map
    // Fruits are keys, calorie numbers are values
    // The latest "apple" value overwrites the first one
    println(pairArray.toMap())
    // {apple=140, banana=150, cherry=90}
}

接下来做什么?