数组

  1. 数组在Kotlin中使用Array<T>类来表示。
  2. 基本类型数组:ByteArray,LongArray,xxxArray等这些类是基本类型数组,但是跟Array类没有继承关系。
  3. 数组的创建
    ①使用arrayof()函数(Java的静态初始化)
    ②使用arrayof()函数(Java的动态初始化)
    ③使用emptyArray()函数
    ④使用Array(size:Int,init:(Int)->T)构造器

集合

Java的集合

Kotlin的集合

  1. 集合的创建
    ①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的集合框架总结一下:

  1. Java提供了两种类型的容器,Collection和Map;
  2. 常用的集合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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@file:kotlin.jvm.JvmVersion
package kotlin.collections

@SinceKotlin("1.1") public typealias RandomAccess = java.util.RandomAccess

@SinceKotlin("1.1") public typealias ArrayList<E> = java.util.ArrayList<E>
@SinceKotlin("1.1") public typealias LinkedHashMap<K, V> = java.util.LinkedHashMap<K, V>
@SinceKotlin("1.1") public typealias HashMap<K, V> = java.util.HashMap<K, V>
@SinceKotlin("1.1") public typealias LinkedHashSet<E> = java.util.LinkedHashSet<E>
@SinceKotlin("1.1") public typealias HashSet<E> = java.util.HashSet<E>

// also @SinceKotlin("1.1")
internal typealias SortedSet<E> = java.util.SortedSet<E>
internal typealias TreeSet<E> = java.util.TreeSet<E>
复制代码

可以看到,我们常用的一些集合如ArrayList、LinkedHashMap、HashMap、LinkedHashSet、HashSet等,在Kotlin中实际上对应的就是Java中的这些集合,Kotlin只是换了个别名而已,这些集合的存储结构与Java中是一样的,所以可以如同在Java中一样的方式使用这些集合。

集合中的高阶函数

前面说到Kotlin将一些对集合的扩展函数都封装在了_Collections、_Arrays这样的文件中,那么究竟有哪些扩展函数呢?了解一下,可以省去写很多工具方法的时间,而且稳定性可能会优于我们自己的工具方法。由于扩展函数数量比较多,这里将按照函数实现的功能,将这些函数分成下面几类。

一 总数函数

1.any 如果有一个元素符合给出的判断条件,返回true*

1
2
3
4
val list = listOf(1, 2, 3, 4, 5, 6)
val listwithNull = listOf(1, 2, 3, 4, null, 5, null, 6, null)
assertTrue(list.any { it % 2 == 0 })
复制代码

2.all 如果全部的元素符合给出的判断条件,返回true

1
2
assertTrue { list.all { it > 0 } }
复制代码

3.count 返回符合给出判断条件的元素的个数

1
2
assertEquals(3, list.count { it % 2 == 0 })
复制代码

4.fold 在一个初始值的基础上从第一项到最后一项进行累加

1
2
assertEquals(220, list.fold(10) { total, next -> total + next * 10 })
复制代码

5.foldRight 与fold类似,但顺序是从最后一项到第一项

1
2
assertEquals(31, list.foldRight(10) { total, next -> total + next })
复制代码

6.forEach 遍历所有元素并执行给定操作

1
2
list.forEach() { print(it) }
复制代码

7.forEachIndexed 与forEach类似,同时可以获取元素的index

1
2
list.forEachIndexed() { index, value -> println("position:$index,value:$value") }
复制代码

8.max 返回最大一项,如果没有返回null

1
2
assertEquals(6, list.max())
复制代码

9.maxBy 根据给定的函数,返回最大一项,如果没有返回null

1
2
assertEquals(1, list.maxBy { -it })
复制代码

10.min 返回最小一项,如果没有返回null

1
2
assertEquals(1, list.min())
复制代码

11.minBy 根据给定的函数,返回最小的一项,如果没有返回null

1
2
assertEquals(6, list.minBy { -it })
复制代码

