2、数组和集合

数组

数组是相同类型数据的有序集合,数组可以代表任何相同类型的一组内容,其中存放的每一个数据称为数组的一个元素。

创建数组

在Kotlin中有两种创建方式:

Array 类

// Array 类的定义
public class Array<T> {
    //构造函数,包括数组大小、元素初始化函数
    public inline constructor(size: Int, init: (Int) -> T)

    //重载[]运算符
    public operator fun get(index: Int): T
    public operator fun set(index: Int, value: T): Unit

    //当前数组大小(可以看到是val类型的,一旦确定不可修改)
    public val size: Int

    //迭代运算重载(后面讲解)
    public operator fun iterator(): Iterator<T>
}

构造函数创建

// [0,2,4]
var arr: Array<Int> = Array(3) { i -> i * 2 }
// [0, 0, 0, 0, 0]
var arr: Array<Int> = Array(5){0}

arrayOf

var arr1: Array<Int> = arrayOf(1,2,3,4,5)
var arr2: Array<String> = arrayOf("Tom","Lucy")

typeArrayOf

Kotlin 也提供了指定类型数组快速创建方法

这些包含基本类型的数组往往在编译时可以得到优化(比如JVM平台会直接编译为基本类型数组,如int[]double[]等,可以免去装箱拆箱开销)Kotlin提供了预设的原生类型数组

原生类型数组 相当于Java
BooleanArray boolean[]
ByteArray byte[]
CharArray char[]
DoubleArray double[]
FloatArray float[]
IntArray int[]
LongArray long[]
ShortArray short[]
var arr1 = intArrayOf(1,2,3)
var arr2 = doubleArrayOf(1.0,2.0,3.0)
var arr3 = charArrayOf('a','b','c')

这些原生类型数组也有一些额外的扩展

val array: IntArray = intArrayOf(7, 3, 9, 1, 6)
println(array.sum())  //快速求和操作,获得数组中所有元素之和
println(array.average())   //求整个数组的平均数
println(array.min()) // 获取最小值
println(array.max()) // 获取最大值

操作元素

访问

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    println(arr[0])
    println(arr[1])
}

设值

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    arr[0] = 10
    println(arr[0])
}

遍历

通过数组元素遍历

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    for (i in arr){
        println(i)
    }
}

通过数组索引遍历

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    for(i in 0..arr.size - 1){
        println(arr[i])
    }
}

上面遍历的时候需要对arr.size - 1来获取有效的索引(因为 size = 5,但是 arr 最后一个元素是 arr[4]

其实Kotlin 的数组提供了一个属性indices来返回的是数组的有效索引范围

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    for(i in arr.indices){
        println(arr[i])
    }
}

如果想同时遍历数组的元素以及索引,那么 Kotlin 的数组提供了一个方法withIndex,它会生成一系列IndexedValue对象

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    for ((index,value) in arr.withIndex()){
        println("$index $value")
    }
}

如果需要使用Lambda表达式快速处理里面的每一个元素,也可以使用forEach高阶函数

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    // 只遍历元素
    arr.forEach { println(it) }
    // 遍历元素和索引
    arr.forEachIndexed { index, value -> println("$index $value") }
}

如果只是想打印数组里面的内容,快速查看,我们可以使用如下方式

fun main() {
    var arr: Array<Int> = arrayOf(1,2,3,4,5)
    println(arr.joinToString()) // 1, 2, 3, 4, 5
    println(arr.joinToString(" - ")) // 1 - 2 - 3 - 4 - 5
    println(arr.joinToString(" - ","<" , ">")) // <1 - 2 - 3 - 4 - 5>
    println(arr.joinToString(limit = 3, truncated = "...")) // 1, 2, 3...
    println(arr.joinToString { "value: $it" }) // value: 1, value: 2, value: 3, value: 4, value: 5
}

数组比较

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    var arr2 = arrayOf(1,2,3,4,5)
    var arr3 = arrayOf(1,3,2,4,5)
    println(arr1 == arr2) // false
    println(arr1.contentEquals(arr2)) // true
    println(arr1.contentEquals(arr3)) // false
}

拷贝

全部拷贝

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    var arr2 = arr1.copyOf()
    println(arr1 == arr2) // false 不是同一个对象
    println(arr1.contentEquals(arr2)) // true
}

指定拷贝的长度

在拷贝时指定要拷贝的长度,如果小于数组长度则只保留前面一部分内容,如果大于则在末尾填充null,因此返回的类型是Int?可空

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    var arr2: Array<Int?> = arr1.copyOf(10)
    println(arr2.joinToString()) // 1, 2, 3, 4, 5, null, null, null, null, null
}

