[TOC]
## 通道
延期的值提供了一種便捷的方法使單個值在多個協程之間進行相互傳輸。通道提供了一種在流中傳輸值的方法。
### 通道基礎
一個 [Channel](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html) 是一個和 `BlockingQueue` 非常相似的概念。其中一個不同是它代替了阻塞的 `put` 操作并提供了掛起的 [send](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html),還替代了阻塞的 `take` 操作并提供了掛起的 [receive](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html)。
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
//sampleStart
val channel = Channel<Int>()
launch {
// 這里可能是消耗大量 CPU 運算的異步邏輯,我們將僅僅做 5 次整數的平方并發送
for (x in 1..5) channel.send(x * x)
}
// 這里我們打印了 5 次被接收的整數:
repeat(5) { println(channel.receive()) }
println("Done!")
//sampleEnd
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt)獲取完整代碼。
這段代碼的輸出如下:
```text
1
4
9
16
25
Done!
```
### 關閉與迭代通道
和隊列不同,一個通道可以通過被關閉來表明沒有更多的元素將會進入通道。在接收者中可以定期的使用 `for` 循環來從通道中接收元素。
從概念上來說,一個 [close](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/close.html)操作就像向通道發送了一個特殊的關閉指令。這個迭代停止就說明關閉指令已經被接收了。所以這里保證所有先前發送出去的元素都在通道關閉前被接收到。
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
//sampleStart
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x * x)
channel.close() // 我們結束發送
}
// 這里我們使用 `for` 循環來打印所有被接收到的元素(直到通道被關閉)
for (y in channel) println(y)
println("Done!")
//sampleEnd
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt)獲取完整代碼。
```
1
4
9
16
25
```
### 構建通道生產者
協程生成一系列元素的模式很常見。這是 _生產者——消費者_ 模式的一部分,并且經常能在并發的代碼中看到它。
你可以將生產者抽象成一個函數,并且使通道作為它的參數,但這與必須從函數中返回結果的常識相違悖。
這里有一個名為 [produce](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html) 的便捷的協程構建器,可以很容易的在生產者端正確工作,并且我們使用擴展函數 [consumeEach](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume-each.html) 在消費者端替代 `for` 循環:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
for (x in 1..5) send(x * x)
}
fun main() = runBlocking {
//sampleStart
val squares = produceSquares()
squares.consumeEach { println(it) }
println("Done!")
//sampleEnd
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt)獲取完整代碼。
```
1
4
9
16
25
Done!
```
### 管道
管道是一種一個協程在流中開始生產可能無窮多個元素的模式:
```kotlin
fun CoroutineScope.produceNumbers() = produce<Int> {
var x = 1
while (true) send(x++) // 在流中開始從 1 生產無窮多個整數
}
```
并且另一個或多個協程開始消費這些流,做一些操作,并生產了一些額外的結果。在下面的例子中,對這些數字僅僅做了平方操作:
```kotlin
fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
for (x in numbers) send(x * x)
}
```
主要的代碼啟動并連接了整個管道:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
//sampleStart
val numbers = produceNumbers() // 從 1 開始生產整數
val squares = square(numbers) // 對整數做平方
for (i in 1..5) println(squares.receive()) // 打印前 5 個數字
println("Done!") // 我們的操作已經結束了
coroutineContext.cancelChildren() // 取消子協程
//sampleEnd
}
fun CoroutineScope.produceNumbers() = produce<Int> {
var x = 1
while (true) send(x++) // 從 1 開始的無限的整數流
}
fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
for (x in numbers) send(x * x)
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt)獲取完整代碼。
```
1
4
9
16
25
Done!
```
> 所有創建了協程的函數被定義在了 [CoroutineScope] 的擴展上,所以我們可以依靠[結構化并發](https://kotlinlang.org/docs/reference/coroutines/composing-suspending-functions.html#structured-concurrency-with-async)來確保沒有常駐在我們的應用程序中的全局協程。
### 使用管道的素數
讓我們來展示一個極端的例子——在協程中使用一個管道來生成素數。我們開啟了一個數字的無限序列。
```kotlin
fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
var x = start
while (true) send(x++) // 開啟了一個無限的整數流
}
```
在下面的管道階段中過濾了來源于流中的數字,刪除了所有可以被給定素數整除的數字。
```kotlin
fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
for (x in numbers) if (x % prime != 0) send(x)
}
```
現在我們開啟了一個從 2 開始的數字流管道,從當前的通道中取一個素數,并為每一個我們發現的素數啟動一個流水線階段:
```
numbersFrom(2) -> filter(2) -> filter(3) -> filter(5) -> filter(7) ……
```
下面的例子打印了前十個素數,在主線程的上下文中運行整個管道。直到所有的協程在該主協程 [runBlocking](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) 的作用域中被啟動完成。我們不必使用一個顯式的列表來保存所有被我們已經啟動的協程。
我們使用 [cancelChildren](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/kotlin.coroutines.-coroutine-context/cancel-children.html)擴展函數在我們打印了前十個素數以后來取消所有的子協程。
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
//sampleStart
var cur = numbersFrom(2)
for (i in 1..10) {
val prime = cur.receive()
println(prime)
cur = filter(cur, prime)
}
coroutineContext.cancelChildren() // 取消所有的子協程來讓主協程結束
//sampleEnd
}
fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
var x = start
while (true) send(x++) // 從 start 開始過濾整數流
}
fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
for (x in numbers) if (x % prime != 0) send(x)
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt)獲取完整代碼。
這段代碼的輸出如下:
```text
2
3
5
7
11
13
17
19
23
29
```
注意,你可以在標準庫中使用[`iterator`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/iterator.html)
協程構建器來構建一個相似的管道。使用 `iterator` 替換 `produce`、`yield` 替換 `send`、`next` 替換 `receive`、
`Iterator` 替換 `ReceiveChannel` 來擺脫協程作用域,你將不再需要 `runBlocking`。然而,如上所示,如果你在 [Dispatchers.Default](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) 上下文中運行它,使用通道的管道的好處在于它可以充分利用多核心 CPU。
不過,這是一種非常不切實際的尋找素數的方法。在實踐中,管道調用了另外的一些掛起中的調用(就像異步調用遠程服務)并且這些管道不能內置使用 `sequence`/`iterator`,因為它們不被允許隨意的掛起,不像`produce` 是完全異步的。
### 扇出
多個協程也許會接收相同的管道,在它們之間進行分布式工作。讓我們啟動一個定期產生整數的生產者協程
(每秒十個數字):
```kotlin
fun CoroutineScope.produceNumbers() = produce<Int> {
var x = 1 // 從 1 開始
while (true) {
send(x++) // 產生下一個數字
delay(100) // 等待 0.1 秒
}
}
```
接下來我們可以得到幾個生產者協程。在這個示例中,它們只是打印它們的 id 和接收到的數字:
```kotlin
fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
for (msg in channel) {
println("Processor #$id received $msg")
}
}
```
現在讓我們啟動五個生產者協程并讓它們工作將近一秒。看看發生了什么:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking<Unit> {
//sampleStart
val producer = produceNumbers()
repeat(5) { launchProcessor(it, producer) }
delay(950)
producer.cancel() // 取消協程生產者從而將它們全部殺死
//sampleEnd
}
fun CoroutineScope.produceNumbers() = produce<Int> {
var x = 1 // start from 1
while (true) {
send(x++) // 產生下一個數字
delay(100) // 等待 0.1 秒
}
}
fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
for (msg in channel) {
println("Processor #$id received $msg")
}
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt)獲取完整代碼。
該輸出將類似于如下所示,盡管接收的是生產者的 id但每個整數也許會不同:
```
Processor #2 received 1
Processor #4 received 2
Processor #0 received 3
Processor #1 received 4
Processor #3 received 5
Processor #2 received 6
Processor #4 received 7
Processor #0 received 8
Processor #1 received 9
Processor #3 received 10
```
注意,取消生產者協程并關閉它的通道,因此通過正在執行的生產者協程通道來終止迭代。
還有,注意我們如何使用 `for` 循環顯式迭代通道以在 `launchProcessor` 代碼中執行扇出。與 `consumeEach` 不同,這個 `for` 循環是安全完美地使用多個協程的。如果其中一個生產者協程執行失敗,其它的生產者協程仍然會繼續處理通道,而通過 `consumeEach`編寫的生產者始終在正常或非正常完成時消耗(取消)底層通道。
### 扇入
多個協程可以發送到同一個通道。比如說,讓我們創建一個字符串的通道,和一個在這個通道中以指定的延遲反復發送一個指定字符串的掛起函數:
```kotlin
suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
while (true) {
delay(time)
channel.send(s)
}
}
```
現在,我們啟動了幾個發送字符串的協程,讓我們看看會發生什么(在示例中,我們在主線程的上下文中作為主協程的子協程來啟動它們):
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
//sampleStart
val channel = Channel<String>()
launch { sendString(channel, "foo", 200L) }
launch { sendString(channel, "BAR!", 500L) }
repeat(6) { // 接收前六個
println(channel.receive())
}
coroutineContext.cancelChildren() // 取消所有子協程來讓主協程結束
//sampleEnd
}
suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
while (true) {
delay(time)
channel.send(s)
}
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt)獲取完整代碼。
輸出如下:
```text
foo
foo
BAR!
foo
foo
BAR!
```
### 帶緩沖的通道
到目前為止展示的通道都是沒有緩沖區的。無緩沖的通道在發送者和接收者相遇時傳輸元素(aka rendezvous(這句話應該是個俚語,意思好像是“又是約會”的意思,不知道怎么翻))。如果發送先被調用,則它將被掛起直到接收被調用,如果接收先被調用,它將被掛起直到發送被調用。
[Channel()](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel.html) 工廠函數與 [produce](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html) 建造器通過一個可選的參數 `capacity`來指定 _緩沖區大小_ 。緩沖允許發送者在被掛起前發送多個元素,就像 `BlockingQueue` 有指定的容量一樣,當緩沖區被占滿的時候將會引起阻塞。
看看如下代碼的表現:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking<Unit> {
//sampleStart
val channel = Channel<Int>(4) // 啟動帶緩沖的通道
val sender = launch { // 啟動發送者協程
repeat(10) {
println("Sending $it") // 在每一個元素發送前打印它們
channel.send(it) // 將在緩沖區被占滿時掛起
}
}
// 沒有接收到東西……只是等待……
delay(1000)
sender.cancel() // 取消發送者協程
//sampleEnd
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt)獲取完整代碼。
使用緩沖通道并給 capacity 參數傳入 _四_ 它將打印“sending” _五_ 次:
```text
Sending 0
Sending 1
Sending 2
Sending 3
Sending 4
```
前四個元素被加入到了緩沖區并且發送者在試圖發送第五個元素的時候被掛起。
### 通道是公平的
發送和接收操作是 _公平的_ 并且尊重調用它們的多個協程。它們遵守先進先出原則,可以看到第一個協程調用 `receive`并得到了元素。在下面的例子中兩個協程“乒”和“乓”都從共享的“桌子”通道接收到這個“球”元素。
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
//sampleStart
data class Ball(var hits: Int)
fun main() = runBlocking {
val table = Channel<Ball>() // 一個共享的 table(桌子)
launch { player("ping", table) }
launch { player("pong", table) }
table.send(Ball(0)) // 乒乓球
delay(1000) // 延遲 1 秒鐘
coroutineContext.cancelChildren() // 游戲結束,取消它們
}
suspend fun player(name: String, table: Channel<Ball>) {
for (ball in table) { // 在循環中接收球
ball.hits++
println("$name $ball")
delay(300) // 等待一段時間
table.send(ball) // 將球發送回去
}
}
//sampleEnd
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt)得到完整代碼
“乒”協程首先被啟動,所以它首先接收到了球。甚至雖然“乒”協程在將球發送會桌子以后立即開始接收,但是球還是被“乓”協程接收了,因為它一直在等待著接收球:
```text
ping Ball(hits=1)
pong Ball(hits=2)
ping Ball(hits=3)
pong Ball(hits=4)
```
注意,有時候通道執行時由于執行者的性質而看起來不那么公平。點擊[這個提案](https://github.com/Kotlin/kotlinx.coroutines/issues/111)來查看更多細節。
### 計時器通道
計時器通道是一種特別的會合通道,每次經過特定的延遲都會從該通道進行消費并產生 `Unit`。雖然它看起來似乎沒用,它被用來構建分段來創建復雜的基于時間的 [produce](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html)管道和進行窗口化操作以及其它時間相關的處理。可以在 [select](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html) 中使用計時器通道來進行“打勾”操作。
使用工廠方法 [ticker](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html) 來創建這些通道。為了表明不需要其它元素,請使用 [ReceiveChannel.cancel](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html) 方法。
現在讓我們看看它是如何在實踐中工作的:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking<Unit> {
val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) //創建計時器通道
var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
println("Initial element is available immediately: $nextElement") // 初始尚未經過的延遲
nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } // 所有隨后到來的元素都經過了 100 毫秒的延遲
println("Next element is not ready in 50 ms: $nextElement")
nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
println("Next element is ready in 100 ms: $nextElement")
// 模擬大量消費延遲
println("Consumer pauses for 150ms")
delay(150)
// 下一個元素立即可用
nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
println("Next element is available immediately after large consumer delay: $nextElement")
// 請注意,`receive` 調用之間的暫停被考慮在內,下一個元素的到達速度更快
nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
println("Next element is ready in 50ms after consumer pause in 150ms: $nextElement")
tickerChannel.cancel() // 表明不再需要更多的元素
}
```
> 可以在[這里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt)獲取完整代碼。
它的打印如下:
```text
Initial element is available immediately: kotlin.Unit
Next element is not ready in 50 ms: null
Next element is ready in 100 ms: kotlin.Unit
Consumer pauses for 150ms
Next element is available immediately after large consumer delay: kotlin.Unit
Next element is ready in 50ms after consumer pause in 150ms: kotlin.Unit
```
請注意,[ticker](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html) 知道可能的消費者暫停,并且默認情況下會調整下一個生成的元素如果發生暫停則延遲,試圖保持固定的生成元素率。
給可選的 `mode` 參數傳入 [TickerMode.FIXED_DELAY](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-ticker-mode/-f-i-x-e-d_-d-e-l-a-y.html) 可以保持固定元素之間的延遲。
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
[kotlin.coroutines.CoroutineContext.cancelChildren]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/kotlin.coroutines.-coroutine-context/cancel-children.html
[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
<!--- INDEX kotlinx.coroutines.channels -->
[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
[SendChannel.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/close.html
[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
[consumeEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume-each.html
[Channel()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel.html
[ticker]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html
[ReceiveChannel.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html
[TickerMode.FIXED_DELAY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-ticker-mode/-f-i-x-e-d_-d-e-l-a-y.html
[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
- 前言
- Kotlin簡介
- IntelliJ IDEA技巧總結
- idea設置類注釋和方法注釋模板
- 像Android Studion一樣創建工程
- Gradle
- Gradle入門
- Gradle進階
- 使用Gradle創建一個Kotlin工程
- 環境搭建
- Androidstudio平臺搭建
- Eclipse的Kotlin環境配置
- 使用IntelliJ IDEA
- Kotlin學習路線
- Kotlin官方中文版文檔教程
- 概述
- kotlin用于服務器端開發
- kotlin用于Android開發
- kotlin用于JavaScript開發
- kotlin用于原生開發
- Kotlin 用于數據科學
- 協程
- 多平臺
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 開始
- 基本語法
- 習慣用法
- 編碼規范
- 基礎
- 基本類型
- 包與導入
- 控制流
- 返回與跳轉
- 類與對象
- 類與繼承
- 屬性與字段
- 接口
- 可見性修飾符
- 擴展
- 數據類
- 密封類
- 泛型
- 嵌套類
- 枚舉類
- 對象
- 類型別名
- 內嵌類
- 委托
- 委托屬性
- 函數與Lambda表達式
- 函數
- Lambda表達式
- 內聯函數
- 集合
- 集合概述
- 構造集合
- 迭代器
- 區間與數列
- 序列
- 操作概述
- 轉換
- 過濾
- 加減操作符
- 分組
- 取集合的一部分
- 取單個元素
- 排序
- 聚合操作
- 集合寫操作
- List相關操作
- Set相關操作
- Map相關操作
- 多平臺程序設計
- 平臺相關聲明
- 以Gradle創建
- 更多語言結構
- 解構聲明
- 類型檢測與轉換
- This表達式
- 相等性
- 操作符重載
- 空安全
- 異常
- 注解
- 反射
- 作用域函數
- 類型安全的構造器
- Opt-in Requirements
- 核心庫
- 標準庫
- kotlin.test
- 參考
- 關鍵字與操作符
- 語法
- 編碼風格約定
- Java互操作
- Kotlin中調用Java
- Java中調用Kotlin
- JavaScript
- 動態類型
- kotlin中調用JavaScript
- JavaScript中調用kotlin
- JavaScript模塊
- JavaScript反射
- JavaScript DCE
- 原生
- 并發
- 不可變性
- kotlin庫
- 平臺庫
- 與C語言互操作
- 與Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 調試
- FAQ
- 協程
- 協程指南
- 基礎
- 取消與超時
- 組合掛起函數
- 協程上下文與調度器
- 異步流
- 通道
- 異常處理與監督
- 共享的可變狀態與并發
- Select表達式(實驗性)
- 工具
- 編寫kotlin代碼文檔
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin與OSGI
- 編譯器插件
- 編碼規范
- 演進
- kotlin語言演進
- 不同組件的穩定性
- kotlin1.3的兼容性指南
- 常見問題
- FAQ
- 與Java比較
- 與Scala比較(官方已刪除)
- Google開發者官網簡介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin樣品
- Kotlin零基礎到進階
- 第一階段興趣入門
- kotlin簡介和學習方法
- 數據類型和類型系統
- 入門
- 分類
- val和var
- 二進制基礎
- 基礎
- 基本語法
- 包
- 示例
- 編碼規范
- 代碼注釋
- 異常
- 根類型“Any”
- Any? 可空類型
- 可空性的實現原理
- kotlin.Unit類型
- kotlin.Nothing類型
- 基本數據類型
- 數值類型
- 布爾類型
- 字符型
- 位運算符
- 變量和常量
- 語法和運算符
- 關鍵字
- 硬關鍵字
- 軟關鍵字
- 修飾符關鍵字
- 特殊標識符
- 操作符和特殊符號
- 算術運算符
- 賦值運算符
- 比較運算符
- 邏輯運算符
- this關鍵字
- super關鍵字
- 操作符重載
- 一元操作符
- 二元操作符
- 字符串
- 字符串介紹和屬性
- 字符串常見方法操作
- 字符串模板
- 數組
- 數組介紹創建及遍歷
- 數組常見方法和屬性
- 數組變化以及下標越界問題
- 原生數組類型
- 區間
- 正向區間
- 逆向區間
- 步長
- 類型檢測與類型轉換
- is、!is、as、as-運算符
- 空安全
- 可空類型變量
- 安全調用符
- 非空斷言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函數
- 函數式編程概述
- OOP和FOP
- 函數式編程基本特性
- 組合與范疇
- 在Kotlin中使用函數式編程
- 函數入門
- 函數作用域
- 函數加強
- 命名參數
- 默認參數
- 可變參數
- 表達式函數體
- 頂層、嵌套、中綴函數
- 尾遞歸函數優化
- 函數重載
- 控制流
- if表達式
- when表達式
- for循環
- while循環
- 循環中的 Break 與 continue
- return返回
- 標簽處返回
- 集合
- list集合
- list集合介紹和操作
- list常見方法和屬性
- list集合變化和下標越界
- set集合
- set集合介紹和常見操作
- set集合常見方法和屬性
- set集合變換和下標越界
- map集合
- map集合介紹和常見操作
- map集合常見方法和屬性
- map集合變換
- 集合的函數式API
- map函數
- filter函數
- “ all ”“ any ”“ count ”和“ find ”:對集合應用判斷式
- 別樣的求和方式:sumBy、sum、fold、reduce
- 根據人的性別進行分組:groupBy
- 扁平化——處理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 區間、數組、集合之間轉換
- 面向對象
- 面向對象-封裝
- 類的創建及屬性方法訪問
- 類屬性和字段
- 構造器
- 嵌套類(內部類)
- 枚舉類
- 枚舉類遍歷&枚舉常量常用屬性
- 數據類
- 密封類
- 印章類(密封類)
- 面向對象-繼承
- 類的繼承
- 面向對象-多態
- 抽象類
- 接口
- 接口和抽象類的區別
- 面向對象-深入
- 擴展
- 擴展:為別的類添加方法、屬性
- Android中的擴展應用
- 優化Snackbar
- 用擴展函數封裝Utils
- 解決煩人的findViewById
- 擴展不是萬能的
- 調度方式對擴展函數的影響
- 被濫用的擴展函數
- 委托
- 委托類
- 委托屬性
- Kotlin5大內置委托
- Kotlin-Object關鍵字
- 單例模式
- 匿名類對象
- 伴生對象
- 作用域函數
- let函數
- run函數
- with函數
- apply函數
- also函數
- 標準庫函數
- takeIf 與 takeUnless
- 第二階段重點深入
- Lambda編程
- Lambda成員引用高階函數
- 高階函數
- 內聯函數
- 泛型
- 泛型的分類
- 泛型約束
- 子類和子類型
- 協變與逆變
- 泛型擦除與實化類型
- 泛型類型參數
- 泛型的背后:類型擦除
- Java為什么無法聲明一個泛型數組
- 向后兼容的罪
- 類型擦除的矛盾
- 使用內聯函數獲取泛型
- 打破泛型不變
- 一個支持協變的List
- 一個支持逆變的Comparator
- 協變和逆變
- 第三階段難點突破
- 注解和反射
- 聲明并應用注解
- DSL
- 協程
- 協程簡介
- 協程的基本操作
- 協程取消
- 管道
- 慕課霍丙乾協程筆記
- Kotlin與Java互操作
- 在Kotlin中調用Java
- 在Java中調用Kotlin
- Kotlin與Java中的操作對比
- 第四階段專題練習
- 朱凱Kotlin知識點總結
- Kotlin 基礎
- Kotlin 的變量、函數和類型
- Kotlin 里那些「不是那么寫的」
- Kotlin 里那些「更方便的」
- Kotlin 進階
- Kotlin 的泛型
- Kotlin 的高階函數、匿名函數和 Lambda 表達式
- Kotlin協程
- 初識
- 進階
- 深入
- Kotlin 擴展
- 會寫「18.dp」只是個入門——Kotlin 的擴展函數和擴展屬性(Extension Functions / Properties)
- Kotlin實戰-開發Android