Kotlin 数组和集合.md
数组
- 数组在Kotlin中使用
Array<T>
类来表示。 - 基本类型数组:ByteArray,LongArray,xxxArray等这些类是基本类型数组,但是跟Array类没有继承关系。
- 数组的创建
①使用arrayof()函数(Java的静态初始化)
②使用arrayof()函数(Java的动态初始化)
③使用emptyArray()函数
④使用Array(size:Int,init:(Int)->T)构造器
集合
Java的集合
Kotlin的集合
- 集合的创建
①Kotlin的集合分为可变和不可变集合。
②声明并初始化List的集合:使用listOf(..)函数。
③声明并初始化MutableList的集合:使用mutableListOf(..)函数/listOfNotNull()/arrayListOf()。
④声明并初始化Set的集合:使用setOf(..)函数。
⑤声明并初始化MutableSet的集合:使用mutableSetOf(..)函数/hashSetOf()/linkedSetOf()/sortedSetOf()。
⑥不可变的Map类型集合的初始化使用:mapOf()函数。
⑦可变的Map类型集合的初始化使用:mutableMapOf()函数/hashMapOf()/linkedMapOf()/sortedMapOf()。
惰性集合
- 通过序列提供效率 list.asSequence()
- 惰性求值
- 一种在需要时才进行求值的计算方式
- 中间操作
- 末端操作
- 序列可以是无限的
- 序列与Java 8 Stream
欲善其事,必利其器,在Java中集合框架占有很重要的地位,在Kotlin中也同样,想要能够在使用这些集合的时候,能够根据现实中的业务场景,信手拈来,就需要对这些集合有比较全面的了解。本篇将对比Java中的集和框架,对Kotlin中的集合框架进行梳理,首先简单回顾一下Java中的集合框架,接着重点介绍Kotlin中的集合框架,最后介绍Kotlin提供的一些关于集合框架的高阶函数。
Java中的集合框架
集合框架的用途是提供一个容器,用来存储对象,具体使用哪个容器,需要根据业务场景的需求,选择合适的容器。如果从容器的用途这个角度,Java提供了两种容器,Collection和Map,Collection是用来存储单个元素形式对象的集合,Map是用来存储键/值映射这样成对元素的集合。了解了这个后,再来看一下Java集合框架的全家福。
从图中可以看到我们常用的集合如LinkedLlist、ArrayList、HashSet、HashMap等清晰的继承关系,Java将一些对集合常用的方法封装在Collections和Arrays两个类中。集合都可以产生一个迭代器,List还能产生一个ListIterator的迭代器。最后简单对Java的集合框架总结一下:
- Java提供了两种类型的容器,Collection和Map;
- 常用的集合List和Set都是Collection接口的实现类,List存储的对象之间有指定的顺序,存储的对象可以重复,Set存储的对象之间没有指定的顺序,存储的对象不可以重复。
Kotlin中的集合框架
Kotlin中的集合框架,去掉了Java中历史遗留的一些结构,同时集合类型在Kotlin中可以是只读的,也可以是可变的,于是Kotlin中的集合框架就与Java中集合框架有如下的映射关系:
Java中的类型 | Kotlin中的只读类型 | Kotlin中的可变类型 |
---|---|---|
Iterator | Iterator | MutableIterator |
Iterable | Iterable | MutableIterable |
Collection | Collection | MutableCollection |
Set | Set | MutableSet |
List | List | MutableList |
ListIterator | ListIterator | MutableListIterator |
Map<K, V> | Map<K, V> | MutableMap<K, V> |
Map.Entry<K, V> | Map.Entry<K, V> | MutableMap.MutableEntry<K,V> |
相应的给出Kotlin的集合框架图,从图中也可以看出,Kotlin集合中的可变类型都实现了只读类型,同时将一些对集合操作的方法,封装在了_Collections和_Arrays等文件中。由于Kotlin中存在扩展函数,因此在使用这些方法时,不是直接通过文件名+点的方式进行调用,而是就像集合自身的方法一样进行调用。从Java的角度看,就像这些方法是这些集合的成员函数。
了解了Kotlin的集合框架,那么实际用到的集合有哪些呢?在Kotlin collections包中可以找到这样一个文件TypeAliasesKt.class文件。
1 | @file:kotlin.jvm.JvmVersion |
可以看到,我们常用的一些集合如ArrayList、LinkedHashMap、HashMap、LinkedHashSet、HashSet等,在Kotlin中实际上对应的就是Java中的这些集合,Kotlin只是换了个别名而已,这些集合的存储结构与Java中是一样的,所以可以如同在Java中一样的方式使用这些集合。
集合中的高阶函数
前面说到Kotlin将一些对集合的扩展函数都封装在了_Collections、_Arrays这样的文件中,那么究竟有哪些扩展函数呢?了解一下,可以省去写很多工具方法的时间,而且稳定性可能会优于我们自己的工具方法。由于扩展函数数量比较多,这里将按照函数实现的功能,将这些函数分成下面几类。
一 总数函数
1.any 如果有一个元素符合给出的判断条件,返回true*
1 | val list = listOf(1, 2, 3, 4, 5, 6) |
2.all 如果全部的元素符合给出的判断条件,返回true
1 | assertTrue { list.all { it > 0 } } |
3.count 返回符合给出判断条件的元素的个数
1 | assertEquals(3, list.count { it % 2 == 0 }) |
4.fold 在一个初始值的基础上从第一项到最后一项进行累加
1 | assertEquals(220, list.fold(10) { total, next -> total + next * 10 }) |
5.foldRight 与fold类似,但顺序是从最后一项到第一项
1 | assertEquals(31, list.foldRight(10) { total, next -> total + next }) |
6.forEach 遍历所有元素并执行给定操作
1 | list.forEach() { print(it) } |
7.forEachIndexed 与forEach类似,同时可以获取元素的index
1 | list.forEachIndexed() { index, value -> println("position:$index,value:$value") } |
8.max 返回最大一项,如果没有返回null
1 | assertEquals(6, list.max()) |
9.maxBy 根据给定的函数,返回最大一项,如果没有返回null
1 | assertEquals(1, list.maxBy { -it }) |
10.min 返回最小一项,如果没有返回null
1 | assertEquals(1, list.min()) |
11.minBy 根据给定的函数,返回最小的一项,如果没有返回null
1 | assertEquals(6, list.minBy { -it }) |
12.none 如果没有任何元素与给定的函数匹配,返回true
1 | assertTrue { list.none() { it >= 7 } } |
13.reduce 与fold类似,但没有初始值
1 | assertEquals(21, list.reduce() { total, next -> total + next }) |
14.reduceRight 与reduce类似,但顺序是从最后一项到第一项
1 | assertEquals(21, list.reduceRight() { total, next -> total + next }) |
15.sumBy 返回每一项通过函数转换后的数据总和
1 | assertEquals(-21, list.sumBy { -it }) |
二 过滤函数
16.drop 返回包含去掉前n个元素的所有元素的列表
1 | assertEquals(listOf(5, 6), list.drop(4)) |
17.dropWhile 返回去掉满足指定函数要求的,从第一个元素开始的所有元素的列表
1 | assertEquals(listOf(4, 5, 6), list.dropWhile { it < 4 }) |
18.dropLastWhile 返回去掉指定函数要求的,从最后一个元素开始的所有元素的列表
1 | assertEquals(listOf(1, 2, 3, 4), list.dropLastWhile { it > 4 }) |
19.filter 保留所有满足指定函数要求的元素
1 | assertEquals(listOf(2, 4, 6), list.filter { it % 2 == 0 }) |
20.filterNot 过滤掉所有满足指定函数要求的元素
1 | assertEquals(listOf(1, 3, 5), list.filterNot { it % 2 == 0 }) |
21.filterNotNull 保留所有不为null的元素
1 | assertEquals(listOf(1, 2, 3, 4, 5, 6), listwithNull.filterNotNull()) |
22.slice 保留list中指定index的元素
1 | assertEquals(listOf(2, 4, 5), list.slice(listOf(1, 3, 4))) |
23.take 保留从第一个元素开始的n个元素
1 | assertEquals(listOf(1, 2, 3), list.take(3)) |
24.takeLast 保留从最后一个元素开始的n个元素
1 | assertEquals(listOf(4, 5, 6), list.takeLast(3)) |
25.takeWhile 保留从第一个元素开始,满足指定函数的元素
1 | assertEquals(listOf(1, 2), list.takeWhile { it < 3 }) |
三 映射函数
26.flatMap 遍历每一个元素,为每一个元素创建一个集合,最后把所有集合合并为一个集合
1 | print(list)//list:[1,2,3,4,5,6] |
27.groupBy 返回一个根据指定函数分组的map
1 | println(mapOf("odd" to listOf(1, 3, 5))) |
28.map 返回一个,每个元素都按照指定函数进行转换后的集合
1 | assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it -> it * 2 }) |
29.mapIndexed 返回一个,每个元素按照包含元素index的指定函数转换后的集合
1 | assertEquals(listOf(0, 2, 6, 12, 20, 30), list.mapIndexed { it, index -> index * it }) |
30.mapNotNull 返回一个过滤掉null元素,并且非null元素按照指定函数进行转换后的集合
1 | println(listwithNull) |
四 元素操作函数
31.contains 如果指定元素在集合中,返回true
1 | assertTrue(list.contains(1)) |
32.elementAt 返回指定index对应的元素,如果index越界,抛IndexOutOfBoundsException
1 | assertEquals(1, list.elementAt(0)) |
33.elementAtOrElse 返回指定index对应的元素,如果index越界,返回指定函数中设置的默认值
1 | assertEquals(20, list.elementAtOrElse(10, { 2 * it })) |
34.elementAtOrNull 返回指定index对应的元素,如果index越界,返回null
1 | assertNull(list.elementAtOrNull(10)) |
35.first 返回第一个满足指定函数的元素,如果没有,抛出NoSuchElementException
1 | assertEquals(2, list.first() { it % 2 == 0 }) |
36.firstOrNull 返回第一个满足指定函数的元素,如果没有返回null
1 | assertNull(list.firstOrNull() { it < 0 }) |
37.indexOf 返回指定元素的第一个index,如果不存在返回-1
1 | assertEquals(-1, list.indexOf(7)) |
38.indexOfFirst 返回第一个满足指定函数的元素的index,如果不存在返回-1
1 | assertEquals(1, list.indexOfFirst { it % 2 == 0 }) |
39.indexOfLast 返回最后一个满足指定函数的元素的index,如果不存在返回-1
1 | assertEquals(5, list.indexOfLast { it % 2 == 0 }) |
40.last 返回最后一个满足指定函数的元素,如果没有,抛出NoSuchElementException
1 | println(list.last() { it % 2 == 0 }) |
41.lastIndexOf 返回指定元素的最后一个index,如果不存在返回-1
1 | println(listwithNull.lastIndexOf(null)) |
42.lastOrNull 返回最后一个满足指定函数的元素,如果没有返回null
1 | assertNull(list.lastOrNull() { it < 0 }) |
43.single 返回满足指定函数的单个元素,如果没有,或者满足条件的元素个数超过一个,抛出异常
1 | assertEquals(1, list.single() { it < 2 }) |
44.singleOrNull 返回满足指定函数的单个元素,如果没有,或者满足条件的元素个数超过一个,返回null
1 | println(list.singleOrNull() { it % 2 == 0 }) |
五 生产函数
45.partition 将一个给定的集合分割成两个集合,第一个集合是由匹配指定函数,返回true的元素组成。第二个集合是由匹配指定函数,返回false的元素组成。
1 | assertEquals(Pair(listOf(1, 3, 5), listOf(2, 4, 6)), list.partition { it % 2 != 0 }) |
46.plus 返回一个包含原集合和给定集合中所有元素的集合,可以使用“+”操作符
1 | assertEquals(listOf(1, 2, 3, 4, 5, 6, 6, 7, 8), list + listOf(6, 7, 8)) |
47.plusElement 在集合中添加元素
1 | println(list.plusElement(7)) |
48.zip 将两个集合按照下标进行配对,组成的Pair是由两个集合中相同index的元素组成,如果两个集合长度不一致,取短的集合的长度。
1 | assertEquals(listOf(Pair(1, 'x'), Pair(2, 'y')), list.zip(listOf('x', 'y'))) |
49.unzip 作用在包含Pair的集合上,依次取各个Pair的first和second值,放入List和List中,然后返回包含List和List的Pair
1 | val listPair = listOf(Pair(1, 2), Pair('a', 'b'), Pair(3, 4), Pair('c', 'd')) |
六 顺序函数
50.reversed 返回一个与指定list顺序相反的list
1 | println(list) |
51.sorted 升序排序
1 | println(list.sorted()) |
52.sortBy 返回一个按照指定函数变换后的升序排序
1 | println(list.sortedBy { it -> it % 3 }) |
53.sortDescending 降序排序
1 | println(list.sortedDescending()) |
54.sortDescendingBy 返回一个按照指定函数变换后的降序排序
1 | println(list.sortedByDescending { it -> it % 3 }) |
内联函数
什么是内联函数
这个玩意吸取自C++,如下:
内联函数是C++的增强特性之一,用来降低程序的运行时间。当内联函数收到编译器的指示时,即可发生内联:编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段
举个现实中的例子:
- 通常我要骑车到菜市场买菜,然后再骑车回来,经过这样的折腾,最后我终于吃上肉了。
你看,为了买菜,我花费了大量的时间成本! - ok,现在我在自家的院子种了一些菜,想吃的时候直接拔出来吃就行了。
你看,这样我节省了不少时间啦。
代码中考虑:
- 在kotlin中,函数就是对象,当你调用某个函数的时候,就会创建相关的对象。
你看,这就是空间上的开销! - 当你去调用某个函数的时候,虚拟机会去找到你调用函数的位置。然后执行函数,然后再回到你调用的初始位置。
你看,这就是时间上的开销! - ok,现在,我直接把
调用的函数里面的代码
放到我调用的地方,省去寻找调用函数的位置的时间。
在代码中的例子:
- 正常的函数调用:
- 使用内联:
将fun syaHello()
函数标识为内联inline fun sayHello()
,神奇的事情发生了,你的代码会变成如下的模样:
kotlin中如何定义内联函数呢?
很简单,在函数前加入一个标识:inline
就可以了!
注意:
在kotlin中,函数就是对象,我们可以将函数作为参数传递给函数。
所以问题来了,若我们的内联函数中的参数有函数呢?那这个函数是不是内联函数呢?
普通函数的参数中包含了对lambda的声明
当你声明doCal
为内联函数的时候,这个函数所使用的lambda(也就是函数)也默认为内联函数。
当然,我们也可以改变这种行为:
return 在内联函数中的表现
在kotlin中,return 只可以用在有名字的函数,或者匿名函数
中,使得该函数执行完毕。
而针对lambda表达式,你不能直接使用return
你可以使用return+label的形式,将这个lambda结束。
但是
若你的lambda应用在一个内联函数的时候,这时候你可以在lambda中使用return
可以这么理解,内联函数在编译的时候,将相关的代码贴入你调用的地方。
lambda表达式就是一段代码而已,这时候你在lambda中的return,相当于在你调用的方法内return
这么个玩法,可以使用在什么地方?
return直接作用在你使用的地方
举个例子:
我们通常会对数组进行操作,如下
就像执行了你写的代码一样,该返回就返回了,最后代码不会执行println("end")
。
内联属性
对属性来说,我们会有get,set的方法来操作这个属性。
get,set就是个函数,我们可以标识他们为内联函数:
1 | val foo: Foo |
当然,你也可以这么写:
1 | inline var bar: Bar |
其他
指定参数的类型
有这么个例子:
1 | fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? { |
我们是这样使用它的:
1 | treeNode.findParentOfType(MyTreeNode::class.java) |
但是这么写不优雅,我们期望这么写:
1 | treeNode.findParentOfType<MyTreeNode>() |
那么该怎么写?
内联函数支持 具体的类型参数的声明 reified
这么写的:
1 | inline fun <reified T> TreeNode.findParentOfType(): T? { |
可见性修饰符
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 _可见性修饰符_。 (getter 总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:private
、 protected
、 internal
和 public
。 如果没有显式指定修饰符的话,默认可见性是 public
。
以下解释了这些修饰符如何应用到不同类型的声明作用域。
包
函数、属性和类、对象和接口可以在顶层声明,即直接在包内:
1 | // 文件名:example.kt |
- 如果你不指定任何可见性修饰符,默认为
public
,这意味着你的声明将随处可见; - 如果你声明为
private
,它只会在声明它的文件内可见; - 如果你声明为
internal
,它会在相同模块内随处可见; protected
不适用于顶层声明。
注意:要使用另一包中可见的顶层声明,仍需将其导入进来。
例如:
1 | // 文件名:example.kt |
类和接口
对于类内部声明的成员:
private
意味着只在这个类内部(包含其所有成员)可见;protected
—— 和private
一样 + 在子类中可见。internal
—— 能见到类声明的 本模块内 的任何客户端都可见其internal
成员;public
—— 能见到类声明的任何客户端都可见其public
成员。
请注意在 Kotlin 中,外部类不能访问内部类的 private 成员。
如果你覆盖一个 protected
成员并且没有显式指定其可见性,该成员还会是 protected
可见性。
例子:
1 | open class Outer { |
构造函数
要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个显式 constructor 关键字):
1 | class C private constructor(a: Int) { …… } |
这里的构造函数是私有的。默认情况下,所有构造函数都是 public
,这实际上等于类可见的地方它就可见(即 一个 internal
类的构造函数只能在相同模块内可见).
局部声明
局部变量、函数和类不能有可见性修饰符。
模块
可见性修饰符 internal
意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:
- 一个 IntelliJ IDEA 模块;
- 一个 Maven 项目;
- 一个 Gradle 源集(例外是
test
源集可以访问main
的 internal 声明); - 一次 `` Ant 任务执行所编译的一套文件。