【Kotlin】Kotlin中的集合

【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:先对每个元素进行转换,然后将结果扁平化为一个新的集合。
  • reducefold:对集合中的元素进行累积操作。
  • forEach:遍历集合中的每个元素。
  • anyall:判断集合中是否存在或所有元素满足某个条件。
  • findfirst:查找符合条件的元素。

示例:

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 的集合体系无缝集成。