12.none 如果没有任何元素与给定的函数匹配,返回true

1
2
assertTrue { list.none() { it >= 7 } }
复制代码

13.reduce 与fold类似,但没有初始值

1
2
assertEquals(21, list.reduce() { total, next -> total + next })
复制代码

14.reduceRight 与reduce类似,但顺序是从最后一项到第一项

1
2
assertEquals(21, list.reduceRight() { total, next -> total + next })
复制代码

15.sumBy 返回每一项通过函数转换后的数据总和

1
2
assertEquals(-21, list.sumBy { -it })
复制代码

二 过滤函数

16.drop 返回包含去掉前n个元素的所有元素的列表

1
2
assertEquals(listOf(5, 6), list.drop(4))
复制代码

17.dropWhile 返回去掉满足指定函数要求的,从第一个元素开始的所有元素的列表

1
2
assertEquals(listOf(4, 5, 6), list.dropWhile { it < 4 })
复制代码

18.dropLastWhile 返回去掉指定函数要求的,从最后一个元素开始的所有元素的列表

1
2
assertEquals(listOf(1, 2, 3, 4), list.dropLastWhile { it > 4 })
复制代码

19.filter 保留所有满足指定函数要求的元素

1
2
assertEquals(listOf(2, 4, 6), list.filter { it % 2 == 0 })
复制代码

20.filterNot 过滤掉所有满足指定函数要求的元素

1
2
assertEquals(listOf(1, 3, 5), list.filterNot { it % 2 == 0 })
复制代码

21.filterNotNull 保留所有不为null的元素

1
2
assertEquals(listOf(1, 2, 3, 4, 5, 6), listwithNull.filterNotNull())
复制代码

22.slice 保留list中指定index的元素

1
2
assertEquals(listOf(2, 4, 5), list.slice(listOf(1, 3, 4)))
复制代码

23.take 保留从第一个元素开始的n个元素

1
2
assertEquals(listOf(1, 2, 3), list.take(3))
复制代码

24.takeLast 保留从最后一个元素开始的n个元素

1
2
assertEquals(listOf(4, 5, 6), list.takeLast(3))
复制代码

25.takeWhile 保留从第一个元素开始,满足指定函数的元素

1
2
assertEquals(listOf(1, 2), list.takeWhile { it < 3 })
复制代码

三 映射函数

26.flatMap 遍历每一个元素,为每一个元素创建一个集合,最后把所有集合合并为一个集合

1
2
3
4
print(list)//list:[1,2,3,4,5,6]
//[1,2],[2,3],[3,4],[4,5],[5,6],[6,7] => [1,2,2,3,3,4,4,5,5,6,6,7]
println(list.flatMap { listOf(it, it + 1) })
复制代码

27.groupBy 返回一个根据指定函数分组的map

1
2
3
4
println(mapOf("odd" to listOf(1, 3, 5)))
println(mapOf("even" to listOf(2, 4, 6)))
println(list.groupBy { if (it % 2 == 0) "even" else "odd" })
复制代码

28.map 返回一个,每个元素都按照指定函数进行转换后的集合

1
2
assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it -> it * 2 })
复制代码

29.mapIndexed 返回一个,每个元素按照包含元素index的指定函数转换后的集合

1
2
assertEquals(listOf(0, 2, 6, 12, 20, 30), list.mapIndexed { it, index -> index * it })
复制代码

30.mapNotNull 返回一个过滤掉null元素,并且非null元素按照指定函数进行转换后的集合

1
2
3
println(listwithNull)
println(listwithNull.mapNotNull { it })
复制代码

四 元素操作函数

31.contains 如果指定元素在集合中,返回true

1
2
assertTrue(list.contains(1))
复制代码

32.elementAt 返回指定index对应的元素,如果index越界,抛IndexOutOfBoundsException

1
2
assertEquals(1, list.elementAt(0))
复制代码

33.elementAtOrElse 返回指定index对应的元素,如果index越界,返回指定函数中设置的默认值

