MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin集合框架概览

2022-03-315.7k 阅读

Kotlin集合框架基础

在 Kotlin 中,集合框架是处理一组对象的强大工具集。集合可分为可变集合和不可变集合,这种区分在保证数据安全和灵活性方面起着关键作用。

不可变集合

不可变集合一旦创建,其内容就不能被修改。这对于确保数据的一致性和安全性非常重要,尤其是在多线程环境中。例如,创建一个不可变的列表:

val immutableList = listOf(1, 2, 3)
// 尝试修改会报错
// immutableList.add(4) 

这里使用 listOf 函数创建了一个包含 1、2、3 的不可变列表。如果尝试对其进行修改操作,如添加元素,编译器会报错。

不可变集合还有 setOfmapOf 函数,分别用于创建不可变的集合和映射:

val immutableSet = setOf(1, 2, 3)
val immutableMap = mapOf("one" to 1, "two" to 2)

可变集合

可变集合允许在创建后修改其内容。例如,使用 mutableListOf 创建一个可变列表:

val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4)
println(mutableList) 

上述代码创建了一个可变列表,并成功添加了元素 4。同样,也有 mutableSetOfmutableMapOf 用于创建可变的集合和映射:

val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
val mutableMap = mutableMapOf("one" to 1, "two" to 2)
mutableMap.put("three", 3)

集合接口与实现

Kotlin 的集合框架基于一系列接口和具体实现类。理解这些接口和实现之间的关系,对于高效使用集合至关重要。

集合接口层次结构

  • Collection:这是所有集合的根接口,定义了基本的集合操作,如 sizeisEmptycontains 等。
  • List:继承自 Collection,额外定义了通过索引访问元素的操作,如 getset 等。列表是有序的,允许重复元素。
  • Set:继承自 Collection,集合中的元素是唯一的,不允许重复。
  • Map:与上述接口不同,它存储键值对,一个键最多映射到一个值。

常见实现类

  • ArrayList:这是 List 接口的可变实现,基于数组实现。它支持快速的随机访问,但在列表中间插入和删除元素的效率较低。
val arrayList = ArrayList<Int>()
arrayList.add(1)
arrayList.add(2)
  • LinkedList:也是 List 接口的可变实现,基于链表实现。它在列表中间插入和删除元素的效率较高,但随机访问的效率低于 ArrayList
val linkedList = LinkedList<Int>()
linkedList.add(1)
linkedList.add(2)
  • HashSetSet 接口的可变实现,基于哈希表实现。它提供快速的添加、删除和包含检查操作,但不保证元素的顺序。
val hashSet = HashSet<Int>()
hashSet.add(1)
hashSet.add(2)
  • TreeSet:同样是 Set 接口的可变实现,基于红黑树实现。它保证元素按自然顺序或自定义顺序排序。
val treeSet = TreeSet<Int>()
treeSet.add(3)
treeSet.add(1)
treeSet.add(2)
println(treeSet) 
  • HashMapMap 接口的可变实现,基于哈希表实现。它提供快速的键值对查找和插入操作。
val hashMap = HashMap<String, Int>()
hashMap.put("one", 1)
hashMap.put("two", 2)
  • TreeMapMap 接口的可变实现,基于红黑树实现。它保证键按自然顺序或自定义顺序排序。
val treeMap = TreeMap<String, Int>()
treeMap.put("two", 2)
treeMap.put("one", 1)
println(treeMap) 

集合操作

Kotlin 的集合框架提供了丰富的操作方法,可分为只读操作、过滤操作、映射操作、折叠操作等。

只读操作

  • 获取元素:对于列表,可以通过索引获取元素。例如:
val list = listOf(1, 2, 3)
val element = list[1]
println(element) 

对于映射,可以通过键获取值:

val map = mapOf("one" to 1, "two" to 2)
val value = map["one"]
println(value) 
  • 检查元素contains 方法用于检查集合中是否包含某个元素。
val set = setOf(1, 2, 3)
val containsElement = set.contains(2)
println(containsElement) 

过滤操作

  • filter:返回满足指定条件的元素组成的新集合。例如,过滤出列表中的偶数:
val numbers = listOf(1, 2, 3, 4)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) 
  • filterNot:与 filter 相反,返回不满足指定条件的元素组成的新集合。
val oddNumbers = numbers.filterNot { it % 2 == 0 }
println(oddNumbers) 

映射操作

  • map:对集合中的每个元素应用一个函数,并返回结果组成的新集合。例如,将列表中的每个元素乘以 2:
val doubledNumbers = numbers.map { it * 2 }
println(doubledNumbers) 
  • flatMap:先对集合中的每个元素应用一个函数,该函数返回一个集合,然后将这些集合扁平化为一个单一的集合。
val nestedLists = listOf(listOf(1, 2), listOf(3, 4))
val flatList = nestedLists.flatMap { it }
println(flatList) 

折叠操作

  • fold:从一个初始值开始,对集合中的每个元素应用一个二元操作符,将所有元素折叠成一个单一的值。例如,计算列表元素的总和:
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum) 

这里 0 是初始值,acc 是累加器,num 是集合中的每个元素。

  • reduce:与 fold 类似,但没有初始值,直接从集合的第一个元素开始。例如:
val product = numbers.reduce { acc, num -> acc * num }
println(product) 

序列(Sequence)

