集合

207 阅读4分钟

一、集合

Kotlin中的集合用于在一个单元中存储一组相关对象。通过使用集合,可以存储,检索操作和聚合数据,也可以作为值参传给函数。

Kotlin中大致分为两种不同集合的形式。它们是:

  • 只读集合(不变)
  • 可变集合
集合类型只读可变
ListlistOfmutableListOf、arrayListOf
SetsetOfmutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
MapmapOfmutableMapOf、hashMapOf、linkedMapOf、sortedMapOf

二、List集合

允许有重复元素

2.1 创建只读List

fun main() {
    //创建List
    var data = listOf("Flutter","Java","Kotlin")
    //元素获取
    println(data[2])//Kotlin
}

数组越界

  • 使用List.getOrElse()函数
  • 使用List.getOrNull()函数
    var data = listOf("Flutter","Java","Kotlin")
    //数组越界
//    println(data[3])//抛异常:ArrayIndexOutOfBoundsException

    //避免数据越界
    //1、当没有这个元素,执行lambda表达式
    println(data.getOrElse(3) { "Android" })//Android
    //2、当没有这个元素,返回null
    println(data.getOrNull(3))//null
    //2.1 使用空合并操作符,返回空时提供默认值
    println(data.getOrNull(3) ?: "Android")//Android

2.2 创建可变List

使用索引值重写数组的值。数组的值是可以修改的,所以把它称为可变数组。要创建可变列表,可以使用mutableListOf函数。

    //创建可变数组
    var mutableData = mutableListOf("Flutter", "Java", "Kotlin")

可变数组的增删改查

    //新增元素iOS
    mutableData.add("iOS")
    println(mutableData.toString())//[Flutter, Java, Kotlin, iOS]
    //删除元素Flutter
    mutableData.remove("Flutter")
    println(mutableData.toString())//[Java, Kotlin, iOS]
    //在下标为1位置新增元素鸿蒙
    mutableData.add(1,"鸿蒙")
    println(mutableData.toString())//[Java, 鸿蒙, Kotlin, iOS]
    //修改下标为1元素内容
    mutableData.set(1,"鸿蒙2.0")
    println(mutableData.toString())//[Java, 鸿蒙2.0, Kotlin, iOS]

2.3 List和MutableList相互转换

List还支持使用toList和toMutableList函数动态实现只读列表和可变列表的相互转换

    //创建只读数组
    var data = listOf("Flutter", "Java", "Kotlin")
    //创建可变数组
    var mutableData = mutableListOf("Flutter", "Java", "Kotlin")
    //MutableList转List
    var data2:List<String> = mutableData.toList()
    //List转MutableList
    var mutableData2:MutableList<String> =  data.toMutableList()

2.4 mutator函数

  • 能修改可变列表的函数有个统一的名字:mutator函数
  • 添加元素运算符与删除元素运算符(都属于mutator函数)
  • 基于lambda表达式指定的条件删除元素
    //使用mutator函数
    mutableData = mutableListOf("Flutter", "Java", "Kotlin")
    //mutator:新增元素iOS
    mutableData += "iOS"
    println(mutableData)//[Flutter, Java, Kotlin, iOS]
    //mutator:删除元素Flutter
    mutableData -= "Flutter"
    mutableData.remove("Flutter")
    println(mutableData)//[Java, Kotlin, iOS]
    
    //使用lambda表达式删除元素
    //先在lambda判断元素是否存在,当元素存在返回true,则删除元素
    mutableData.removeIf { it.contains("iOS") }
    println(mutableData)//[Java, Kotlin]

2.5 List集合遍历

  • for..in
  • forEach
  • forEachIndexed
    //List集合遍历
    //方法一:for..in
    for (md in mutableData){
        println("①-$md")
    }
    //方法二:forEach
    mutableData.forEach {
        println("②-$it")
    }
    //方法三:forEachIndexed
    mutableData.forEachIndexed { index, s ->
        println("③-$index-$s")
    }

三、Set集合

不允许有重复元素

3.1 创建只读Set

    //创建只读Set
    var set = setOf("Flutter", "Java", "Kotlin","Java","Flutter")
    println(set.size)//3,因为后面2项与前面重复
    //元素获取
    println(set.elementAt(2))//Kotlin
    //这里的安全操作和List类似。
    println(set.elementAtOrElse(3) { "Set-Android" })//Set-Android
    println(set.elementAtOrNull(3) ?: "Set-Android")//Set-Android

