Kotlin学习笔记
变量
val
定义只读变量,只能为其赋值1次var
定义可变变量
交换变量
var a = 1
var b = 2
a = b.also { b = a }
条件结构
if
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
if
块也可以做表达式
fun maxOf(a: Int, b: Int) = if (a > b) a else b
if
的分支可以是代码块,最后的表达式作为该块的值
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
when
when 表达式取代了类 C 语言的 switch 语句
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}
when
将它的参数与所有的分支条件顺序比较,直到某个分支满足条件。 when
既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。(像 if
一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。)
如果其他分支都不满足条件将会求值 else
分支。 如果 when 作为一个表达式使用,则必须有 else
分支, 除非编译器能够检测出所有的可能情况都已经覆盖了
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
可以用任意表达式(而不只是常量)作为分支条件
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
也可以检测一个值在 in
或者不在 !in
一个区间或者集合中
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
空值类型
变量的值可以允许为空
fun parseInt(str: String): Int? {
// ……
}
var str: String? = null
可以进行空值检测
if (a != null) {
// 在此条件分支内,a的类型为 `Int` 而不是 `Int?`
println("a = $a is not null")
}
同理,对于任意类型也可以进行检测
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`
return obj.length
}
// 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
return null
}
缩写
val files = File("Test").listFiles()
println(files?.size) // if not null
println(files?.size ?: "empty") // if not null ans else
files?.let {...} // if not null
循环结构
repeat 循环 repeat(n) {...}
循环n次
for 循环
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
while 循环
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y 在此处可见
函数
Kotlin 应用程序的入口为 main 函数
fun main() {
println("Hello world!")
}
函数可以携带参数,并且声明返回值
fun sum(a: Int, b: Int): Int {
return a + b
}
可以将表达式作为函数体,此时函数会自动推断返回类型
fun sum(a: Int, b: Int) = a + b
没有返回值或返回无意义的值 fun sayHello(): Unit {}
并且可以省略 fun sayHello() {}
默认参数
函数参数可以有默认值,当省略相应的参数时使用默认值。
fun read(
b: Array<Byte>,
off: Int = 0,
len: Int = b.size,
) { /*……*/ }
如果一个默认参数在一个无默认值的参数之前,那么该默认值只能通过使用具名参数调用该函数来使用
fun foo(
bar: Int = 0,
baz: Int,
) { /*……*/ }
foo(baz = 1) // 使用默认值 bar = 0
覆盖方法总是使用与基类型方法相同的默认参数值。 当覆盖一个带有默认参数值的方法时,必须从签名中省略默认参数值
open class A {
open fun foo(i: Int = 10) { /*……*/ }
}
class B : A() {
override fun foo(i: Int) { /*……*/ } // 不能有默认值
}
可变参数
函数的参数(通常是最后一个)可以用 vararg
修饰符标记,表示该参数为可变数量的参数
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
只有一个参数可以标注为 vararg
。如果 vararg
参数不是列表中的最后一个参数, 可以使用具名参数语法传递其后的参数的值,或者,如果参数具有函数类型,则通过在括号外部传一个 Lambda。
当我们调用 vararg
函数时,我们可以一个接一个地传参,例如 asList(1, 2, 3)
,或者,如果我们已经有一个数组并希望将其内容传给该函数,我们使用伸展(spread)操作符(在数组前面加 *
)
val list1 = asList(1, 2, 3)
val a = arrayOf(1, 2, 3)
val list2 = asList(-1, 0, *a, 4)
尾递归
Kotlin 支持一种称为尾递归的函数式编程风格。 这允许一些通常用循环写的算法改用递归函数来写,而无堆栈溢出的风险。 当一个函数用 tailrec
修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本:
// 最大公因数
tailrec fun gcd(a: Int, b: Int): Int = if (a > 0) gcd(b % a, a) else b
优化前后对比
// 优化前
private final int gcd(int a, int b) {
return a > 0 ? this.gcd(b % a, a) : b;
}
// 优化后
private final int gcd(int a, int b) {
while(a > 0) {
int var10000 = b % a;
b = a;
a = var10000;
}
return b;
}
要符合 tailrec
修饰符的条件的话,函数必须将其自身调用作为它执行的最后一个操作。在递归调用后有更多代码时,不能使用尾递归,并且不能用在 try/catch/finally 块中。
高阶函数
高阶函数是将函数用作参数或返回值的函数。
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
函数类型
Kotlin 使用类似 (Int) -> String
的一系列函数类型来处理函数的声明: val onClick: () -> Unit = ……
。
这些类型具有与函数签名相对应的特殊表示法,即它们的参数和返回值:
所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:
(A, B) -> C
表示接受类型分别为A
与B
两个参数并返回一个C
类型值的函数类型。 参数类型列表可以为空,如() -> A
。Unit
返回类型不可省略。函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型
A.(B) -> C
表示可以在A
的接收者对象上以一个B
类型参数来调用并返回一个C
类型值的函数。 带有接收者的函数字面值通常与这些类型一起使用。挂起函数属于特殊种类的函数类型,它的表示法中有一个 suspend 修饰符 ,例如
suspend () -> Unit
或者suspend A.(B) -> C
。
函数类型表示法可以选择性地包含函数的参数名:(x: Int, y: Int) -> Point
。 这些名称可用于表明参数的含义。
如需将函数类型指定为可空,请使用圆括号:
((Int, Int) -> Int)?
。
函数类型可以使用圆括号进行接合:
(Int) -> ((Int) -> Unit)
箭头表示法是右结合的,
(Int) -> (Int) -> Unit
与前述示例等价,但不等于((Int) -> (Int)) -> Unit
。
还可以通过使用类型别名给函数类型起一个别称:
typealias ClickHandler = (Button, ClickEvent) -> Unit
函数类型的值可以通过其 invoke(……)
操作符调用:f.invoke(x)
或者直接 f(x)
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // 类扩展调用
集合
区间
使用 in
运算符来检测某个数字是否在指定区间内,使用 !in
检测某个数字是否在指定区间外:
if (i in 1..4) { // 等同于 1 <= i && i <= 4
print(i)
}
区间迭代,可以设置步长,反向迭代区间等等:
for (x in 1..5) {
print(x)
}
for (x in 1..10 step 2) {
print(x)
}
for (x in 9 downTo 0 step 3) {
print(x)
}
集合操作
Kotlin 标准库提供了基本集合类型的实现: set、list 以及 map。 一对接口代表每种集合类型:
一个 只读 接口,提供访问集合元素的操作。
一个 可变 接口,通过写操作扩展相应的只读接口:添加、删除和更新其元素。
创建
创建集合的最常用方法是使用标准库函数 listOf<T>()
、setOf<T>()
、mutableListOf<T>()
、mutableSetOf<T>()
。 如果以逗号分隔的集合元素列表作为参数,编译器会自动检测元素类型。创建空集合时,须明确指定类型。
val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<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}") // Copy size: 3
//readOnlyCopyList.add(4) // 编译异常
println("Read-only copy size: ${readOnlyCopyList.size}") // Read-only copy size: 3
转换
映射
映射 转换从另一个集合的元素上的函数结果创建一个集合。 基本的映射函数是 map()
。 它将给定的 lambda 函数应用于每个后续元素,并返回 lambda 结果列表。 结果的顺序与元素的原始顺序相同。 如需应用还要用到元素索引作为参数的转换,请使用 mapIndexed()
。
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 }) // [3, 6, 9]
println(numbers.mapIndexed { idx, value -> value * idx }) // [0, 2, 6]
如果转换在某些元素上产生 null
值,则可以通过调用 mapNotNull()
函数取代 map()
或 mapIndexedNotNull()
取代 mapIndexed()
来从结果集中过滤掉 null
值。
val numbers = setOf(1, 2, 3)
println(numbers.mapNotNull { if ( it == 2) null else it * 3 }) // [3, 9]
println(numbers.mapIndexedNotNull { idx, value -> if (idx == 0) null else value * idx }) // [2, 6]
映射转换时,有两个选择:转换键,使值保持不变,反之亦然。 要将指定转换应用于键,请使用 mapKeys()
;反过来,mapValues()
转换值。 这两个函数都使用将映射条目作为参数的转换,因此可以操作其键与值。
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
println(numbersMap.mapKeys { it.key.toUpperCase() }) // {KEY1=1, KEY2=2, KEY3=3, KEY11=11}
println(numbersMap.mapValues { it.value + it.key.length }) // {key1=5, key2=6, key3=7, key11=16}
合拢
合拢 转换是根据两个集合中具有相同位置的元素构建配对。 在 Kotlin 标准库中,这是通过 zip()
扩展函数完成的。 在一个集合(或数组)上以另一个集合(或数组)作为参数调用时,zip()
返回 Pair
对象的列表(List
)。 接收者集合的元素是这些配对中的第一个元素。 如果集合的大小不同,则 zip()
的结果为较小集合的大小;结果中不包含较大集合的后续元素。 zip()
也可以中缀形式调用 a zip b
。
val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")
println(colors zip animals) // [(red, fox), (brown, bear), (grey, wolf)]
val twoAnimals = listOf("fox", "bear")
println(colors.zip(twoAnimals)) // [(red, fox), (brown, bear)]
也可以使用带有两个参数的转换函数来调用 zip()
:接收者元素和参数元素。 在这种情况下,结果 List
包含在具有相同位置的接收者对和参数元素对上调用的转换函数的返回值。
val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")
println(colors.zip(animals) { color, animal -> "The ${animal.capitalize()} is $color"}) // [The Fox is red, The Bear is brown, The Wolf is grey]
当拥有 Pair
的 List
时,可以进行反向转换 unzip
从这些键值对中构建两个列表
val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
println(numberPairs.unzip()) // ([one, two, three, four], [1, 2, 3, 4])
println(numberPairs.unzip()) // ([one, two, three, four], [1, 2, 3, 4])
关联
关联 转换允许从集合元素和与其关联的某些值构建 Map。 在不同的关联类型中,元素可以是关联 Map 中的键或值。
基本的关联函数 associateWith()
创建一个 Map
,其中原始集合的元素是键,并通过给定的转换函数从中产生值。 如果两个元素相等,则仅最后一个保留在 Map 中
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length }) // {one=3, two=3, three=5, four=4}
为了使用集合元素作为值来构建 Map,有一个函数 associateBy()
。 它需要一个函数,该函数根据元素的值返回键。如果两个元素相等,则仅最后一个保留在 Map 中。 还可以使用值转换函数来调用 associateBy()
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateBy { it.first().toUpperCase() }) // {O=one, T=three, F=four}
println(numbers.associateBy(keySelector = { it.first().toUpperCase() }, valueTransform = { it.length })) // {O=3, T=5, F=4}
过滤
基本的过滤函数是 filter()
。当使用一个谓词来调用时,filter()
返回与其匹配的集合元素。对于 List
和 Set
,过滤结果都是一个 List
,对 Map
来说结果还是一个 Map
。
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3) // [three, four]
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
println(filteredMap) // {key11=11}
有些函数只是针对集合元素简单地检测一个谓词:
如果至少有一个元素匹配给定谓词,那么
any()
返回true
。如果没有元素与给定谓词匹配,那么
none()
返回true
。如果所有元素都匹配给定谓词,那么
all()
返回true
。注意,在一个空集合上使用任何有效的谓词去调用all()
都会返回true
。这种行为在逻辑上被称为 vacuous truth。
val numbers = listOf("one", "two", "three", "four")
println(numbers.any { it.endsWith("e") }) // true
println(numbers.none { it.endsWith("a") }) // true
println(numbers.all { it.endsWith("e") }) // false
println(emptyList<Int>().all { it > 5 }) // vacuous truth
排序
sorted()
和 sortedDescending()
进行自然顺序升序和降序排序
如需为集合排序定义自定义顺序,可以提供自己的 Comparator
。 为此,调用传入 Comparator
的 sortedWith()
函数。
val numbers = listOf("one", "two", "three", "four")
println("Sorted ascending: ${numbers.sorted()}") // Sorted ascending: [four, one, three, two]
println("Sorted descending: ${numbers.sortedDescending()}") // Sorted descending: [two, three, one, four]
val sortedNumbers = numbers.sortedBy { it.length }
println("Sorted by length ascending: $sortedNumbers") // Sorted by length ascending: [one, two, four, three]
val sortedByLast = numbers.sortedByDescending { it.last() }
println("Sorted by the last letter descending: $sortedByLast") // Sorted by the last letter descending: [four, two, one, three]
val numbers = listOf("one", "two", "three", "four")
println("Sorted by length ascending: ${numbers.sortedWith(compareBy { it.length })}") // Sorted by length ascending: [one, two, four, three]
println("Sorted by length ascending: ${numbers.sortedWith(compareBy { it.length })}") // Sorted by length ascending: [one, two, four, three]
reversed()
函数以相反的顺序检索集合。asReversed()
返回相同集合实例的一个反向视图,因此,如果原始列表不会发生变化,那么它会比 reversed()
更轻量,更合适
val numbers = listOf("one", "two", "three", "four")
println(numbers.reversed()) // [four, three, two, one]
val reversedNumbers = numbers.asReversed()
println(reversedNumbers) // [four, three, two, one]
numbers.add("five")
println(reversedNumbers) // [five, four, three, two, one]
shuffled()
函数返回一个包含了以随机顺序排序的集合元素的新的 List
val numbers = listOf("one", "two", "three", "four")
println(numbers.shuffled())
List
val doubled = List(3, { it * 2 }) // 如果你想操作这个集合,应使用 MutableList
println(doubled) // [0, 2, 4]
按索引取元素
val numbers = listOf(1, 2, 3, 4)
println(numbers.get(0))
println(numbers[0])
//numbers.get(5) // exception!
println(numbers.getOrNull(5)) // null
println(numbers.getOrElse(5, {it})) // 5
线性查找
val numbers = listOf(1, 2, 3, 4, 2, 5)
println(numbers.indexOf(2)) // 1
println(numbers.lastIndexOf(2)) // 4
val numbers = mutableListOf(1, 2, 3, 4)
println(numbers.indexOfFirst { it > 2}) // 2
println(numbers.indexOfLast { it % 2 == 1}) // 2
有序列表中的二分查找
binarySearch()
函数搜索已排序列表中的元素,如果存在需要查找的元素,则函数返回其索引;否则,将返回 (-insertionPoint - 1)
,其中 insertionPoint
为应插入此元素的索引,以便列表保持排序。 如果有多个具有给定值的元素,搜索则可以返回其任何索引。
val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort()
println(numbers) // [four, one, three, two]
println(numbers.binarySearch("two")) // 3
println(numbers.binarySearch("z")) // -5
println(numbers.binarySearch("two", 0, 2)) // -3
排序
对于可变列表,标准库中提供了类似的扩展函数,这些扩展函数可以执行相同的排序操作。 将此类操作应用于列表实例时,它将更改指定实例中元素的顺序。
就地排序函数的名称与应用于只读列表的函数的名称相似,但没有 ed/d
后缀:
sort*
在所有排序函数的名称中代替sorted*
:sort()
、sortDescending()
、sortBy()
等等。shuffle()
代替shuffled()
。reverse()
代替reversed()
。
Set
union()
函数将两个集合合并为一个集合(并集)。也能以中缀形式使用 a union b
。 对于有序集合,操作数的顺序很重要:在结果集合中,左侧操作数在前。
intersect()
查找两个集合中都存在的元素(交集) 。 subtract()
查找另一个集合中不存在的集合元素(差集) 。
val numbers = setOf("one", "two", "three")
println(numbers union setOf("four", "five")) // [one, two, three, four, five]
println(setOf("four", "five") union numbers) // [four, five, one, two, three]
println(numbers intersect setOf("two", "one")) // [one, two]
println(numbers subtract setOf("three", "four")) // [one, two]
Map
映射的键和值作为 Pair
对象传递(通常使用中缀函数 to
创建)。to
符号创建了一个短时存活的 Pair
对象,因此建议仅在性能不重要时才使用它。 为避免过多的内存使用,请使用其他方法。例如,可以创建可写 Map 并使用写入操作填充它。 apply()
函数可以帮助保持初始化流畅。
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val numbersMap = mutableMapOf<String, String>().apply { this["one"] = "1"; this["two"] = "2" }
字符串
获取、查找元素
val str = "xiaomi"
println(str[0]) // x
println(str.first()) // x
println(str.last()) // i
println(str.indexOf("ia")) // 1
println(str.lastIndexOf('i')) // 5
字符串截取
val str = "xiaomi"
println(str.substring(0, 2)) // xi
println(str.split('i')) // [x, aom, ]
字符串替换,可以是正则表达式
val str = "xiaomi"
println(str.replace('i', '2')) // x2aom2
println(str.replace(Regex("a[a-z]+i"), "hello")) // xihello
println(str.replaceFirst('i', '2')) // x2aomi
// 还有 replaceBefore(), replaceBeforeLast(), replaceAfter(), replaceAfterLast()等等
str.length
获取字符串长度
str.reversed()
反转字符串类与对象
str.startWith()
str.endWith()
判断字符串的起始与结尾
类与对象
实例化
使用 class
声明类 class Invoice { /……/ }
一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后,如果主构造函数没有任何注解或者可见性修饰符,可以省略 constructor
class Person constructor(firstName: String) { /……/ }
class Person(firstName: String) { /……/ }
如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面
class Customer public @Inject constructor(name: String) { /……/ }
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块中。在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
/*
First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5
*/
创建类的实例,调用构造函数
val invoice = Invoice()
val customer = Customer("Joe Smith")
继承
所有类拥有一个共同的超类 Any
。默认情况下,类是 final
的,不能被继承。使用 open
关键字使其可被继承
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果派生类有一个主构造函数,其基类必须用派生类主构造函数的参数就地初始化
如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 不同的次构造函数可以调用基类型的不同的构造函数。
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
使用 override
可以重写父类用 open
标记的方法,并且重写后的方法默认是 open
的,用 final
可以使其避免被字类继承
open class Rectangle() : Shape() {
final override fun draw() { /* ... */ }
}
属性也可以继承,也可以在主构造函数中使用 override
关键字作为属性声明的一部分。但是 val
属性无法继承 var
属性,反之 var
属性可以继承 val
属性。
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
class Polygon : Shape {
override var vertexCount: Int = 0 // 以后可以设置为任何数
}
在构造派生类的新实例过程中,会首先完成对基类的初始化。设计一个基类是,应避免在构造函数、属性初始化器及 init
块中使用 open
成员
派生类中的代码可以使用 super
关键字调用其超类的函数与属性访问器的实现。
open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
接口
使用 interface
定义接口。在接口中声明的属性要么是抽象的,要么提供访问器的实现。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
一个接口可以从其他接口派生
interface Named {
val name: String
}
interface Person : Named {
val firstName: String
val lastName: String
override val name: String get() = "$firstName $lastName"
}
data class Employee(
// 不必实现“name”
override val firstName: String,
override val lastName: String,
val position: Position
) : Person
泛型
协程
阻塞与非阻塞
delay()
是一个特殊的挂起函数,不会造成线程阻塞,挂起函数只能在协程里使用,相反Thread.sleep()
会造成阻塞
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!") // 在延迟后打印输出
}
println("Hello,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}
/*
Hello,
World!
*/
GlobalScope.launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 在延迟后退出
/*
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
*/
显式(以非阻塞方式)等待所启动的后台 Job 执行结束
val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // 等待直到子协程执行结束
runBlocking
协程构建器显式阻塞
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L)
println("World!")
}
println("Hello,") // 主线程中的代码会立即执行
runBlocking { // 但是这个表达式阻塞了主线程
delay(2000L) // ……我们延迟 2 秒来保证 JVM 的存活
}
}
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> { // 开始执行主协程
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L)
println("World!")
}
println("Hello,") // 主协程在这里会立即执行
}
coroutineScope
会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。与runlocking
阻塞当前线程来等待不同,coroutineScope
是一个挂起函数,会释放底层线程用于其他用途。
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // 创建一个协程作用域
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出
}
println("Coroutine scope is over") // 这一行在内嵌 launch 执行完毕后才输出
}
/*
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
*/
挂起函数
有 suspend
修饰的函数
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}
suspend fun doWorld() {
delay(1000L)
println("World!")
}
async 并发
async
类似 launch
,启动一个单独的协程,协程之间可以并发操作。不同点是 async
可以附带返回值
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
可以通过设置 CoroutineStart.Lazy
将其设置为惰性启动。需要通过 await()
和 start()
手动唤醒
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
// 执行一些计算
one.start() // 启动第一个
two.start() // 启动第二个
println("The answer is ${one.await() + two.await()}")
}
上一个代码中,如果没有进行 start()
操作,那么在 print()
中会顺序执行两个协程,表现为程序运行时间翻倍
// somethingUsefulOneAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
// somethingUsefulTwoAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
这些 xxxAsync
函数不是 挂起 函数。它们可以在任何地方使用。 然而,它们总是在调用它们的代码中意味着异步(这里的意思是 并发 )执行。
// 注意,在这个示例中我们在 `main` 函数的右边没有加上 `runBlocking`
fun main() {
val time = measureTimeMillis {
// 我们可以在协程外面启动异步执行
val one = somethingUsefulOneAsync()
val two = somethingUsefulTwoAsync()
// 但是等待结果必须调用其它的挂起或者阻塞
// 当我们等待结果的时候,这里我们使用 `runBlocking { …… }` 来阻塞主线程
runBlocking {
println("The answer is ${one.await() + two.await()}")
}
}
}
结构化并发,在一个作用域内部如果发生了错误,这个作用域内的所有协程都会被取消
协程中断
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束
println("main: Now I can quit.")
/*
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
*/
所有的挂起函数都是可被取消的
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) {
// 每秒打印消息两次
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // 等待一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消一个作业并且等待它结束
println("main: Now I can quit.")
/*
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm sleeping 3 ...
job: I'm sleeping 4 ...
main: Now I can quit.
*/
协程正在执行计算任务,不能被取消,调用cancel后程序仍进行了若干次循环。若将 while(i < 5)
改成 while(isActive)
则可以正常取消。isActive
是一个可以被使用在 CoroutineScope
中的扩展属性
协程被取消后抛出 CancellationException
异常。 try {...} finally {...}
可以使协程在被取消后执行一定的动作
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
println("job: I'm running finally")
}
}
可以使用 withContext(NonCancellable)
挂起一个不能被取消的协程
withTimeout()
限制协程运行时间,超时后抛出 TimeoutCancellationException
异常。withTimeoutOrNull()
通过返回 null
替代抛出一个异常
异步流
使用 suspend
修饰符标记函数使其在不阻塞的情况下执行代码并返回。若返回值为列表,为了表示异步计算的值流,可以使用Flow类型
suspend fun simple(): List<Int> {
delay(100) // 假装我们在这里做了一些异步的事情
return listOf(1, 2, 3)
}
fun main() = runBlocking<Unit> {
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
simple().forEach { value -> println(value) }
}
/*
I'm not blocked 1
1
2
3
I'm not blocked 2
I'm not blocked 3
*/
fun simple(): Flow<Int> = flow { // 流构建器
for (i in 1..3) {
delay(100) // 假装我们在这里做了一些有用的事情
emit(i) // 发送下一个值
}
}
fun main() = runBlocking<Unit> {
// 启动并发的协程以验证主线程并未阻塞
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
// 收集这个流
simple().collect { value -> println(value) }
}
/*
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3
*/
flow { ... }
构建块中的代码可以挂起。函数
simple
不再标有suspend
修饰符。流使用 emit 函数 发射 值。
流使用 collect 函数 收集 值。
直到流被收集的时候才会运行
使用流转换操作符可以发射任意值任意次
(1..3).asFlow() // 一个请求流
.transform { request ->
emit("Making request $request")
emit(performRequest(request))
}
.collect { response -> println(response) }
/*
Making request 1
response 1
Making request 2
response 2
Making request 3
response 3
*/
collect
被称为末端操作符,其他的末端操作符还有
等等
val sum = (1..5).asFlow()
.map { it * it } // 数字 1 至 5 的平方
.reduce { a, b -> a + b } // 求和(末端操作符)
println(sum)
- 感谢你赐予我前进的力量