序列是 Kotlin 集合框架中的一个重要概念,它提供了一种惰性求值的方式来处理集合。

序列的特点

  • 惰性求值:序列的操作不会立即执行,而是等到结果需要时才进行计算。这对于处理大型数据集非常有效,可以减少内存的使用。例如:
val numbersSequence = sequence {
    for (i in 1..1000000) {
        yield(i)
    }
}
val sumOfSquares = numbersSequence
   .map { it * it }
   .filter { it % 2 == 0 }
   .sum()
println(sumOfSquares) 

在这个例子中,mapfilter 操作不会立即执行,直到调用 sum 时才会开始计算,这样可以避免一次性处理大量数据。

  • 链式操作:序列支持链式调用多个操作,使得代码更加简洁和易读。

与集合的区别

集合的操作是立即执行的,而序列是惰性的。例如,创建一个包含大量元素的列表并对其进行操作:

val largeList = (1..1000000).toList()
val sumOfSquaresFromList = largeList
   .map { it * it }
   .filter { it % 2 == 0 }
   .sum()
println(sumOfSquaresFromList) 

这里 mapfilter 操作会立即创建新的列表,占用大量内存。而序列在这方面则更具优势,尤其在处理大数据时。

集合的并发访问

在多线程环境中,需要注意集合的并发访问问题,以避免数据竞争和不一致性。

线程安全的集合

Kotlin 提供了一些线程安全的集合实现,如 ConcurrentHashMap(对应 Java 中的 ConcurrentHashMap)。使用线程安全的集合可以确保在多线程环境下的安全访问。例如:

import java.util.concurrent.ConcurrentHashMap

val concurrentMap = ConcurrentHashMap<String, Int>()
concurrentMap.put("one", 1)

同步访问

除了使用线程安全的集合,还可以通过同步块来确保对集合的安全访问。例如,对一个普通的可变列表进行同步访问:

val mutableList = mutableListOf(1, 2, 3)
val lock = Any()
thread {
    synchronized(lock) {
        mutableList.add(4)
    }
}
thread {
    synchronized(lock) {
        println(mutableList) 
    }
}

这里通过 synchronized 块,使用同一个锁对象 lock 来确保对 mutableList 的访问是线程安全的。

自定义集合

在某些情况下,需要创建自定义的集合。Kotlin 提供了扩展集合接口的能力,以便实现自定义的集合行为。

扩展集合接口

可以通过实现集合接口的方法来创建自定义集合。例如,创建一个自定义的列表,只允许添加偶数:

class EvenOnlyList : MutableList<Int> {
    private val backingList = mutableListOf<Int>()

    override val size: Int
        get() = backingList.size

    override fun isEmpty(): Boolean = backingList.isEmpty()

    override fun contains(element: Int): Boolean = backingList.contains(element)

    override fun iterator(): MutableIterator<Int> = backingList.iterator()

    override fun containsAll(elements: Collection<Int>): Boolean = backingList.containsAll(elements)

    override fun add(element: Int): Boolean {
        if (element % 2 == 0) {
            return backingList.add(element)
        }
        return false
    }

    override fun addAll(elements: Collection<Int>): Boolean {
        var result = true
        for (element in elements) {
            if (element % 2 != 0) {
                result = false
                break
            }
        }
        if (result) {
            return backingList.addAll(elements)
        }
        return false
    }

    override fun clear() = backingList.clear()

    override fun get(index: Int): Int = backingList[index]

    override fun indexOf(element: Int): Int = backingList.indexOf(element)

    override fun lastIndexOf(element: Int): Int = backingList.lastIndexOf(element)

    override fun listIterator(): MutableListIterator<Int> = backingList.listIterator()

    override fun listIterator(index: Int): MutableListIterator<Int> = backingList.listIterator(index)

    override fun removeAt(index: Int): Int = backingList.removeAt(index)

    override fun set(index: Int, element: Int): Int {
        if (element % 2 == 0) {
            return backingList.set(index, element)
        }
        throw IllegalArgumentException("Element must be even")
    }

    override fun subList(fromIndex: Int, toIndex: Int): MutableList<Int> = backingList.subList(fromIndex, toIndex)

    override fun remove(element: Int): Boolean = backingList.remove(element)

    override fun removeAll(elements: Collection<Int>): Boolean = backingList.removeAll(elements)

    override fun retainAll(elements: Collection<Int>): Boolean = backingList.retainAll(elements)
}

然后可以使用这个自定义的列表:

val evenList = EvenOnlyList()
evenList.add(2)
// 下面这行代码会失败,因为 3 不是偶数
// evenList.add(3) 

通过这种方式,可以根据具体需求定制集合的行为,满足特殊的业务逻辑。

综上所述,Kotlin 的集合框架提供了丰富的功能和灵活性,从基础的集合创建到复杂的操作,从线程安全到自定义集合,都能满足开发者在不同场景下的需求。深入理解和熟练运用这些特性,对于编写高效、健壮的 Kotlin 程序至关重要。无论是处理小型数据集还是大数据量,Kotlin 的集合框架都能提供合适的解决方案。同时,结合序列的惰性求值和多线程安全的处理,能够进一步提升程序的性能和稳定性。开发者在实际应用中,可以根据具体需求选择合适的集合类型和操作方式,充分发挥 Kotlin 集合框架的强大功能。