3.2 创建可变Set

    //创建可变Set
    var mset = mutableSetOf("Flutter","Java", "Kotlin","Java","Flutter")
    //这里增删改查也跟List类似就不多描述了。
    mset.add("iOS")
    println(mset)//[Flutter, Java, Kotlin, iOS]
    mset.remove("Java")
    println(mset)//[Flutter, Kotlin, iOS]

四、Map集合

4.1 创建只读Map

  • 使用to函数将它左边和右边的值转化成一对Pair。
  • 直接使用Pair
    //创建只读Map
    //方法一:
    var map = mapOf("Kotlin" to 12,"Java" to 32,"Flutter" to 8)
    println(map)//{Kotlin=12, Java=32, Flutter=8}
    //方法二:
    map = mapOf(Pair("Kotlin",15),Pair("Java",28),Pair("Flutter",55))
    println(map)//{Kotlin=15, Java=28, Flutter=55}

    //获取值
    println(map["Kotlin"])//15
    println(map.get("Java"))//28
    println(map.getOrElse("iOS") { "Android" })//Android
    println(map.getOrDefault("iOS", 100))//100

4.2 创建可变Map

    //创建可变Map
    var mapM = mutableMapOf("Kotlin" to 19, "Java" to 51, "Flutter" to 15)
    println(mapM)//{Kotlin=19, Java=51, Flutter=15}
    //添加元素
    mapM.put("iOS", 36)
    println(mapM)//{Kotlin=19, Java=51, Flutter=15, iOS=36}
    //删除元素Flutter
    mapM -= "Flutter"
    println(mapM)//{Kotlin=19, Java=51, iOS=36}
    //没有Android元素返回帅次
    println(mapM.getOrElse("Android") { "帅次" })//帅次
    println(mapM)//{Kotlin=19, Java=51, iOS=36}
    //没有Vs元素,则添加Vs元素
    mapM.getOrPut("Vs") { 20 }
    println(mapM)//{Kotlin=19, Java=51, iOS=36, Vs=20}
    // //没有Ap元素返回94
    println(mapM.getOrDefault("Ap", 94))
    println(mapM)//{Kotlin=19, Java=51, iOS=36, Vs=20}

4.3 遍历Map

2种forEach遍历Map

    map = mapOf(Pair("Kotlin",15),Pair("Java",28),Pair("Flutter",55))
    //遍历Map
    //方法一:
    map.forEach {
        println("一:${it.key} - ${it.value}")
    }
    //方法二:
    map.forEach { (s, i) ->
        println("二:$s - &i")
    }

五、集合转换

    //集合转换
    var list = listOf("Flutter", "Java", "Kotlin", "Java", "Flutter")
    println("未去重:$list")
    //对List的元素进行去重
    var listTo = list.toSet().toList()
    println("去重:$listTo")

    //kotlin提供的快捷函数一步到位
    println(list.distinct())

六、Array 和 IntArray 的区别

Array

Array<T> 可以为任何 T 类型存储固定数量的元素。它和 Int 类型参数一起使用, 例如 Array<Int>,编译成 Java 代码,会生成 Integer[] 实例。我们可以通过 arrayOf 方法创建数组。

val arrayOfInts: Array<Int> = arrayOf(1, 2, 3, 4, 5)

IntArray

IntArray 可以让我们使用基础数据类型的数组,编译成 Java 代码,会生成 int[] (其它的基础类型的数组还有 ByteArray , CharArray 等等), 我们可以通过 intArrayOf 工厂方法创建数组。

val intArray: IntArray = intArrayOf(1, 2, 3, 4, 5)

如何选择

默认使用 IntArray,因为它的性能更好,不需要对每个元素进行装箱。IntArray 进行初始化的时候,默认将每个索引的值初始化为 0,代码如下所示。

val intArray = IntArray(10)
val arrayOfInts = Array<Int>(5) { i -> i * 2 }

Array<Int> 的性能比较差,会对每个元素进行装箱,如果你需要创建包含 null 值的数组,Kotlin 也提供了 arrayOfNulls 方法,帮助我们进行创建。

val notActualPeople: Array<Person?> = arrayOfNulls<Person>(13)

七、Iterable 和 Sequence 的区别

Iterable

Iterable 对应 Java 的 java.lang.Iterable, Iterable 会立即处理输入的元素,并返回一个包含结果的新集合。

我们来举一个简单的例子 返回年龄 > 21 前 5 个人的集合