使用copyOfRange拷贝指定下标范围上的元素

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    var arr2 = arr1.copyOfRange(1,3) // 从 index = 1 开始拷贝,到 index = 3 的前一个元素
    println(arr2.joinToString()) // 2, 3
}

也有一个和copyOfRange相似的方法sliceArray分割数组,参数可以传入Range

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    var arr2 = arr1.sliceArray(1..3) // 按照传入的 Range 进行分割
    println(arr2.joinToString()) // 2, 3, 4
}

拼接数组

两个数组也可以直接拼接到一起,形成一个长度为10的新数组,按顺序拼接

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    var arr2 = arrayOf(6,7,8,9,10)
    var arr3 = arr1 + arr2
    println(arr3.joinToString()) // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}

查找元素

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    println(arr1.contains(3)) // 判断数组中是否包含3这个元素
    println(3 in arr1) // 判断数组中是否包含3这个元素
    println(arr1.indexOf(3)) // 获取3这个元素的索引
    println(arr1.binarySearch(3)) // 二分查找,返回索引,找不到返回负数,但需要数组有序
}
val array = arrayOf(1, 2, 3, 4, 5)
println(array.any())   //判断数组是否为空数组(容量为0)
println(array.first())   //快速获取首个元素
println(array.last())    //快速获取最后一个元素

翻转数组

翻转原数组

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    arr1.reverse()
    println(arr1.joinToString()) // 5, 4, 3, 2, 1
}

仅翻转指定下标

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    arr1.reverse(1,3)
    println(arr1.joinToString()) // 1, 3, 2, 4, 5
}

翻转并生成新数组

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    var arr2 = arr1.reversedArray()
    println(arr1.joinToString()) // 1, 2, 3, 4, 5
    println(arr2.joinToString()) // 5, 4, 3, 2, 1
}

打乱数组

如果我们想要直接将数组中元素打乱,也有一个快速洗牌的函数将所有元素顺序重新随机分配

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    arr1.shuffle()
    println(arr1.joinToString()) // 3, 5, 4, 2, 1
    arr1.shuffle()
    println(arr1.joinToString()) // 1, 2, 4, 5, 3
}

数组排序

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    arr1.shuffle() // 打乱
    println(arr1.joinToString()) // 2, 4, 5, 3, 1
    arr1.sort() // 排序
    println(arr1.joinToString()) // 1, 2, 3, 4, 5
}

倒序

fun main() {
    var arr1 = arrayOf(1,2,3,4,5)
    arr1.shuffle() // 打乱
    println(arr1.joinToString()) // 2, 4, 5, 3, 1
    arr1.sortDescending() // 倒序排序
    println(arr1.joinToString()) // 5, 4, 3, 2, 1
}

填充数组

