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

大多数扩展对于数组和集合类都可以互相使用。这里大部分以 Array 为例。
更多详见 https://Kotlinlang.org/docs/collections-overview.html
数组与集合的转换
使用 Array 提供的集合快速转换函数转为集合
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
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
val set = setOf(1, 2, 3, 4)val array = set.toTypedArray()println(array.joinToString()) // 输出:1, 2, 3, 4
如果是数字类型,可以转成特定的原始数组:
val intList = listOf(1, 2, 3)
val intArray = intList.toIntArray() // 输出类型:IntArrayprintln(intArray.joinToString()) // 输出:1, 2, 3
associateWith 将集合中的每个元素作为 key,通过指定的函数计算出对应的 value,最终生成一个 Map<K, V>
val words = listOf("apple", "banana", "cherry")
val map = words.associateWith { it.length }
println(map) // {apple=5, banana=6, cherry=6}
associateBy 与 associateWith 相反,它通过指定的函数计算出 key,而集合元素本身或通过另一个函数计算出的值作为 value。
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
val map = words.associateBy(keySelector = { it.first() },valueTransform = { it.length }
)
println(map) // {a=5, b=6, c=6}
associate是最通用的形式,允许你完全控制 key-value 对的生成规则
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。
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,用于直接合并生成新的类型
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
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:
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 就支持解构,所以可以在遍历时直接使用解构之后的变量进行操作:
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
for ((index, item) in array.withIndex()) { // 使用withIndex解构后可以同时遍历索引和元素println("元素$item,位置: $index")
}
forEach、forEachIndexed 遍历每一个元素,并对每个元素执行指定的操作(通过 lambda 表达式)
注意无返回值,映射是使用 map。
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
array.forEach { println(it) } // 只带元素
array.forEachIndexed { index, item -> // 同时带索引和元素println("元素$item,位置: $index")
}
contentEquals、contentDeepEquals 比较数组内容是否相同
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:
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)) // falseprintln(arr1.contentDeepEquals(arr2)) // true
}/*
contentEquals只做浅比较,遇到子数组时比较引用
contentDeepEquals做深比较,递归比较子数组的内容
*/
copyOf 、copyOfRange 拷贝一个数组的内容并生成一个新的数组
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
可以指定拷贝的长度或是拷贝的范围
val array2: Array<Int?> = array1.copyOf(10)
// 在拷贝时指定要拷贝的长度,如果小于数组长度则只保留前面一部分内容,如果大于则在末尾填充null,因此返回的类型是Int?可空
val array2: Array<Int> = array1.copyOfRange(1, 3) // 从第二个元素开始拷贝到第四个元素前为止,共2个元素
// 使用copyOfRange拷贝指定下标范围上的元素
sliceArray 分割数组
val array1 = arrayOf(1, 2, 3, 4, 5)
val array2 = array1.sliceArray(1..3) // 从第二个元素到第四个元素共三个元素的数组
+拼接、-去重
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 没有 -
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 查找
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 如何判断对象相同
class Student(val name: String, val age: Int)fun main() {val array = arrayOf(Student("小明", 18), Student("小红", 17))println(array.contains(Student("小明", 18))) // false
}
源码
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 了。
class Student(val name: String, val age: Int) {override fun equals(other: Any?): Boolean {if (this === other) return trueif (other !is Student) return falseif (name != other.name) return false // 判断名字是否相同if (age != other.age) return false // 判断年龄是否相同return true}
}
any 判断数组是否为空数组(容量为0)
println(array.any()) // 判断数组是否为空数组(容量为0) 非空为true
first、last 获取数组首尾元素
println(array.first()) // 快速获取首个元素
println(array.last()) // 快速获取最后一个元素
fill 填充
val array1 = arrayOf(1, 2, 3, 4, 5)
array1.fill(10) // 重新将数组内部元素全部填充为10
reversedArray、reverse、shuffle、sort、sortDescending 排序
逆序
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val array2: Array<Int> = array1.reversedArray() // 翻转数组元素顺序,并生成新的数组
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
array1.reverse() // 直接在原数组上进行元素顺序翻转
array1.reverse(1, 3) // 仅翻转指定下标范围内的元素
打乱
array1.shuffle() // 使用shuffle函数将数组中元素顺序打乱
排序
array1.sort() // 使用sort函数对数组中元素进行排序,排序规则可以自定义
array1.sort(1, 3) // 仅排序指定下标范围内的元素
array1.sortDescending() // 按相反顺序排序
注意,排序操作并不是任何类型上来就支持的,由于这里使用的是基本类型 Int,它默认实现了 Comparable 接口,这个接口用于告诉程序排序规则,所以,如果数组中的元素是未实现 Comparable 接口的类型,那么无法支持排序操作。
// 首先类型需要实现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.ageoverride fun toString(): String = "$name ($age)"
}
这样,自定义的类型就支持比较和排序了:
val array1 = arrayOf(Student("小明", 18), Student("小红", 17))
array1.sort()
sum 求和、average 求平均值、min、max 获取最值
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 对数组中的每个元素进行转换(映射)
注意不会对原来的改变
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 过滤
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 同样支持过滤
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 元素,只保留非空的值。
val arr = arrayOf(1, null, 2, null, 3)
val result = arr.filterNotNull() // 返回 List
println(result) // [1, 2, 3]
filterIsInstance 过滤出指定类型的元素
val arr = arrayOf(1, "two", 3.0, "four")
val strings = arr.filterIsInstance<String>()
println(strings) // [two, four]
flatten、flatMap 扁平化
对于多维(嵌套)
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 先对集合中每个元素执行映射(返回集合),然后将结果“压平”成一个列表。
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]
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 没有此扩展】
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>
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>>。分组规则自定义,可以按长度、类型、首字母等。
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 判断数组中是否 存在 至少一个元素满足条件,如果不传条件,则判断数组是否非空。
val arr = arrayOf(1, 2, 3, 4)
println(arr.any()) // true
println(arr.any { it > 3 }) // true
println(arr.any { it > 10 }) // false
none 判断数组中是否 没有 元素满足条件,如果不传条件,表示判断是否为空数组。
val arr = arrayOf(1, 2, 3)
println(arr.none()) // false
println(arr.none { it < 0 }) // true
println(arr.none { it == 2 }) // false
all 判断数组中 所有元素 是否都满足条件
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
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
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.take(3)) // [10, 20, 30]
takeLast(n) 取出数组末尾的 n 个元素,返回 List
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.take(2)) // [40, 50]
takeWhile 从头开始取元素,只要满足条件就继续取,直到遇到不满足的为止,返回 List
val arr = arrayOf(1, 2, 3, 2, 1)
println(arr.takeWhile { it < 3 }) // [1, 2]
drop(n) 跳过前 n 个元素,取剩下的,返回 List
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.drop(2)) // [30, 40, 50]
dropLast(n) 去掉最后 n 个元素,保留前面的,返回 List
val arr = arrayOf(10, 20, 30, 40, 50)
println(arr.dropLast(2)) // [10, 20, 30]
