Kotlin: 数据结构篇
Admin Lv3

单独的语法就如同无米之炊,Kotlin内建了不少数据结构供开发者使用

与Java的互操作性

由于Java已经有了成熟的集合框架,Kotlin直接将其拿过来做了一层包裹,增加了一些新功能,形成了自己的集合框架: kotlin.collections

类型层次结构

Kotlin集合框架中的数据类型可分为两种,一是不可更改其储存值的不可变类,二是可更改其值的可变类。两组类型镜像对称构成了整体的层次结构

Map
Iterable
Collection
List
Set
MutableMap
MutableIterable
MutableCollection
MutableList
MutableSet

不可变类虽然缺少更改操作,但防止了值不小心改变以及保证了线程安全;可变类拥有更多的操作,但需要额外的开销(获取和释放锁)来去保证线程安全;总之,两者各有利弊

初始化

这些集合类型通过Kotlin集合框架里的xxxOf静态方法进行初始化

1
2
val x = listOf(1, 2, 3)
val y = mutableMapOf(1 to 'A', 2 to 'B')

集合运算

加减元素以及合并可以通过基本的运算符来实现

1
2
3
4
5
6
7
8
( listOf(1,2) + 3 ).equals( listOf(1,2,3) ) // true
( listOf(1,2,2) - 2 ).equals( listOf(1,2) ) // true

( mapOf(1 to 'A') + (2 to 'B') ).equals( mapOf(1 to 'A', 2 to 'B') ) // true
( mapOf(1 to 'A') - 1 ).equals( mapOf() ) // true

( listOf(1,2) + listOf(3,4) ).equals( listOf(1,2,3,4) ) // true
( mapOf(1 to 'A') + mapOf(2 to 'B') ).equals( mapOf(1 to 'A', 2 to 'B') ) // true

值得注意的是这些运算会产生新的对象,不改变已有的对象

泛函编程

不同于围绕对象式编程,泛函编程将计算机中的运算视为数学上的函数运算,避免去改变对象的值。泛函指的是以函数为输入的一类特殊数学函数,Kotlin支持一系列泛函变幻方法来方便将函数作用到集合类型上,相比于for语句和forEach方法,这些变幻方法有着更高的可读性且保证线程安全

布尔变幻

1
2
3
listOf(1,3).any { it % 3 == 0 } // 是否任何元素是3的倍数,it指代函数唯一的参数
listOf(1,3).all { it % 3 == 0 } // 是否全是3的倍数
listOf(1,3).none { it % 3 == 0 } // 是否没有3的倍数

映射变幻

1
2
3
4
listOf(1,3).map{ it * 2 }.equals( listOf(2,6) ) // true
listOf(1,3,null).mapNotNull{ it * 2 }.equals( listOf(2,6) ) // true
listOf(1,3).mapIndexed { (index, elem) -> index * elem } // true
listOf( listOf(1,2), listOf(2,3) ).flatMap{ it }.equals( listOf(1,2,2,3) ) // true

筛子变幻

1
2
3
listOf(1,2,3).filter { it % 3 == 0 } // 只保留3的倍数
listOf(1,2,3).filterNot { it % 3 == 0 } // 过滤掉3的倍数
listOf(1,2,null).filterNotNull() // 只保留非空元素

累加变幻

1
2
listOf(1,2,3).reduce { acc, item -> acc * item } // 6
listOf().reduceOrNull { acc, item -> acc * item } // null

分组变幻

1
2
3
4
5
6
7
8
listOf(1,2,3,4).groupBy{ 
it % 2 == 0 ? "even" : "odd"
}.equals(
mapOf(
"even" to listOf(2,4),
"odd" to listOf(1,3)
)
) // true

序列

上述这些方法都会强制去将输入的函数作用在每个元素上,但有些情形,比如下载第一个符合条件的URL,只需要用到一部分元素。序列便用于解决这类问题,其只在需要的时候才去将函数作用在元素上

1
2
3
4
5
6
7
8
9
10
11
sequenceOf("https://example.com", "https://web.archive.org", "https://letsencrypt.org").map {
fetchPage(it)
}.first {
responseCodeIs200(it)
} // 只会fetch第一个url

listOf("https://example.com", "https://web.archive.org", "https://letsencrypt.org").map {
fetchPage(it)
}.first {
responseCodeIs200(it)
} // 会fetch所有url