fun main() {
    var arr = arrayOfNulls<Int>(10)
    arr.fill(1) // 全部填充 1
    println(arr.contentToString()) // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}

集合

在Kotlin中,默认提供了以下类型的集合:

所有集合类都是继承自Collection接口(Map除外)

Kotlin 的集合设计和 Java 不同的另一项重要特质是,它把访问集合数据的接口和修改集合数据的接口分开了。

这种区别在于最基础的使用集合的接口之中:

除了集合之外,Kotlin 中的 Map 类(它没有继承 Collection 或是 Iterable)也被表示成了两种不同的版本:Map 和 MutableMap。

集合创建函数

List

fun main() {
    var list: MutableList<Int> = mutableListOf(1,2,3)
    list.add(4) // 使用add函数添加一个新的元素到列表末尾
    list[0] = 10 // 修改某个位置上的元素
    println(list) // [10, 2, 3, 4]
    println(list[2]) // 获取某个位置上的元素
}

插入元素

fun main() {
    val list = mutableListOf(1, 2, 3, 4)
    list.add(2, 666)   //将666插入到第三个元素的位置上去
    println(list) // [1, 2, 666, 3, 4]
}

删除元素

fun main() {
    val list = mutableListOf("AAA", "BBB", "CCC", "DDD")
    list.removeAt(2)  //使用removeAt可以删除指定位置上的元素
    println(list) // [AAA, BBB, DDD]
    list.remove("DDD")    //使用remove可以删除指定元素
    println(list) // [AAA, BBB]
}

只读列表

使用listOf生成的列表是只读的

fun main() {
    var list = listOf(1,2,3)
    list[0] = 10 // No set method providing array access
}

Set

Set集合非常特殊,虽然它也可以保存一组数据,但是它不允许存在重复元素

fun main() {
    var set = mutableSetOf("AAA","BBB","BBB","CCC")
    println(set) // [AAA, BBB, CCC]
}

操作方法和 List 相同

Map

Map是一个非常特殊的集合类型,它存储了一些的键值对

在 Kotlin 中,提供了一个非常方便的中缀函数来定义键值对

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

也就是说,创建一个键值对是这样的,其中 100 是 key,lucy 是 value

var kv: Pair<Int, String> = 100 to "lucy"

创建一个 map 其实就是创建多个键值对

fun main() {
    var map = mutableMapOf(
        "key1" to "value1", 
        "key2" to "value2"
    )
    println(map) // {key1=value1, key2=value2}
}

访问元素

fun main() {
    var map = mutableMapOf(
        "key1" to "value1",
        "key2" to "value2"
    )

    map["key3"] = "value3" // 添加键值对
    map.put("key4", "value4") // 添加键值对
    map += "key5" to "value5" // 添加键值对

    println(map["key1"]) // value1
    println(map.contains("key1")) // true
    println("key1" in map) // 同上 true
    println(map.containsValue("value1")) // true
}

注意: Map中的键值对存储,只能通过Key去访问Value,而不能通过Value来反向查找Key,映射关系是单向的。

获取 keys 或 values

fun main() {
    var map = mutableMapOf(
        "key1" to "value1",
        "key2" to "value2"
    )
    var keys: MutableSet<String> = map.keys
    var values: MutableCollection<String> = map.values
    println(keys) // [key1, key2]
    println(values) // [value1, value2]
}

遍历

fun main() {
    var map = mutableMapOf(
        "key1" to "value1",
        "key2" to "value2"
    )

    map.forEach {(key,value) -> println("$key - $value")}

    for ((key,value) in map){
        println("$key - $value")
    }

    for (entry in map){
        println("${entry.key} - ${entry.value}")
    }
}

不同的Map

有了Map之后,我们在处理一些映射关系的时候就很方便了。跟Set一样,官方也提供了多种多样的集合

val map1 = mapOf(1 to "AAA")   //只读Map
val map2 = hashMapOf(1 to "AAA")   //不保存Key顺序的Map
val map3 = linkedMapOf(1 to "AAA")   //保存Key顺序的Map,跟mutableMapOf一样
val map4 = sortedMapOf(1 to "AAA")   //根据排序规则自动对Key排序的Map
val map5 = emptyMap<Int, String>()   //空Map
val hashMap = HashMap<Int, String>()   //采用构造函数创建的HashMap,不保存Key顺序的Map,同map2
val linkedHashSet = LinkedHashMap<Int, String>()   //采用构造函数创建的LinkedHashMap,保存Key顺序的Map,同map3

迭代器

集合类型的顶层接口都是一个叫做Collection的接口,继承自Iterable接口

public interface Iterator<out T> {
    //获取下一个待遍历元素
    public operator fun next(): T

    //如果还有元素没有遍历,那么返回true否则返回false,而这个函数也是运算符重载函数正好对应着 for in 操作
    public operator fun hasNext(): Boolean
}

获取迭代器对象

val list = listOf("AAA", "BBB", "CCC")
val iterator: Iterator<String> = list.iterator()   //通过iterator函数得到迭代器对象

迭代

val list = listOf("AAA", "BBB", "CCC")
val iterator: Iterator<String> = list.iterator()
while (iterator.hasNext()) {   //使用while不断判断是否存在下一个
    println(iterator.next())   //每次循环都取出一个
}

集合和数组

数组转集合

数组跟集合的联动,有些时候我们可能拿到的是一个数组对象,但是我们希望将其转换为集合类进行操作,我们可以使用数组提供的集合快速转换函数来进行转换

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()

集合转数组

var list = listOf(1,2,3,4,5)
var arr = list.toIntArray()

压缩操作

它可以将当前集合元素和另一个集合中具有相同索引的元素组合起来,生成一个装满Pair的列表

val list1 = listOf(1, 2, 3)
val list2 = listOf("AAA", "BBB", "CCC")
val pairs: List<Pair<Int, String>> = list1.zip(list2)
println(pairs) // [(1, AAA), (2, BBB), (3, CCC)]

利用压缩操作我们可以快速将两个List集合揉成一个Map集合

val map = mutableMapOf<Int, String>()
map.putAll(list1.zip(list2))
println(map)  //结果 {1=AAA, 2=BBB, 3=CCC}