1. 概述

本文将深入对比 Kotlin 中的 Array<T>List<T>。我们会从多个维度分类讨论它们之间的差异,并逐一展开分析,帮助你在实际开发中做出更合理的选择。

踩坑提示:刚从 Java 转 Kotlin 的同学容易混淆两者在泛型、可变性上的行为,尤其数组协变 vs 泛型不变的问题,务必小心!

2. Array vs List

2.1 数据结构

Array<T> 是 JVM 原生数组的直接映射
Kotlin 编译器会把 Array<T> 直接编译成 JVM 的数组类型(JVM array),因此它的大小在创建时就固定了,无法动态扩容。

例如:

val cities = arrayOf("Tehran", "Sari", "Neka")

通过 javap 查看字节码,结果如下:

0: iconst_3
1: anewarray     #8     // class java/lang/String
  • iconst_3 将整数 3 推入操作栈;
  • anewarray 根据栈顶值创建指定长度的对象数组。

⚠️ 这说明 Kotlin 数组底层就是标准 JVM 数组,性能高且受 JVM 特殊优化。

List<T>MutableList<T> 是接口,有多种实现方式:

  • ArrayList<T>:基于动态数组实现,支持自动扩容,随机访问快(O(1)),但插入/删除慢(O(n))。
  • LinkedList<T>:基于链表实现,插入删除快(O(1)),但随机访问慢(O(n))。

👉 因此,List 的内存布局和运行时特性取决于具体实现类,不像 Array 那样统一。

2.2 可变性(Mutability)

Array 天然是可变的:你可以修改任意位置的元素:

cities[0] = "Berlin" // 合法

但 ❌ 不能添加或删除元素,因为长度固定。

List<T> 接口是只读的:它不提供任何修改方法(如 add/remove)。哪怕你用 listOf() 创建的列表,也不能增删改。

如果需要可变操作,必须使用 MutableList<T>

val colors = mutableListOf("Blue") // [Blue]
colors[0] = "Green"        // 替换: [Green]
colors.add(0, "Red")       // 插入头部: [Red, Green]
colors.add("Blue")         // 尾部追加: [Red, Green, Blue]

📌 总结: | 类型 | 是否可变 | 是否可变长 | |------------------|----------|-----------| | Array<T> | ✅ | ❌ | | List<T> | ❌ | ❌ | | MutableList<T> | ✅ | ✅ |

2.3 泛型协变性(Generic Variance)

这是最容易踩坑的部分之一,尤其对熟悉 Java 的开发者。

List<T> 是协变的(covariant),因为它声明为 interface List<out T>。这意味着子类型关系被保留:

val colors = listOf("Red")           // List<String>
val colorsAsAny: List<Any> = colors  // ✅ 编译通过

解释:由于 StringAny 的子类,而 List<out T> 允许协变,所以 List<String> 可以赋值给 List<Any>

MutableList<T> 是不变的(invariant),因为它的泛型既出现在输入也出现在输出位置(比如 add(T)get()):

val colors = mutableListOf("Red")              // MutableList<String>
val colorsAsAny: MutableList<Any> = colors     // ❌ 编译失败!

同理,Array<T> 也是 invariant:

val colors: Array<Any> = arrayOf<String>("Red") // ❌ 编译错误

💡 对比 Java:

  • Java 中泛型是 invariant,但数组是 covariant(会导致运行时 ArrayStoreException);
  • Kotlin 更安全:泛型默认 invariant,只读集合允许协变,可变集合一律不变。

2.4 原始类型特化(Specialized Primitives)

✅ Kotlin 提供了针对基本类型的专用数组构造函数,避免装箱开销:

val bytes = byteArrayOf(42)
val shorts = shortArrayOf(42)
val ints = intArrayOf(42)
val longs = longArrayOf(42)
val floats = floatArrayOf(42.0f)
val doubles = doubleArrayOf(42.0)
val chars = charArrayOf('a')
val booleans = booleanArrayOf(true)

这些数组会被编译成 JVM 的 primitive array(如 [I 表示 int[]),使用专门的字节码指令:

0: iconst_1
1: newarray  byte

这里的 newarray 指令专用于原始类型数组,比 anewarray 更高效。

❌ 而 List 没有这种优化 —— 即使你写 listOf(1, 2, 3),存储的仍是 Integer 对象,存在装箱/拆箱开销。

📌 场景建议:

  • 高频数值计算、大数据量场景 → 优先用 IntArray 等原始数组;
  • 普通业务逻辑 → 用 List<Int> 完全没问题。

2.5 其他细微差异

Java 互操作性(Interop)

Kotlin 对部分 Java 类型做了“映射”处理:

  • java.util.Listkotlin.collections.List
  • T[]kotlin.Array<T>

详见官方文档:Java 互操作 - 映射类型

⚠️ 注意:数组的映射规则略有不同,请参考 Java 数组互操作

相等性比较(Equality)

一个非常隐蔽但重要的区别:

println(intArrayOf(1) == intArrayOf(1)) // false(引用比较)
println(listOf(1) == listOf(1))         // true(内容比较)
  • Array<T> 使用 引用相等(即 == 等价于 ===
  • List<T> 使用 结构相等(逐个元素比较)

📌 实际影响:如果你把数组作为 Map 的 key 或 Set 的元素,很可能出问题,除非你自己包装或使用 contentEquals()

3. 总结

维度 Array List / MutableList
底层结构 JVM 原生数组 接口,多种实现(ArrayList、LinkedList)
大小是否可变 ❌ 固定长度 ✅ MutableList 可变
是否可修改元素 ❌ List 不可变,MutableList 可变
泛型协变性 ❌ 不变(invariant) ✅ List 协变(covariant)
原始类型优化 ✅ 有 byteArrayOf/intArrayOf 等 ❌ 无,只能装箱
相等性比较 引用比较 内容比较
Java 互操作 直接对应 T[] 对应 java.util.List

最佳实践建议

  • 需要高性能、大量原始数据 → 用 IntArray 等原始数组
  • 通用集合操作、函数式编程 → 优先用 List<T>(不可变更安全)
  • 需要频繁增删 → 用 MutableList<T> + ArrayList 实现
  • 别拿数组当 key!要用内容比较时记得手动处理

最后提醒一句:不要被语法糖迷惑,arrayOf()listOf() 看似相似,底层机制完全不同。理解清楚才能写出高效又安全的 Kotlin 代码。


原始标题:Difference Between List and Array in Kotlin