【Kotlin】数组集合常用扩展函数

大多数扩展对于数组和集合类都可以互相使用。这里大部分以 Array 为例。

更多详见 https://Kotlinlang.org/docs/collections-overview.html

数组与集合的转换

使用 Array 提供的集合快速转换函数转为集合

kotlin 复制代码
val array = arrayOf("AAA", "BBB", "CCC")
val list: List<String> = array.toList()
val list: MutableList<String> = array.toMutableList()
val set: Set<String> = array.toSet()
val set: MutableSet<String> = array.toMutableSet()

List、Set 转 Array

kotlin 复制代码
val list = listOf("Apple", "Banana", "Cherry")

// 方式 1:使用 toTypedArray()
val array1 = list.toTypedArray()

// 方式 2:使用 toArray()(较少用)
val array2 = list.toArray(arrayOfNulls<String>(list.size))

println(array1.joinToString()) // 输出:Apple, Banana, Cherry
kotlin 复制代码
val set = setOf(1, 2, 3, 4)

val array = set.toTypedArray()

println(array.joinToString()) // 输出:1, 2, 3, 4

如果是数字类型,可以转成特定的原始数组:

kotlin 复制代码
val intList = listOf(1, 2, 3)
val intArray = intList.toIntArray()   // 输出类型:IntArray

println(intArray.joinToString())      // 输出:1, 2, 3

associateWith 将集合中的每个元素作为 key ,通过指定的函数计算出对应的 value ,最终生成一个 Map<K, V>

kotlin 复制代码
val words = listOf("apple", "banana", "cherry")
val map = words.associateWith { it.length }
println(map)    // {apple=5, banana=6, cherry=6}

associateByassociateWith 相反,它通过指定的函数计算出 key ,而集合元素本身或通过另一个函数计算出的值作为 value

kotlin 复制代码
val words = listOf("apple", "banana", "cherry")
val map = words.associateBy { it.first() }
println(map)    // {a=apple, b=banana, c=cherry}

val map = words.associateBy { it.length }
println(map)    // {5=apple, 6=cherry}     key 相同,被覆盖了

带 valueTransform

kotlin 复制代码
val map = words.associateBy(
    keySelector = { it.first() },
    valueTransform = { it.length }
)
println(map)    // {a=5, b=6, c=6}

associate是最通用的形式,允许你完全控制 key-value 对的生成规则

kotlin 复制代码
val words = listOf("apple", "banana", "cherry")
val map = words.associate { it.first() to it.length }
println(map)    // {a=5, b=6, c=6}

zip压缩,把两个集合按元素顺序配对 ,生成由 Pair 组成的List

kotlin 复制代码
val names = arrayOf("Alice", "Bob", "Charlie")
val ages = arrayOf(20, 25, 30)

val result: List<Pair<String, Int>> = names.zip(ages)

println(result)  // [(Alice, 20), (Bob, 25), (Charlie, 30)]

// zip 把两个集合配成一个 List<Pair<K, V>> 再 .toMap() 把它转成 Map<K, V>
val map: Map<String, Int> = names.zip(ages).toMap()
println(map)  // {Alice=20, Bob=25, Charlie=30}


val names = listOf("apple", "banana", "cherry")
val prices = listOf(1.5, 2.0, 3.2)
val fruitPriceMap = names.zip(prices)
    .map { (name, price) -> name.uppercase() to "$${price}" }
    .toMap()
// 简化
// val fruitPriceMap = names.zip(prices).associate { (name, price) -> name.uppercase() to "$${price}" }
println(fruitPriceMap)  // {APPLE=$1.5, BANANA=$2.0, CHERRY=$3.2}

zip 还可以带一个 Lambda,用于直接合并生成新的类型

kotlin 复制代码
val names = arrayOf("Alice", "Bob", "Charlie")
val ages = arrayOf(20, 25, 30)

