Skip to content

集合

1.前言

集合是用于处理一组对象的容器。常见的集合有三种类型。列表类Set类Map类

2.线性容器

线性的容器,特点是以相对顺序存储同一类型的对象。有一个整数索引(index)来表示其相对的位置。查找性能差,代表为数组。

3.数组

最简单也是使用范围最广泛的线性容器。最大的弊端就是长度是固定的。长度在创建数组时就确定了,无法改变。

4.复杂数组

数组的元素是一个Collection,而不是常见的基本数据类型。

kotlin
val carray = arrayof<MutableList<Int>> {
    mutableListof(),
    mutableListof()
}

val narray = Array<MultableList<Int>> {10} {mutableListOf()}

其关键在于要声明元素的类型,其他与基本数据类型的数组是一样的。另外,如果数组数量比较少,方便直接写。那就用字面构造函数,或者数组元素的构造方法。

5.多维数组

最常见的是二维数组

kotlin
val smatrix = arrayOf(
    arrayOf(1,2,3),
    arrayOf(4,5,6),
    arrayOf(7,8,9)
)

val matrix = Array(5) {IntArray(6)}

6.Ranges

用于表示区间的表达式,这一特点与python非常像,最为直观的理解就是数组的索引,用操作符..来表示区间。比如0~9 就是 0..9,通常用于for~loop中:

kotlin
if(i in 1..4){
    println(i)
}

for(i in 1..4){
    println(i)
}

还可以指定步长和边界以及方向

kotlin
for(i in 0 until 10){
    println(i)
}

for(i in 0 until 10 step 2){
    println(i)
}

// for(int i = 0; i < 10; i += 2)

for(i in 9 downTo 0){
    println(i)
}

// 还可以用于字符
for (c in 'a'..'z') { // for (char c = 'a'; c <= 'z'; c++)
    print(c)
}

Range是一个表达式,所以在其之上做其他操作,但需要注意这时需要加上括号,比如:

kotlin
    for (i in (0..9).filter {it % 2 == 0 }) {
        println(i) // only evens
    }
    for (c in ('a'..'z').map { it.toUpperCase() }) {
        println(c) // upper case
    }

需要注意,虽然Ranges方便操作数组的索引,但如果想要带着索引遍历数组的话,还是要用专用的遍历方式,而不是用Range,比如:

kotlin
for ((index, value) in array.withIndex()) {
    println("the element: [$index] = $value")
}

注意与repeat的区别 Ranges是一个数据结构代表着一个区间,这个区间可能是一个整数范围,也可能是一个字符范围,其实也可以是其他自定义数据类型,只要能表达 出区间的概念。只不过整数区间是为常用的一种方式,以及整数区间可以方便当作数组和列表的索引。

但有时如果仅仅想重复一件事情n次,那就没有必要用Ranges,虽然它也可以,这时最为方便的是函数repeat,它与区间的唯一区别是repeat是没有返回值的,它仅是把一件事情重复n次,但没有返回值也就是说没有办法再转化为其他数组或者列表。

kotlin
repeat(10) { println("repeat # $it") }
//repeat # 0
//repeat # 1
//repeat # 2
//repeat # 3
//repeat # 4
//repeat # 5
//repeat # 6
//repeat # 7
//repeat # 8
//repeat # 9

而比如Ranges是可以转化为其他数组和列表的:

kotlin
(0 until 5).map { it * it }.toIntArray()
// [0, 1, 4, 9, 16]

列表List 列表可以简单理解为无限长的数组,它最大的特点是长度不固定,不必事先定好长度,它会随着添加元素而自动增长。所以,当你事先不知道容器的长度时,就需要用List。它是一个泛型,其余操作与数组一样。

kotlin
val names = listOf("James", "Donald", "Kevin", "George")
names.map { it.toUpper() }
 .forEach { println(it) }

序列Sequence 序列与列表比较难区分,直观上它们是一样的。简单来说它并不是容器,它并不持有对象,它生产对象,类似于物理上的信号发射器和RxJava中的Observable,是有时序上的概念的,当你需要时它就生产出来一个元素。

队列queue 队列可以用双端队列deque(读作dek),具体实现对象是 ArrayDeque T。

双端队列是强大的数据结构,即可以用作队列,也可以用作栈。

Set容器 Set是一个不含有重复元素的容器,特点是不会保存相对顺序,可以快速实现检索。

kotlin
    val names = setOf("James", "Harden", "Donald", "Joe")
    for (nm in names) {
        println(nm)
    }
    names.filter { it.length > 4 }
        .forEach { println(it) }

Map容器 由映射键->值对组成的二维容器,键不可重复,值可以重复,不会保存相对顺序,也可以用于快速检索。

kotlin
    val nameMap = mapOf("James" to 15, "Harden" to 30, "Donald" to 80, "Joe" to 86)
    for (nm in nameMap.keys) {
        println(nm)
    }
    for (age in nameMap.values) {
        println(age)
    }
    for (e in nameMap.entries) {
        println("${e.key} is ${e.value}")
    }
    nameMap.filter { it.key.length > 5 }
        .forEach { println("${it.key} = ${it.value}") }

注意Immutability 有一个地方需要特别注意,那就是容器的不可变性Immutability,用常规的方法创建的集合对象是不可变的Immutable,就是无法向其中添加元素也无法删除元素。对象的不可变Immutable在函数式编程中是很重要的特性可以有效的减少异步和并发带来的状态一致性问题。

kotlin
val names = listOf("James", "Donald", "Kevin", "George")
names.add("Paul") // compile error, names is immutable
names.map { it.toUpper() }
 .forEach { println(it) }

这样写会有编译错误,因为用listOf创建的列表对象是不可变的Immutable。如果想要改变就必须用支持更改的对象,如MutableList, MutableSet和MutableMap,如:

kotlin
val names = mutableListOf("James", "Donald", "Kevin", "George")
names.add("Paul") // okay
names.map { it.toUpper() }
 .forEach { println(it) }

如果有可能还是要尽可能的用不可变对象(Immutable objects)

集合的操作 集合的操作就是函数式的三板斧过滤filter,转化map和折叠化约fold/reduce,前面讲的所有的容器都是支持的,结合lambdas可以写出非常规范的函数式代码。

Released under the MIT License.