val people: List<Person> = getPeople()
val allowedEntrance = people
		.filter { it.age >= 21 }
		.map { it.name }
		.take(5)
  • 首先通过 filter 函数检查每个人的年龄,将结果放入到一个新的结果集中
  • 通过 map 函数对上一步得到的结果进行名字映射,然后生成一个新的列表 list<String>
  • 通过 take 函数返回前 5 个元素,得到最终的结果集

Sequence

Sequence 是 Kotlin 中一个新的概念,用来表示一个延迟计算的集合。Sequence 只存储操作过程,并不处理任何元素,直到遇到终端操作符才开始处理元素,我们也可以通过 asSequence 扩展函数,将现有的集合转换为 Sequence ,代码如下所示。

val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
	.filter { it.age >= 21 }
	.map { it.name }
	.take(5)
	.toList()

在这个例子中, toList() 表示终端操作符,filtermaptake 都是中间操作符,返回 Sequence 实例,当 Sequence 遇到中间操作符时,只是存储操作过程,并不参与计算,直到遇到 toList()

Sequence 的好处它不会生成中间结果集,直接对原始列表中的每一个人重复这个步骤,直到找到 5 个人,返回最终的结果集。

如何选择

如果数据量比较小,可以使用 Iterable。虽然会创建中间结果集,在数据不大的情况下,对性能的影响不会很严重。

如果处理的数据量比较大,Sequence 是最好的选择,因为不会创建中间结果集,内存开销更小。

八、常用的 For 循环遍历方法

  • 用 .. 关键字,表示左闭右闭区间
  • 用 downTo 关键字,实现降序循环
  • 用 until 关键字,表示左闭右开区间

我们经常会使用以下方法进行遍历。

for (i in 0..args.size - 1) {
	println(args[i])
}

但是 Array 有一个可读性更强的扩展属性 lastIndex

for (i in 0..args.lastIndex) {
	println(args[i])
}

但是实际上我们不需要知道最后一个索引,有一个更加简单的写法。

for (i in 0 until args.size) {
	println(args[i])
}

当然也可以降序遍历。

for (i in args.size downTo 0) {  
     println(args[i])
}  

还有一个更加直接的写法,通过下面的方式直接迭代集合。

for (arg in args) {
	println(arg)
}

区间表达式 ( .. 、 downTo 、 until) 除了创建一些临时变量之外,不会创建额外的对象,但是区间表达式 和 step 关键字结合起来一起使用,就会创建 IntRange 、 IntProgression 对象,会占用更多的内存。

当然你也可以使用下标扩展属性 indices 得到它的范围。

for (i in args.indices) {
	println(args[i])
}

反编译后:  
for(int var4 = array.length; var3 < var4; ++var3) {  

}

通过 indices 遍历数组, 编译之后的代码 ,除了创建了一些临时变量,并没有创建额外的对象。

您也可以使用 forEach 函数,传递一个 lambda 表达式来处理每个元素。

args.forEach { arg ->
	println(arg)
}

反编译后:  
Integer[] var5 = array;  
int var6 = array.length;  
for(int var7 = 0; var7 < var6; ++var7) {  
 Object element$iv = var5[var7];  
 int value = ((Number)element$iv).intValue();  
 boolean var10 = false;  
}

通过 forEach 遍历数组的方式,会创建额外的对象,并且存在装箱/拆箱开销,会占用更多的内存。

另外还有两个遍历的方法:

  • withIndex 函数,它返回一个 Iterable 对象,该对象可以被解构为当前索引和元素。
for ((index, arg) in args.withIndex()) {
    println("$index: $arg")
}

反编译后:  
Integer[] var5 = array;  
int var6 = array.length;  
for(int var3 = 0; var3 < var6; ++var3) {  
 int value = var5[var3];  
}

通过 withIndex 方式遍历数组,虽然不会创建额外的对象,但是存在装箱/拆箱的开销

  • forEachIndexed 函数,它为每个索引和参数提供了一个 lambda 表达式。
args.forEachIndexed { index, arg ->
    println("$index: $arg")
}

如何选择

  • 通过 indices 和区间表达式 ( .. 、 downTo 、 until)  都不会创建额外的对象。
  • 区间表达式 和 step 关键字结合一起使用, 会有创建额外的对象的开销,占用更多的内存。
  • 通过 forEach 遍历数组的方式,会创建额外的对象,占用内存,并且存在装箱 / 拆箱开销。
  • 通过 withIndex 方式遍历数组,不会创建额外的对象,但是存在装箱/拆箱的开销。