// 返回值不是 Pair List 了
val result: List<String> = names.zip(ages) { name, age -> "$name is $age years old" }
println(result)   // [Alice is 20 years old, Bob is 25 years old, Charlie is 30 years old]

unzip,从Pair List拆分回 2 个 List

kotlin 复制代码
val list: List<Pair<Int, String>> = listOf(1 to "A", 2 to "B", 3 to "C")
val unzipList: Pair<List<Int>, List<String>> = list.unzip()  // 转换出来是一个存放两个List的Pair
println(unzipList)     // ([1, 2, 3], [A, B, C])


val pairs = listOf(
    "Alice" to 20,
    "Bob" to 25,
    "Charlie" to 30
)
val (names, ages) = pairs.unzip()
println(names)  // [Alice, Bob, Charlie]
println(ages)   // [20, 25, 30]

joinToString 打印内容

如果只是想打印数组里面的内容,可以使用 joinToString

kotlin 复制代码
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
println(array.joinToString())  // 使用joinToString将数组中的元素转换为字符串,默认使用逗号隔开:7, 3, 9, 1, 6
println(array.joinToString(",", "[", "]"))  // 自定义分隔符,前后缀: [7,3,9,1,6]
println(array.joinToString(limit = 1, truncated = "..."))  // 甚至可以限制数量,多余的用自定义的字符串...代替: 7, ...
println(array.joinToString() { (it * it).toString() })   // 自定义每一个元素转换为字符串的结果

withIndex 与 IndexedValue 同时遍历索引和元素本身

同时遍历索引和元素本身,也可以使用 withIndex 函数,它会生成一系列 IndexedValue 对象。

使用 for in 遍历时,也可以对待遍历的元素进行【结构】操作,当然,前提是这些对象类型支持解构,比如 IndexedValue 就支持解构,所以可以在遍历时直接使用解构之后的变量进行操作:

kotlin 复制代码
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
for ((index, item) in array.withIndex()) {  // 使用withIndex解构后可以同时遍历索引和元素
    println("元素$item,位置: $index")
}

forEach、forEachIndexed 遍历每一个元素,并对每个元素执行指定的操作(通过 lambda 表达式)

注意无返回值,映射是使用 map。

kotlin 复制代码
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
array.forEach { println(it) }           // 只带元素
array.forEachIndexed { index, item ->   // 同时带索引和元素
    println("元素$item,位置: $index")
}

contentEquals、contentDeepEquals 比较数组内容是否相同

kotlin 复制代码
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val array2: Array<Int> = arrayOf(1, 2, 3, 4, 5)
println(array1 == array2)   // false
println(array1 === array2)  // false
/* 
== 会调用 .equals() 方法。对于 数组 (Array) 来说,它并没有重写 equals,继承的是 Any.equals() 的实现。而 Any.equals() 默认是引用相等(也就是和 === 一样)。
*/
println(array1.contentEquals(array2))   // 需要使用扩展函数contentEquals来进行内容比较

对于多维数组,由于数组内存放的是数组,在比较两个嵌套数组的内容是否相同时,需要使用深度比较 contentDeepEquals

kotlin 复制代码
fun main() {
    val arr1: Array<IntArray> = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4), intArrayOf(5, 6))
    val arr2: Array<IntArray> = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4), intArrayOf(5, 6))
    println(arr1.contentEquals(arr2))            // false
    println(arr1.contentDeepEquals(arr2))        // true
}

/*
contentEquals只做浅比较,遇到子数组时比较引用
contentDeepEquals做深比较,递归比较子数组的内容
*/

copyOf 、copyOfRange 拷贝一个数组的内容并生成一个新的数组

kotlin 复制代码
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val array2: Array<Int> = array1.copyOf()   // 使用copyOf来拷贝数据内容并创建一个新的数组存储

println(array2 == array1)                // false
println(array2 === array1)               // false
println(array2.contentEquals(array1))    // true