1
2
assertEquals(20, list.elementAtOrElse(10, { 2 * it }))
复制代码

34.elementAtOrNull 返回指定index对应的元素,如果index越界,返回null

1
2
assertNull(list.elementAtOrNull(10))
复制代码

35.first 返回第一个满足指定函数的元素,如果没有,抛出NoSuchElementException

1
2
assertEquals(2, list.first() { it % 2 == 0 })
复制代码

36.firstOrNull 返回第一个满足指定函数的元素,如果没有返回null

1
2
assertNull(list.firstOrNull() { it < 0 })
复制代码

37.indexOf 返回指定元素的第一个index,如果不存在返回-1

1
2
assertEquals(-1, list.indexOf(7))
复制代码

38.indexOfFirst 返回第一个满足指定函数的元素的index,如果不存在返回-1

1
2
assertEquals(1, list.indexOfFirst { it % 2 == 0 })
复制代码

39.indexOfLast 返回最后一个满足指定函数的元素的index,如果不存在返回-1

1
2
assertEquals(5, list.indexOfLast { it % 2 == 0 })
复制代码

40.last 返回最后一个满足指定函数的元素,如果没有,抛出NoSuchElementException

1
2
println(list.last() { it % 2 == 0 })
复制代码

41.lastIndexOf 返回指定元素的最后一个index,如果不存在返回-1

1
2
println(listwithNull.lastIndexOf(null))
复制代码

42.lastOrNull 返回最后一个满足指定函数的元素,如果没有返回null

1
2
assertNull(list.lastOrNull() { it < 0 })
复制代码

43.single 返回满足指定函数的单个元素,如果没有,或者满足条件的元素个数超过一个,抛出异常

1
2
assertEquals(1, list.single() { it < 2 })
复制代码

44.singleOrNull 返回满足指定函数的单个元素,如果没有,或者满足条件的元素个数超过一个,返回null

1
2
println(list.singleOrNull() { it % 2 == 0 })
复制代码

五 生产函数

45.partition 将一个给定的集合分割成两个集合,第一个集合是由匹配指定函数,返回true的元素组成。第二个集合是由匹配指定函数,返回false的元素组成。

1
2
assertEquals(Pair(listOf(1, 3, 5), listOf(2, 4, 6)), list.partition { it % 2 != 0 })
复制代码

46.plus 返回一个包含原集合和给定集合中所有元素的集合,可以使用“+”操作符

1
2
assertEquals(listOf(1, 2, 3, 4, 5, 6, 6, 7, 8), list + listOf(6, 7, 8))
复制代码

47.plusElement 在集合中添加元素

1
2
println(list.plusElement(7))
复制代码

48.zip 将两个集合按照下标进行配对,组成的Pair是由两个集合中相同index的元素组成,如果两个集合长度不一致,取短的集合的长度。

1
2
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
2
3
4
val listPair = listOf(Pair(1, 2), Pair('a', 'b'), Pair(3, 4), Pair('c', 'd'))
println(listPair)
println(listPair.unzip())
复制代码

六 顺序函数

50.reversed 返回一个与指定list顺序相反的list

1
2
3
println(list)
println(list.reversed())
复制代码

51.sorted 升序排序

1
2
println(list.sorted())
复制代码

52.sortBy 返回一个按照指定函数变换后的升序排序

1
2
println(list.sortedBy { it -> it % 3 })
复制代码

53.sortDescending 降序排序

1
2
println(list.sortedDescending())
复制代码

54.sortDescendingBy 返回一个按照指定函数变换后的降序排序

1
println(list.sortedByDescending { it -> it % 3 })

内联函数

什么是内联函数

这个玩意吸取自C++,如下:

内联函数是C++的增强特性之一,用来降低程序的运行时间。当内联函数收到编译器的指示时,即可发生内联:编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段

