【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]
相关推荐
I'm Jie19 小时前
(五)Gradle 依赖传递与冲突处理
java·spring boot·spring·kotlin·gradle·maven
消失的旧时光-19431 天前
Kotlin 高阶函数在回调设计中的最佳实践
android·开发语言·kotlin
用户091 天前
Kotlin Flow的6个必知高阶技巧
android·面试·kotlin
用户091 天前
Jetpack Compose静态与动态CompositionLocal深度解析
android·面试·kotlin
Jeled2 天前
「高级 Android 架构师成长路线」的第 1 阶段 —— 强化体系与架构思维(Clean Architecture 实战)
android·kotlin·android studio·1024程序员节
明道源码2 天前
Kotlin 控制流、函数、Lambda、高阶函数
android·开发语言·kotlin
橙子199110162 天前
在 Kotlin 中,ViewModel 的获取
开发语言·vue.js·kotlin
hweiyu002 天前
Gradle 构建脚本迁移:从 Groovy DSL 到 Kotlin DSL,语法与技巧对比
开发语言·kotlin·gradle
消失的旧时光-19432 天前
搞懂 Kotlin 的 List、Set、Map、HashMap、LinkedHashMap,以及 asSequence() 的底层原理与实战场景。
kotlin·数据处理·1024程序员节