可以指定拷贝的长度或是拷贝的范围

kotlin 复制代码
val array2: Array<Int?> = array1.copyOf(10)
// 在拷贝时指定要拷贝的长度,如果小于数组长度则只保留前面一部分内容,如果大于则在末尾填充null,因此返回的类型是Int?可空
kotlin 复制代码
val array2: Array<Int> = array1.copyOfRange(1, 3)  // 从第二个元素开始拷贝到第四个元素前为止,共2个元素
// 使用copyOfRange拷贝指定下标范围上的元素

sliceArray 分割数组

kotlin 复制代码
val array1 = arrayOf(1, 2, 3, 4, 5)
val array2 = array1.sliceArray(1..3)   // 从第二个元素到第四个元素共三个元素的数组

+拼接、-去重

kotlin 复制代码
val array1 = arrayOf(1, 2, 3, 4, 5)
val array2 = arrayOf(3, 7, 4, 9, 10)
val array3 = array1 + array2      // [1, 2, 3, 4, 5, 3, 7, 4, 9, 10]
val array4 = array3 + 11          // [1, 2, 3, 4, 5, 3, 7, 4, 9, 10, 11]

Array 没有 -

kotlin 复制代码
val l1 = listOf("AAA", "DDD", "CCC")
val l2 = listOf("BBB", "CCC", "EEE")
val l3 = l1 - l2  // 前面的集合减去与后面集合存在重复内容的部分: [AAA, DDD]

val l4 = l1 + "H"
val l5 = l1 + l2

contains、in、indexOf、binarySearch 查找

kotlin 复制代码
val array = arrayOf(13, 16, 27, 32, 38)
println(array.contains(13))   // 判断数组中是否包含13这个元素
println(array in 13)   // 跟contains函数效果一样,判断数组中是否包含13这个元素
println(array.indexOf(26))    // 寻找指定元素的下标位置
println(array.binarySearch(16))    // 二分搜索某个元素的下标位置(效率吊打上面那个函数,但是需要数组元素有序)

注意,contains 如何判断对象相同

kotlin 复制代码
class Student(val name: String, val age: Int)

fun main() {
    val array = arrayOf(Student("小明", 18), Student("小红", 17))
    println(array.contains(Student("小明", 18)))   // false
}

源码

kotlin 复制代码
public operator fun <@ Kotlin .internal.OnlyInputTypes T> Array<out T>.contains(element: T): Boolean {
    return indexOf(element) >= 0  // 调用内部indexOf函数看看能不能找到这个元素的下标
}

public fun <@ Kotlin .internal.OnlyInputTypes T> Array<out T>.indexOf(element: T): Int {
    if (element == null) {
       ...
    } else {
        for (index in indices) {   // 直接通过遍历的形式对数组内的元素一个一个进行判断
            if (element == this[index]) {   // 可以看到,这里判断使用的是==运算符
                return index
            }
        }
    }
    return -1
}

使用==的判断实际上取决于 equals 函数的重写,如果要让两个对象实现自定义的判断,需要重写对应类型的 equals 函数,否则无法实现自定义比较,默认情况下判断的是两个对象是否为同一个对象。重写 equals 再使用 contains 则为 true 了。

kotlin 复制代码
class Student(val name: String, val age: Int) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Student) return false
        if (name != other.name) return false  // 判断名字是否相同
        if (age != other.age) return false  // 判断年龄是否相同
        return true
    }
}

any 判断数组是否为空数组(容量为0)

kotlin 复制代码
println(array.any())   // 判断数组是否为空数组(容量为0)     非空为true

first、last 获取数组首尾元素

kotlin 复制代码
println(array.first())   // 快速获取首个元素
println(array.last())    // 快速获取最后一个元素

fill 填充

kotlin 复制代码
val array1 = arrayOf(1, 2, 3, 4, 5)
array1.fill(10)   // 重新将数组内部元素全部填充为10

