上一篇我们学习了Kotlin中的委托,今天继续来学习Kotlin中的集合。集合的内容包含的比较多,分为三篇来学习,今天是学习的集合概述,包括集合分类,定义,以及简单使用。
Kotlin 标准库提供了基本集合类型的实现: set、list 以及 map。 一对接口代表每种集合类型,其中每种集合类型包含了两种实现:
只读集合 (List,Map,Set)可变集合(MutableList,MutableMap,MutableSet)请注意,更改可变集合不需要它是以 var 定义的变量:写操作修改同一个可变集合对象,因此引用不会改变。如下代码所示:
val numbers = mutableListOf("one", "two", "three", "four") //用val修饰的可变集合 numbers.add("five") // 这是可以的只读集合类型是型变的。 这意味着,如果类 Rectangle 继承自 Shape,则可以在需要 List <Shape> 的任何地方使用 List <Rectangle>。 换句话说,集合类型与元素类型具有相同的子类型关系。 map 在值(value)类型上是型变的,但在键(key)类型上不是。但是可变集合不是型变的;否则将导致运行时故障。
为了对Kotlin中基本集合有个更直观了解,我们可以看下Kotlin 基本集合接口的图表:
Collection<T> 是集合层次结构的根。此接口表示一个只读集合的共同行为:检索大小、检测是否为成员等等。 Collection 继承自 Iterable 接口,它定义了迭代元素的操作。可以使用 Collection 作为适用于不同集合类型的函数的参数。但是从源码中我们可以看到Collection是一个接口,所以具体使用集合时请使用 Collection 的继承者: List 与 Set。
... public interface Collection<out E> : Iterable<E> { //... } ...同样MutableCollection也是一个接口类型,但是除了具有Collection的特点,还具有add和remove等写操作。
List<T> 以指定的顺序存储元素,并提供使用索引访问元素的方法。索引从 0 开始 – 第一个元素的索引 – 直到 最后一个元素的索引 即 (list.size - 1)。定义和常用用法如下:
//声明一个只读list val numbers = listOf("one", "two", "three", "four") //或者可以加入泛型声明 //val numbers = listOf<String>("one", "two", "three", "four") //或者可以接收一个大小用来初始化 //val numbers = List(3, { it * 2 }) // 如果你想操作这个集合,应使用 MutableList println(doubled) println("Number of elements: ${numbers.size}")//list的大小 println("Third element: ${numbers.get(2)}")//获取第三个元素 println("Fourth element: ${numbers[3]}")//获取第四个元素 println("Index of element \"two\" ${numbers.indexOf("two")}")//通过元素获取元素下标结果:
Number of elements: 4 Third element: three Fourth element: four Index of element "two" 1List 元素(包括空值)可以重复:List 可以包含任意数量的相同对象或单个对象的出现。 如果两个 List 在相同的位置具有相同大小和相同结构的元素,则认为它们是相等的(需要注意的是这里说的相等时值相等,并不是引用地址相等)。
val bob = Person("Bob", 31) val people = listOf<Person>(Person("Adam", 20), bob, bob) val people2 = listOf<Person>(Person("Adam", 20), Person("Bob", 31), bob) println(people == people2) bob.age = 32 println(people == people2)打印结果:
true falseMutableList 是可以进行写操作的 List,例如用于在特定位置添加或删除元素。在 Kotlin 中,List 的默认实现是 ArrayList,可以将其视为可调整大小的数组。关于对MutableList的写操作我们放到后面再进行统一学习。这里只给出定义:
//不指定size val mutableList = mutableListOf<String>() //或者指定size val mutableList = mutableListOf(5)Set<T> 存储唯一的元素;null 元素也是唯一的:一个 Set 只能包含一个 null。当两个 set 具有相同的大小并且对于一个 set 中的每个元素都能在另一个 set 中存在相同元素,则两个 set 相等。
//只读set定义,同样可以通过泛型来定义,这里将不做展示 val numbers = setOf(1, 2, 3, 4) println("Number of elements: ${numbers.size}") if (numbers.contains(1)) println("1 is in the set") val numbersBackwards = setOf(4, 3, 2, 1) println("The sets are equal: ${numbers == numbersBackwards}")打印结果:
Number of elements: 4 1 is in the set The sets are equal: trueSet的默认实现 - LinkedHashSet – 保留元素插入的顺序。 因此,依赖于顺序的函数,例如 first() 或 last(),会在这些 set 上返回可预测的结果。
val numbers = setOf(1, 2, 3, 4) // LinkedHashSet is the default implementation val numbersBackwards = setOf(4, 3, 2, 1) println(numbers.first() == numbersBackwards.first()) //第一个是1,第二个是4,不相等结果为false println(numbers.first() == numbersBackwards.last())//第一个是1,第二个是1,相等结果为true另一种实现方式 – HashSet – 不声明元素的顺序,所以在它上面调用这些函数会返回不可预测的结果。但是,HashSet 只需要较少的内存来存储相同数量的元素。
MutableSet 是一个带有来自 MutableCollection 的写操作接口的 Set。例如用于在特定位置添加或删除元素。关于对MutableSet 的写操作我们放到后面再进行统一学习。这里只给出定义:
//不指定size val mutableSet = mutableSetOf<String>() //指定size val mutableSet = mutableSetOf(5)Map<K, V> 不是 Collection 接口的继承者;但是它也是 Kotlin 的一种集合类型。 Map 存储 键-值 对(或 条目);键是唯一的,但是不同的键可以与相同的值配对。Map 接口提供特定的函数进行通过键访问值、搜索键和值等操作。
//这里通过中缀表达式来定义只读map val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1) println("All keys: ${numbersMap.keys}")//打印key集合 println("All values: ${numbersMap.values}")//打印value结合打印结果:
All keys: [key1, key2, key3, key4] All values: [1, 2, 3, 1]需要注意的是无论键值对的顺序如何,包含相同键值对的两个 Map 是相等的。
MutableMap 是一个具有写操作的 Map 接口,可以使用该接口添加一个新的键值对或更新给定键的值。关于对MutableMap 的写操作我们放到后面再进行统一学习。这里只给出定义:
//不指定size val mutableMap = mutableMapOf<String,String>() //指定初始元素 val numbersMap = mutableMapOf("one" to 1, "two" to 2)还有用于创建没有任何元素的集合的函数:emptyList()、emptySet() 与 emptyMap()。 创建空集合时,应指定集合将包含的元素类型。
//空集合示例 val emptyList = emptyList<String>() val emptySet = emptySet<String>() val emptyMap = emptyMap<String,String>()在特定时刻通过集合复制函数,例如toList()、toMutableList()、toSet() 等等。创建了集合的快照。 结果是创建了一个具有相同元素的新集合 如果在源集合中添加或删除元素,则不会影响副本。副本也可以独立于源集合进行更改。
val sourceList = mutableListOf(1, 2, 3) val copyList = sourceList.toMutableList() val readOnlyCopyList = sourceList.toList() sourceList.add(4) println("Copy size: ${copyList.size}") //3,向原List中添加元素,并不能影响通过复制函数得到的副本 println("Read-only copy size: ${readOnlyCopyList.size}") //4Iterable<T> 接口的继承者(包括 Set 与 List)可以通过调用 iterator() 函数获得迭代器。 一旦获得迭代器它就指向集合的第一个元素;调用 next() 函数将返回此元素,并将迭代器指向下一个元素(如果下一个元素存在)。 一旦迭代器通过了最后一个元素,它就不能再用于检索元素;也无法重新指向到以前的任何位置。要再次遍历集合,请创建一个新的迭代器。
val numbers = listOf("one", "two", "three", "four") val numbersIterator = numbers.iterator() while (numbersIterator.hasNext()) { println(numbersIterator.next()) }你还可以使用for循环遍历:
val numbers = listOf("one", "two", "three", "four") for (item in numbers) { println(item) }或者forEach()函数遍历:
val numbers = listOf("one", "two", "three", "four") numbers.forEach { println(it) }对于列表,有一个特殊的迭代器实现: ListIterator 它支持列表双向迭代:正向与反向。 反向迭代由 hasPrevious() 和 previous() 函数实现。 此外, ListIterator 通过 nextIndex() 与 previousIndex() 函数提供有关元素索引的信息。
val numbers = listOf("one", "two", "three", "four") val listIterator = numbers.listIterator() println("Iterating nextwards:") while (listIterator.hasNext()) { print("Index: ${listIterator.nextIndex()}") println(", value: ${listIterator.next()}") } println("Iterating backwards:") while (listIterator.hasPrevious()) { print("Index: ${listIterator.previousIndex()}") println(", value: ${listIterator.previous()}") }打印结果:
Iterating nextwards: Index: 0, value: one Index: 1, value: two Index: 2, value: three Index: 3, value: four Iterating backwards: Index: 3, value: four Index: 2, value: three Index: 1, value: two Index: 0, value: one为了迭代可变集合,于是有了 MutableIterator 来扩展 Iterator 使其具有元素删除函数 remove() 。因此,可以在迭代时从集合中删除元素。
val numbers = mutableListOf("one", "two", "three", "four") val mutableIterator = numbers.iterator() mutableIterator.next() mutableIterator.remove() println("After removal: $numbers")打印结果:
After removal: [two, three, four]除了删除元素, MutableListIterator 还可以在迭代列表时插入和替换元素。
val numbers = mutableListOf("one", "four", "four") val mutableListIterator = numbers.listIterator() mutableListIterator.next() mutableListIterator.add("two") mutableListIterator.next() mutableListIterator.set("three") println(numbers)打印结果:
[one, two, three, four]区间实现了一个公共接口:ClosedRange<T>,表示一个数学意义上的闭区间,有两个端点start和end都包含在区间内,主要操作是contains,通常以in和!in操作符形式使用。
要为类创建一个区间,请在区间起始值上调用 rangeTo() 函数,并提供结束值作为参数。 rangeTo() 通常以操作符 … 形式调用。
//一个简单区间定义 val range = 1..100 println(range.contains(1))//true println(10 in range)//true //或者 val versionRange = Version(1, 11)..Version(1, 30) println(Version(0, 9) in versionRange) //false println(Version(1, 20) in versionRange) //true数列具有三个基本属性:first 元素、last 元素和一个非零的 step。 首个元素为 first,后续元素是前一个元素加上一个 step。 以确定的步长在数列上进行迭代等效于 Java中基于索引的 for 循环。
for (int i = first; i <= last; i += step) { // …… }通过迭代数列隐式创建区间时,此数列的 first 与 last 元素是区间的端点,step 为 1 。
for (i in 1..10) print(i)要指定数列步长,请在区间上使用 step 函数。
for (i in 1..8 step 2) print(i)数列的 last 元素是这样计算的:
对于正步长:不大于结束值且满足 (last - first) % step == 0 的最大值。对于负步长:不小于结束值且满足 (last - first) % step == 0 的最小值。因此,last 元素并非总与指定的结束值相同。
也可以用中缀表达式:
for (i in 4 downTo 1) print(i) for (i in 1..8 step 2) print(i) for (i in 0 until 10)print(i)reversed()函数不支持中缀表达式
首先我们来了解一下什么是序列,序列其实类似集合的一个接口,只不过它的操作都是惰性集合操作,所有在集合上的操作符都适用于序列。既然所有在集合上的操作符都适用于序列,为啥还要弄出一个序列,它们有什么区别了?
序列的操作都是惰性集合操作执行的顺序也不同怎么理解第一点了?
当 Iterable 的处理包含多个步骤时,它们会优先执行:每个处理步骤完成并返回其结果——中间集合。 在此集合上执行以下步骤。反过来,序列的多步处理在可能的情况下会延迟执行:仅当请求整个处理链的结果时才进行实际计算。
怎么理解第二点了?
Sequence 对每个元素逐个执行所有处理步骤。 反过来,Iterable 完成整个集合的每个步骤,然后进行下一步。
因此,这些序列可避免生成中间步骤的结果,从而提高了整个集合处理链的性能。 但是,序列的延迟性质增加了一些开销,这些开销在处理较小的集合或进行更简单的计算时可能很重要。 因此,应该同时考虑使用 Sequence 与 Iterable,并确定在哪种情况更适合。
那么什么时候我们应该用序列了?举个栗子:
我们拿到了100W条用户数据,需要将这些用户年龄是偶数的姓名全打印出来。如果不使用序列,我们的做法为:
println(userList .filter { it.age % 2 != 0 } .map { User::age })这个写法肯定没有错,对于少量数据来说也没什么影响,可是这里大家注意,是100W条数据,我们可以点击map和filter查看源码,每一步操作都会生成另外一个集合,对于大量数据来说,这可是一笔很大的消耗,在性能上是很不理想的。这时候就到了我们序列大显身手的时候了,来看看序列是如何减少性能消耗的:
println(userList.asSequence() .filter { it.age % 2 != 0 } .map { User::age } .toList())这里我们先将集合转换为序列,在最后的操作中才将序列转换为集合的。序列在中间操作都是惰性的,不会创建额外的集合来保存过程中产生的中间结果,使用序列可以高效的对集合元素执行链式操作。
今天的学习笔记就先到这里了,下篇我们将继续学习Kotlin中的集合公共操作。 老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!