【Kotlin】Kotlin中的集合

本文介绍了Kotlin中集合的相关,并列举一下其使用方式
在 Kotlin 中,集合类(Collections)是非常重要的一部分,它提供了丰富且功能强大的 API 来操作数据集合。Kotlin 的集合类在很大程度上与 Java 的集合框架兼容,但 Kotlin 在其基础上进行了扩展和增强,提供了更简洁、更安全、更富表达力的 API。
一个重要的概念是,Kotlin 明确区分了只读 (read-only) 集合和可变 (mutable) 集合。
- 只读集合接口: 它们只提供读取数据的方法,不能添加、删除或修改元素。例如:
List<T>、Set<T>、Map<K, V>。 - 可变集合接口: 它们在只读接口的基础上,提供了修改集合的方法。例如:
MutableList<T>、MutableSet<T>、MutableMap<K, V>。
Kotlin 的集合类主要分为三大类:列表 (List)、集合 (Set) 和 映射 (Map)。这些集合类都继承自 kotlin.collections 包中的接口。
List<T>
有序集合,保持元素的插入顺序,其内部的元素可以重复。
- 只读列表:通常通过 listOf() 或 mutableListOf().toList() 创建。
- 可变列表:通常通过 mutableListOf() 创建。除了 List 的功能外,还支持添加、删除、更新元素。
除了上面两种创建方式,还可以使用 arrayListOf() 返回一个 ArrayList ,其实就是Java 的 ArrayList。
Set<T>
Set是一种无序的集合,不包含重复的元素。
- 只读集合 通常通过
setOf()或mutableSetOf().toSet()创建。不保证元素的顺序,不允许有重复元素。 - 可变集合 通常通过
mutableSetOf()创建。支持添加、删除元素。
其他的创建方式还有 hashSetOf(),返回一个 HashSet (Java 的 HashSet)。
linkedSetOf(): 返回一个 LinkedHashSet (Java 的 LinkedHashSet)。
Map<K, V>
映射 (也称为字典或关联数组) 存储键值对,其中每个键都是唯一的,并且映射到一个值。
Map<K, V>(只读): 通常通过mapOf()或mutableMapOf().toMap()创建。用来存储键值对,键是唯一的,不保证元素的顺序 (除非使用特定实现如LinkedHashMap)。MutableMap<K, V>(可变): 通常通过mutableMapOf()创建。支持添加、删除、更新键值对。
还可以使用 hashMapOf() 创建一个 HashMap (Java 的 HashMap)。
linkedMapOf(): 返回一个 LinkedHashMap (Java 的 LinkedHashMap)。
Kotlin 集合与 Java 集合对比
Kotlin 的集合在很大程度上是基于 Java 集合框架的,但进行了优化和扩展,提供了更安全、更简洁的 API。
1. 只读与可变分离 (最主要区别)
Kotlin: 明确区分了只读接口 (List, Set, Map) 和可变接口 (MutableList, MutableSet, MutableMap)。
这在编译时强制执行了不变性,有助于避免运行时错误和并发问题。当你只需要读取集合时,声明为只读类型可以更好地表达意图,并防止意外修改。
val readOnlyList: List<String> = listOf("A", "B", "C")
// readOnlyList.add("D") // 编译错误!
val mutableList: MutableList<String> = mutableListOf("X", "Y", "Z")
mutableList.add("W") // 可以修改
而使用 Java的集合接口 (如 List, Set, Map) 本身就包含了修改方法。虽然可以通过 Collections.unmodifiableList() 等方法创建不可修改的视图,但这只是一个运行时检查,如果你仍然持有原始的可变集合引用,它仍然可以被修改。
List<String> javaList = new ArrayList<>(Arrays.asList("A", "B", "C"));
// javaList.add("D"); // 可以直接修改
List<String> unmodifiableJavaList = Collections.unmodifiableList(javaList);
// unmodifiableJavaList.add("E"); // 运行时抛出 UnsupportedOperationException
// 但是,如果修改 javaList,unmodifiableJavaList 也会随之改变
javaList.add("F"); // unmodifiableJavaList 现在也包含 "F"
在Java中也可以直接创建不可变集合,使用 Java 9 以后引入的 List.of(), Set.of(), Map.of() 方法。
List<Integer> numbers = Arrays.asList(1, 2, 3); // 返回一个固定大小的List
// 或者
List<Integer> numbersJava9 = List.of(1, 2, 3); // 不可变List
Map<String, Integer> users = new HashMap<>();
users.put("Alice", 30);
2. 可空性支持
Kotlin 的类型系统原生支持可空性。这意味着你可以明确指定集合是否可以包含 null 元素,以及集合本身是否可以为 null。
val nullableStrings: List<String?> = listOf("A", null, "B") // 列表中可以有 null
val nonNullableList: List<String> = listOf("C", "D") // 列表中不能有 null
// 如果一个List本身可能为null
var maybeList: List<Int>? = null
maybeList = listOf(1, 2)
而 Java 在语言层面没有原生支持可空性,null 是一种常见的运行时错误源 (NullPointerException)。通常通过 @Nullable 和 @NonNull 注解来提示,但这些只是编译器或工具的提示,不能像 Kotlin 那样在编译时强制执行。
3. 集合扩展函数
Kotlin提供了大量的 扩展函数 (Extension Functions) 来操作集合,这使得集合操作变得非常简洁和富有表现力。例如:filter, map, forEach, firstOrNull, count, groupBy 等。这些函数通常链式调用,形成了非常强大的函数式编程风格。
val nums = listOf(1, 2, 3, 4, 5)
val evenSquared = nums.filter { it % 2 == 0 }.map { it * it } // 过滤偶数并平方
println(evenSquared) // 输出: [4, 16]
以下是一些常用的扩展函数:
map:对集合中的每个元素进行转换,返回新的集合。filter:过滤出符合条件的元素,返回新的集合。flatMap:先对每个元素进行转换,然后将结果扁平化为一个新的集合。reduce和fold:对集合中的元素进行累积操作。forEach:遍历集合中的每个元素。any和all:判断集合中是否存在或所有元素满足某个条件。find和first:查找符合条件的元素。
示例:
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10]
val even = numbers.filter { it % 2 == 0 } // [2, 4]
val sum = numbers.reduce { acc, i -> acc + i } // 15
这些扩展函数让 Kotlin 的集合操作更加直观和简洁,提高了开发效率。
4. 与 Java 集合的互操作性
Kotlin 集合与 Java 集合是完全兼容的,并且可以无缝互操作。
- 在 Kotlin 代码中,你可以直接使用 Java 的
ArrayList,HashSet,HashMap等。当你在 Kotlin 中使用这些 Java 集合时,它们会自动被视为可变集合。 - 当 Kotlin 的只读集合传递给 Java 方法时,它们会被转换为相应的 Java 接口,但仍然是“只读视图”。修改这些视图会导致运行时异常,而修改原始 Kotlin 可变集合则会反映在 Java 视图中。
- 当你从 Java 方法接收集合时,Kotlin 会将其视为可变集合,但在 Kotlin 中你可以轻松地将其转换为只读视图(例如
someJavaList.toList())。
Kotlin 的 Sequence(序列)
Sequence 是 Kotlin 提供的一种惰性集合操作机制,类似于 Java 的 Stream API。它的主要特点是:
- 惰性计算:
Sequence中的操作不会立即执行,而是按需计算,只有在终端操作(如toList()、forEach())被调用时才会执行。 - 适合大数据集:由于是惰性计算,
Sequence在处理大量数据时更高效,因为它避免了创建中间集合。 - 链式操作:支持链式调用多个操作,代码更简洁。
示例:
val numbers = sequenceOf(1, 2, 3, 4, 5)
val result = numbers
.map { it * 2 } // 不会立即执行
.filter { it % 3 == 0 } // 不会立即执行
.toList() // 触发实际计算,返回 [6]
println(result) // 输出 [6]
与 Java 的 Stream 相比,Kotlin 的 Sequence 在语法上更简洁,且与 Kotlin 的集合体系无缝集成。