reversedArray、reverse、shuffle、sort、sortDescending 排序

逆序

kotlin 复制代码
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val array2: Array<Int> = array1.reversedArray()   // 翻转数组元素顺序,并生成新的数组
kotlin 复制代码
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
array1.reverse()   // 直接在原数组上进行元素顺序翻转
array1.reverse(1, 3)   // 仅翻转指定下标范围内的元素

打乱

kotlin 复制代码
array1.shuffle()  // 使用shuffle函数将数组中元素顺序打乱

排序

kotlin 复制代码
array1.sort()   // 使用sort函数对数组中元素进行排序,排序规则可以自定义
array1.sort(1, 3)   // 仅排序指定下标范围内的元素
array1.sortDescending()   // 按相反顺序排序

注意,排序操作并不是任何类型上来就支持的,由于这里使用的是基本类型 Int,它默认实现了 Comparable 接口,这个接口用于告诉程序排序规则,所以,如果数组中的元素是未实现 Comparable 接口的类型,那么无法支持排序操作。

kotlin 复制代码
// 首先类型需要实现Comparable接口,泛型参数T填写为当前类型
class Student(private val name: String, private val age: Int) : Comparable<Student> {
  	// 接口中只有一个函数需要实现,这个是比较的核心算法,参数是另一个需要比较的元素,返回值Int类型
  	// 使用当前对象this和给定对象other进行比较,如果返回小于0的数,说明当前对象应该排在前面,反之排后面,返回0表示同样的级别
    override fun compareTo(other: Student): Int = this.age - other.age
    override fun toString(): String = "$name ($age)"
}

这样,自定义的类型就支持比较和排序了:

kotlin 复制代码
val array1 = arrayOf(Student("小明", 18), Student("小红", 17))
array1.sort()

sum 求和、average 求平均值、min、max 获取最值

kotlin 复制代码
val array: Array<Int> = arrayOf(1, 2, 3, 4, 5)
println(array.sum())            // 15
println(array.average())        // 3.0
println(array.min())            // 1
println(array.max())            // 5

map、mapIndexed 对数组中的每个元素进行转换(映射)

注意不会对原来的改变

kotlin 复制代码
val numbers = arrayOf(1, 2, 3, 4)
val squares = numbers.map { it * it }       // [1, 4, 9, 16]
val squaresIdx = numbers.mapIndexed { index, value ->  index * value}     // [0, 2, 6, 12]

filter、filterNotNull、filterIsInstance 过滤

kotlin 复制代码
val numbers = arrayOf(1, 2, 3, 4, 5, 6)
val evens = numbers.filter { it % 2 == 0 }   // [2, 4, 6]

val numbers = arrayOf(1, 2, 3, 4, 5, 6)
val result = numbers
    .filter { it % 2 == 0 }  // 先筛选偶数
    .map { it * it }         // 再平方

Map 同样支持过滤

kotlin 复制代码
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
println(filteredMap) // {key11=11}

filterNotNull 过滤掉 null 元素,只保留非空的值。

kotlin 复制代码
val arr = arrayOf(1, null, 2, null, 3)
val result = arr.filterNotNull()      // 返回 List
println(result)    // [1, 2, 3]

filterIsInstance 过滤出指定类型的元素

kotlin 复制代码
val arr = arrayOf(1, "two", 3.0, "four")
val strings = arr.filterIsInstance<String>()
println(strings)  // [two, four]

flatten、flatMap 扁平化

对于多维(嵌套)

kotlin 复制代码
val arr: Array<Array<Int>> = arrayOf(
    arrayOf(1, 2, 3),
    arrayOf(4, 5),
    arrayOf(6)
)
val result = arr.flatten()      // 返回 List
println(result)    // [1, 2, 3, 4, 5, 6]

flatMap 先对集合中每个元素执行映射(返回集合),然后将结果"压平"成一个列表。

