上一篇我们学习了Kotlin中的委托,今天继续来学习Kotlin中的集合。集合的内容包含的比较多,分为三篇来学习,今天是学习的集合概述,包括集合分类,定义,以及简单使用。
Kotlin 标准库提供了基本集合类型的实现: set、list 以及 map。 一对接口代表每种集合类型,其中每种集合类型包含了两种实现:
请注意,更改可变集合不需要它是以 var 定义的变量:写操作修改同一个可变集合对象,因此引用不会改变。如下代码所示:
val numbers = mutableListOf("one", "two", "three", "four") //用val修饰的可变集合
numbers.add("five") // 这是可以的
只读集合类型是型变的。 这意味着,如果类 Rectangle 继承自 Shape,则可以在需要 List
为了对Kotlin中基本集合有个更直观了解,我们可以看下Kotlin 基本集合接口的图表:
Collection
...
public interface Collection<out E> : Iterable<E> {
//...
}
...
同样MutableCollection也是一个接口类型,但是除了具有Collection的特点,还具有add和remove等写操作。
List
//声明一个只读list
val numbers = listOf("one", "two", "three", "four")
//或者可以加入泛型声明
//val numbers = listOf("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" 1
List 元素(包括空值)可以重复: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
false
MutableList 是可以进行写操作的 List,例如用于在特定位置添加或删除元素。在 Kotlin 中,List 的默认实现是 ArrayList,可以将其视为可调整大小的数组。关于对MutableList的写操作我们放到后面再进行统一学习。这里只给出定义:
//不指定size
val mutableList = mutableListOf<String>()
//或者指定size
val mutableList = mutableListOf(5)
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: true
Set的默认实现 - 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
//这里通过中缀表达式来定义只读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}") //4
Iterable
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
要为类创建一个区间,请在区间起始值上调用 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 元素并非总与指定的结束值相同。
var ascRange = 0.rangeTo(10) //相当于 0..10
var desRange = 10.downTo(0) //相当于 10..0
desRange = desRange.reversed() //翻转区间等同于ascRange
var dwonRange = 10.downTo(0).step(3)
for(r in dwonRange){ //输出结果:10,7,4,1
println(r)
}
println("-----------------")
var untilRange = 0.until(5)
for(r in untilRange ){//输出结果:0,1,2,3,4
println(r)
}
也可以用中缀表达式:
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,并确定在哪种情况更适合。
//由元素
val numbersSequence = sequenceOf("four", "three", "two", "one")
//由集合(Iterable)
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
//由函数generateSequence()
val oddNumbers = generateSequence(1) { it + 2 } // `it` 是上一个元素
println(oddNumbers.take(5).toList())
//由组块
val oddNumbers = sequence {
yield(1)
yieldAll(listOf(3, 5))
yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList())
那么什么时候我们应该用序列了?举个栗子:
我们拿到了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中的集合公共操作。
老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!