举个现实中的例子:

  1. 通常我要骑车到菜市场买菜,然后再骑车回来,经过这样的折腾,最后我终于吃上肉了。
    你看,为了买菜,我花费了大量的时间成本!
  2. ok,现在我在自家的院子种了一些菜,想吃的时候直接拔出来吃就行了。
    你看,这样我节省了不少时间啦。

代码中考虑:

  1. 在kotlin中,函数就是对象,当你调用某个函数的时候,就会创建相关的对象。
    你看,这就是空间上的开销!
  2. 当你去调用某个函数的时候,虚拟机会去找到你调用函数的位置。然后执行函数,然后再回到你调用的初始位置。
    你看,这就是时间上的开销!
  3. ok,现在,我直接把调用的函数里面的代码放到我调用的地方,省去寻找调用函数的位置的时间。

在代码中的例子:

  1. 正常的函数调用:
  2. 使用内联:
    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
2
3
4
5
6
val foo: Foo
inline get() = Foo()

var bar: Bar
get() = ...
inline set(v) { ... }

当然,你也可以这么写:

1
2
3
inline var bar: Bar
get() = ...
set(v) { ... }

其他

指定参数的类型
有这么个例子:

1
2
3
4
5
6
7
8
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p.parent
}
@Suppress("UNCHECKED_CAST")
return p as T?
}

我们是这样使用它的:

1
treeNode.findParentOfType(MyTreeNode::class.java)

但是这么写不优雅,我们期望这么写:

1
treeNode.findParentOfType<MyTreeNode>()

那么该怎么写?
内联函数支持 具体的类型参数的声明 reified
这么写的:

1
2
3
4
5
6
7
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}

可见性修饰符

类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 _可见性修饰符_。 (getter 总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:privateprotectedinternalpublic。 如果没有显式指定修饰符的话,默认可见性是 public

以下解释了这些修饰符如何应用到不同类型的声明作用域。

函数、属性和类、对象和接口可以在顶层声明,即直接在包内:

1
2
3
4
5
// 文件名:example.kt
package foo

fun baz() { …… }
class Bar { …… }
  • 如果你不指定任何可见性修饰符,默认为 public,这意味着你的声明将随处可见;
  • 如果你声明为 private,它只会在声明它的文件内可见;
  • 如果你声明为 internal,它会在相同模块内随处可见;
  • protected 不适用于顶层声明。

注意:要使用另一包中可见的顶层声明,仍需将其导入进来。

例如:

1
2
3
4
5
6
7
8
9
// 文件名:example.kt
package foo

private fun foo() { …… } // 在 example.kt 内可见

public var bar: Int = 5 // 该属性随处可见
private set // setter 只在 example.kt 内可见

internal val baz = 6 // 相同模块内可见

类和接口

对于类内部声明的成员:

  • private 意味着只在这个类内部(包含其所有成员)可见;
  • protected—— 和 private一样 + 在子类中可见。
  • internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
  • public —— 能见到类声明的任何客户端都可见其 public 成员。

请注意在 Kotlin 中,外部类不能访问内部类的 private 成员。

如果你覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可见性。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // 默认 public

protected class Nested {
public val e: Int = 5
}
}

class Subclass : Outer() {
// a 不可见
// b、c、d 可见
// Nested 和 e 可见

override val b = 5 // “b”为 protected
}

class Unrelated(o: Outer) {
// o.a、o.b 不可见
// o.c 和 o.d 可见(相同模块)
// Outer.Nested 不可见,Nested::e 也不可见
}

构造函数

要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个显式 constructor 关键字):

1
class C private constructor(a: Int) { …… }

这里的构造函数是私有的。默认情况下,所有构造函数都是 public,这实际上等于类可见的地方它就可见(即 一个 internal 类的构造函数只能在相同模块内可见).

局部声明

局部变量、函数和类不能有可见性修饰符。

模块

可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:

  • 一个 IntelliJ IDEA 模块;
  • 一个 Maven 项目;
  • 一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
  • 一次 `` Ant 任务执行所编译的一套文件。