kotlin 复制代码
val list = listOf(Container(listOf("A", "B")), Container(listOf("C", "D")))
val flatList: List<String> = list.flatMap { it.list }   // 先将每一个Container映射为List
println(flatList)   // [AAA, BBB, CCC, DDD]
kotlin 复制代码
val arr = arrayOf(1, 2, 3)
val result = arr.flatMap { num ->
    arrayOf(num, num * 10).toList()
}
println(result)  // [1, 10, 2, 20, 3, 30]

chunked 分块、partition 分区、groupBy 分组

chunked(size: Int) 分成指定大小的块(最后一块可能更小) 【Array 没有此扩展】

kotlin 复制代码
val list = listOf(1, 2, 3, 4, 5, 6, 7)
val chunks: List<List<Int>> = list.chunked(3)
println(chunks)  // [[1, 2, 3], [4, 5, 6], [7]]

partition(predicate) 根据条件将集合分成两个部分:第一个包含符合条件的元素,第二个包含不符合条件的元素。返回 Pair<List, List>

kotlin 复制代码
val arr = arrayOf(1, 2, 3, 4, 5, 6)
val (even, odd) = arr.partition { it % 2 == 0 }
println("Even: $even")  // Even: [2, 4, 6]
println("Odd: $odd")    // Odd: [1, 3, 5]

groupBy(keySelector) 根据某个规则将元素分组,返回 Map<K, List<T>>。分组规则自定义,可以按长度、类型、首字母等。

kotlin 复制代码
val arr = arrayOf("apple", "banana", "avocado", "blueberry", "cherry")
val grouped = arr.groupBy { it.first() } // 按首字母分组
println(grouped)  // {a=[apple, avocado], b=[banana, blueberry], c=[cherry]}

any、none、all 判断条件是否满足

any 判断数组中是否 存在 至少一个元素满足条件,如果不传条件,则判断数组是否非空。

kotlin 复制代码
val arr = arrayOf(1, 2, 3, 4)
println(arr.any())           // true
println(arr.any { it > 3 })  // true
println(arr.any { it > 10 }) // false

none 判断数组中是否 没有 元素满足条件,如果不传条件,表示判断是否为空数组。

kotlin 复制代码
val arr = arrayOf(1, 2, 3)
println(arr.none())           // false
println(arr.none { it < 0 })  // true
println(arr.none { it == 2 }) // false

all 判断数组中 所有元素 是否都满足条件

kotlin 复制代码
val arr = arrayOf(2, 4, 6)
println(arr.all { it % 2 == 0 })  // true
println(arr.all { it < 6 })       // false

slice、take、drop 切片

slice(indices) 根据 索引范围索引列表 取出子集,返回 List

kotlin 复制代码
val arr = arrayOf("a", "b", "c", "d", "e")
println(arr.slice(1..3))      // [b, c, d]
println(arr.slice(listOf(0, 2, 4)))  // [a, c, e]

take(n) 取出数组开头的前 n 个元素,返回 List

kotlin 复制代码
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.take(3))     // [10, 20, 30]

takeLast(n) 取出数组末尾的 n 个元素,返回 List

kotlin 复制代码
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.take(2))     // [40, 50]

takeWhile 从头开始取元素,只要满足条件就继续取,直到遇到不满足的为止,返回 List

kotlin 复制代码
val arr = arrayOf(1, 2, 3, 2, 1)
println(arr.takeWhile { it < 3 })      // [1, 2]

drop(n) 跳过前 n 个元素,取剩下的,返回 List

kotlin 复制代码
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.drop(2))    // [30, 40, 50]

dropLast(n) 去掉最后 n 个元素,保留前面的,返回 List

kotlin 复制代码
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.dropLast(2))    // [10, 20, 30]
相关推荐
FunnySaltyFish13 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker19 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z4 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton4 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream4 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam5 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker5 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc5 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景6 天前
kotlin协程学习小计
android·kotlin