《Kotlin 程序設(shè)計(jì)》第十二章 Kotlin的多線程:協(xié)程(Coroutines)

第十二章 Kotlin的多線程:協(xié)程(Coroutines)

Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, non-blocking code (and much more). In this tutorial we will go through some basics of using Kotlin coroutines with the help of the kotlinx.coroutines library, which is a collection of helpers and wrappers for existing Java libraries.

Make sure it's configured for Kotlin 1.1 or higher.

Since coroutines have the experimental status in Kotlin 1.1, by default the compiler reports a warning every time they are used. We can opt-in for the experimental feature and use it without a warning by adding this code to build.gradle:

apply plugin: 'kotlin'

kotlin {
    experimental {
        coroutines 'enable'
    }
}

Since we'll be using the kotlinx.coroutines, let's add its recent version to our dependencies:

dependencies {
    ...
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.15"
}

This library is published to Bintray JCenter repository, so let us add it:

repositories {
    jcenter()
}

This code to pom.xml:

<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-maven-plugin</artifactId>
    ...
    <configuration>
        <args>
            <arg>-Xcoroutines=enable</arg>
        </args>
    </configuration>
</plugin>

Since we'll be using the kotlinx.coroutines, let's add its recent version to our dependencies:

<dependencies>
    ...
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlinx-coroutines-core</artifactId>
        <version>0.15</version>
    </dependency>
</dependencies>

This library is published to Bintray JCenter repository, so let us add it:

<repositories>
    ...
    <repository>
        <id>central</id>
        <url>http://jcenter.bintray.com</url>
    </repository>
</repositories>

That's it, we are good to go and write code under src/main/kotlin.

My first coroutine

One can think of a coroutine as a light-weight thread. Like threads, coroutines can run in parallel, wait for each other and communicate.
The biggest difference is that coroutines are very cheap, almost free: we can create thousands of them, and pay very little in terms of performance.
True threads, on the other hand, are expensive to start and keep around. A thousand threads can be a serious challenge for a modern machine.

So, how do we start a coroutine? Let's use the launch {} function:

launch(CommonPool) {
    ...
}

This starts a new coroutine on a given thread pool. In this case we are using CommonPool that uses ForkJoinPool.commonPool(). Threads still exist in a program based on coroutines, but one thread can run many coroutines, so there's no need for too many threads.

Let's look at a full program that uses launch:

import kotlinx.coroutines.experimental.*

fun main(args: Array<String>) {
    println("Start")

    // Start a coroutine
    launch(CommonPool) {
        delay(1000)
        println("Hello")
    }

    Thread.sleep(2000) // wait for 2 seconds
    println("Stop")
}

Here we start a coroutine that waits for 1 second and prints Hello.

We are using the delay() function that's like Thread.sleep(), but better: it doesn't block a thread, but only suspends the coroutine itself.
The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool.

The main thread (that runs the main() function) must wait until our coroutine completes, otherwise the program ends before Hello is printed.

Exercise: try removing the sleep() from the program above and see the result.

If we try to use the same non-blocking delay() function directly inside main(), we'll get a compiler error:

Suspend functions are only allowed to be called from a coroutine or another suspend function

This is because we are not inside any coroutine. We can use delay if we wrap it into runBlocking {} that starts a coroutine and waits until it's done:

runBlocking {
    delay(2000)
}

So, first the resulting program prints Start, then it runs a coroutine through launch {}, then it runs another one through runBlocking {} and blocks until it's done, then prints Stop. Meanwhile the first coroutine completes and prints Hello. Just like threads, we told you :)

Let's run a lot of them

Now, let's make sure that coroutines are really cheaper than threads. How about starting a million of them? Let's try starting a million threads first:

val c = AtomicInteger()

for (i in 1..1_000_000)
    thread(start = true) {
        c.addAndGet(i)
    }

println(c.get())

This runs a 1'000'000 threads each of which adds to a common counter. My patience runs out before this program completes on my machine (definitely over a minute).

Let's try the same with coroutines:

val c = AtomicInteger()

for (i in 1..1_000_000)
    launch(CommonPool) {
        c.addAndGet(i)
    }

println(c.get())

This example completes in less than a second for me, but it prints some arbitrary number, because some coroutines don't finish before main() prints the result. Let's fix that.

We could use the same means of synchronization that are applicable to threads (a CountDownLatch is what crosses my mind in this case), but let's take a safer and cleaner path.

Async: returning a value from a coroutine

Another way of starting a coroutine is async {}. It is like launch {}, but returns an instance of Deferred<T>, which has an await() function that returns the result of the coroutine. Deferred<T> is a very basic future (fully-fledged JDK futures are also supported, but here we'll confine ourselves to Deferred for now).

Let's create a million coroutines again, keeping their Deferred objects. Now there's no need in the atomic counter, as we can just return the numbers to be added from our coroutines:

val deferred = (1..1_000_000).map { n ->
    async (CommonPool) {
        n
    }
}

All these have already started, all we need is collect the results:

val sum = deferred.sumBy { it.await() }

We simply take every coroutine and await its result here, then all results are added together by the standard library function sumBy(). But the compiler rightfully complains:

Suspend functions are only allowed to be called from a coroutine or another suspend function

await() can not be called outside a coroutine, because it needs to suspend until the computation finishes, and only coroutines can suspend in a non-blocking way. So, let's put this inside a coroutine:

runBlocking {
    val sum = deferred.sumBy { it.await() }
    println("Sum: $sum")
}

Now it prints something sensible: 1784293664, because all coroutines complete.

Let's also make sure that our coroutines actually run in parallel. If we add a 1-second delay() to each of the async's, the resulting program won't run for 1'000'000 seconds (over 11,5 days):

val deferred = (1..1_000_000).map { n ->
    async (CommonPool) {
        delay(1000)
        n
    }
}

This takes about 10 seconds on my machine, so yes, coroutines do run in parallel.

Suspending functions

Now, let's say we want to extract our workload (which is "wait 1 second and return a number") into a separate function:

fun workload(n: Int): Int {
    delay(1000)
    return n
}

A familiar error pops up:

Suspend functions are only allowed to be called from a coroutine or another suspend function

Let's dig a little into what it means. The biggest merit of coroutines is that they can suspend without blocking a thread. The compiler has to emit some special code to make this possible, so we have to mark functions that may suspend explicitly in the code. We use the suspend modifier for it:

suspend fun workload(n: Int): Int {
    delay(1000)
    return n
}

Now when we call workload() from a coroutine, the compiler knows that it may suspend and will prepare accordingly:

async (CommonPool) {
    workload(n)
}

Our workload() function can be called from a coroutine (or another suspending function), but can not be called from outside a coroutine. Naturally, delay() and await() that we used above are themselves declared as suspend, and this is why we had to put them inside runBlocking {}, launch {} or async {}.

Kotlin 1.1 的新特性

目錄

JavaScript

從 Kotlin 1.1 開(kāi)始,JavaScript 目標(biāo)平臺(tái)不再當(dāng)是實(shí)驗(yàn)性的。所有語(yǔ)言功能都支持,
并且有許多新的工具用于與前端開(kāi)發(fā)環(huán)境集成。更詳細(xì)改動(dòng)列表,請(qǐng)參見(jiàn)下文
。

協(xié)程(實(shí)驗(yàn)性的)

Kotlin 1.1 的關(guān)鍵新特性是協(xié)程,它帶來(lái)了 future/await、 yield 以及類似的編程模式的
支持。Kotlin 的設(shè)計(jì)中的關(guān)鍵特性是協(xié)程執(zhí)行的實(shí)現(xiàn)是語(yǔ)言庫(kù)的一部分,
而不是語(yǔ)言的一部分,所以你不必綁定任何特定的編程范式或并發(fā)庫(kù)。

協(xié)程實(shí)際上是一個(gè)輕量級(jí)的線程,可以掛起并稍后恢復(fù)。協(xié)程通過(guò)掛起函數(shù)支持:對(duì)這樣的函數(shù)的調(diào)用可能會(huì)掛起協(xié)程,并啟動(dòng)一個(gè)新的協(xié)程,我們通常使用匿名掛起函數(shù)(即掛起 lambda 表達(dá)式)。

我們來(lái)看看在外部庫(kù) kotlinx.coroutines 中實(shí)現(xiàn)的 async/await

// 在后臺(tái)線程池中運(yùn)行該代碼
fun asyncOverlay() = async(CommonPool) {
    // 啟動(dòng)兩個(gè)異步操作
    val original = asyncLoadImage("original")
    val overlay = asyncLoadImage("overlay")
    // 然后應(yīng)用疊加到兩個(gè)結(jié)果
    applyOverlay(original.await(), overlay.await())
}

// 在 UI 上下文中啟動(dòng)新的協(xié)程
launch(UI) {
    // 等待異步疊加完成
    val image = asyncOverlay().await()
    // 然后在 UI 中顯示
    showImage(image)
}

這里,async { …… } 啟動(dòng)一個(gè)協(xié)程,當(dāng)我們使用 await() 時(shí),掛起協(xié)程的執(zhí)行,而執(zhí)行正在等待的操作,并且在等待的操作完成時(shí)恢復(fù)(可能在不同的線程上) 。

標(biāo)準(zhǔn)庫(kù)通過(guò) yieldyieldAll 函數(shù)使用協(xié)程來(lái)支持惰性生成序列
在這樣的序列中,在取回每個(gè)元素之后掛起返回序列元素的代碼塊,
并在請(qǐng)求下一個(gè)元素時(shí)恢復(fù)。這里有一個(gè)例子:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
//sampleStart
  val seq = buildSequence {
      for (i in 1..5) {
          // 產(chǎn)生一個(gè) i 的平方
          yield(i * i)
      }
      // 產(chǎn)生一個(gè)區(qū)間
      yieldAll(26..28)
  }
  
  // 輸出該序列
  println(seq.toList())
//sampleEnd
}

</div>

運(yùn)行上面的代碼以查看結(jié)果。隨意編輯它并再次運(yùn)行!

更多信息請(qǐng)參見(jiàn)協(xié)程文檔教程。

請(qǐng)注意,協(xié)程目前還是一個(gè)實(shí)驗(yàn)性的功能,這意味著 Kotlin 團(tuán)隊(duì)不承諾
在最終的 1.1 版本時(shí)保持該功能的向后兼容性。

其他語(yǔ)言功能

類型別名

類型別名允許你為現(xiàn)有類型定義備用名稱。
這對(duì)于泛型類型(如集合)以及函數(shù)類型最有用。
這里有幾個(gè)例子:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

//sampleStart
typealias OscarWinners = Map<String, String>

fun countLaLaLand(oscarWinners: OscarWinners) =
        oscarWinners.count { it.value.contains("La La Land") }

// 請(qǐng)注意,類型名稱(初始名和類型別名)是可互換的:
fun checkLaLaLandIsTheBestMovie(oscarWinners: Map<String, String>) =
        oscarWinners["Best picture"] == "La La Land"
//sampleEnd

fun oscarWinners(): OscarWinners {
    return mapOf(
            "Best song" to "City of Stars (La La Land)",
            "Best actress" to "Emma Stone (La La Land)",
            "Best picture" to "Moonlight" /* …… */)
}

fun main(args: Array<String>) {
    val oscarWinners = oscarWinners()

    val laLaLandAwards = countLaLaLand(oscarWinners)
    println("LaLaLandAwards = $laLaLandAwards (in our small example), but actually it's 6.")

    val laLaLandIsTheBestMovie = checkLaLaLandIsTheBestMovie(oscarWinners)
    println("LaLaLandIsTheBestMovie = $laLaLandIsTheBestMovie")
}

</div>

更詳細(xì)信息請(qǐng)參閱其 KEEP。

已綁定的可調(diào)用引用

現(xiàn)在可以使用 :: 操作符來(lái)獲取指向特定對(duì)象實(shí)例的方法或?qū)傩缘?a target="_blank">成員引用。
以前這只能用 lambda 表達(dá)式表示。
這里有一個(gè)例子:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

//sampleStart
val numberRegex = "\\d+".toRegex()
val numbers = listOf("abc", "123", "456").filter(numberRegex::matches)
//sampleEnd

fun main(args: Array<String>) {
    println("Result is $numbers")
}

</div>

更詳細(xì)信息請(qǐng)參閱其 KEEP。

密封類和數(shù)據(jù)類

Kotlin 1.1 刪除了一些對(duì) Kotlin 1.0 中已存在的密封類和數(shù)據(jù)類的限制。
現(xiàn)在你可以在同一個(gè)文件中的任何地方定義一個(gè)密封類的子類,而不只是以作為密封類嵌套類的方式。
數(shù)據(jù)類現(xiàn)在可以擴(kuò)展其他類。
這可以用來(lái)友好且清晰地定義一個(gè)表達(dá)式類的層次結(jié)構(gòu):

<div class="sample" markdown="1" data-min-compiler-version="1.1">

//sampleStart
sealed class Expr

data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}
val e = eval(Sum(Const(1.0), Const(2.0)))
//sampleEnd

fun main(args: Array<String>) {
    println("e is $e") // 3.0
}

</div>

更詳細(xì)信息請(qǐng)參閱其文檔或者
密封類
數(shù)據(jù)類的 KEEP。

lambda 表達(dá)式中的解構(gòu)

現(xiàn)在可以使用解構(gòu)聲明語(yǔ)法來(lái)解開(kāi)傳遞給 lambda 表達(dá)式的參數(shù)。
這里有一個(gè)例子:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
//sampleStart
    val map = mapOf(1 to "one", 2 to "two")
    // 之前
    println(map.mapValues { entry ->
        val (key, value) = entry
        "$key -> $value!"
    })
    // 現(xiàn)在
    println(map.mapValues { (key, value) -> "$key -> $value!" })
//sampleEnd    
}

</div>

更詳細(xì)信息請(qǐng)參閱其文檔及其 KEEP。

下劃線用于未使用的參數(shù)

對(duì)于具有多個(gè)參數(shù)的 lambda 表達(dá)式,可以使用 _ 字符替換不使用的參數(shù)的名稱:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
    val map = mapOf(1 to "one", 2 to "two")

//sampleStart
    map.forEach { _, value -> println("$value!") }
//sampleEnd    
}

</div>

這也適用于解構(gòu)聲明

<div class="sample" markdown="1" data-min-compiler-version="1.1">

data class Result(val value: Any, val status: String)

fun getResult() = Result(42, "ok").also { println("getResult() returns $it") }

fun main(args: Array<String>) {
//sampleStart
    val (_, status) = getResult()
//sampleEnd
    println("status is '$status'")
}

</div>

更詳細(xì)信息請(qǐng)參閱其 KEEP。

數(shù)字字面值中的下劃線

正如在 Java 8 中一樣,Kotlin 現(xiàn)在允許在數(shù)字字面值中使用下劃線來(lái)分隔數(shù)字分組:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

//sampleStart
val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
//sampleEnd

fun main(args: Array<String>) {
    println(oneMillion)
    println(hexBytes.toString(16))
    println(bytes.toString(2))
}

</div>

更詳細(xì)信息請(qǐng)參閱其 KEEP

對(duì)于屬性的更短語(yǔ)法

對(duì)于沒(méi)有自定義訪問(wèn)器、或者將 getter 定義為表達(dá)式主體的屬性,現(xiàn)在可以省略屬性的類型:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

//sampleStart
data class Person(val name: String, val age: Int) {
    val isAdult get() = age >= 20 // 屬性類型推斷為 “Boolean”
}
//sampleEnd

fun main(args: Array<String>) {
    val akari = Person("Akari", 26)
    println("$akari.isAdult = ${akari.isAdult}")
}

</div>

內(nèi)聯(lián)屬性訪問(wèn)器

如果屬性沒(méi)有幕后字段,現(xiàn)在可以使用 inline 修飾符來(lái)標(biāo)記該屬性訪問(wèn)器。
這些訪問(wèn)器的編譯方式與內(nèi)聯(lián)函數(shù)相同。

<div class="sample" markdown="1" data-min-compiler-version="1.1">

//sampleStart
public val <T> List<T>.lastIndex: Int
    inline get() = this.size - 1
//sampleEnd

fun main(args: Array<String>) {
    val list = listOf('a', 'b')
    // 其 getter 會(huì)內(nèi)聯(lián)
    println("Last index of $list is ${list.lastIndex}")
}

</div>

你也可以將整個(gè)屬性標(biāo)記為 inline——這樣修飾符應(yīng)用于兩個(gè)訪問(wèn)器。

更詳細(xì)信息請(qǐng)參閱其文檔及其 KEEP。

局部委托屬性

現(xiàn)在可以對(duì)局部變量使用委托屬性語(yǔ)法。
一個(gè)可能的用途是定義一個(gè)延遲求值的局部變量:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

import java.util.Random

fun needAnswer() = Random().nextBoolean()

fun main(args: Array<String>) {
//sampleStart
    val answer by lazy {
        println("Calculating the answer...")
        42
    }
    if (needAnswer()) {                     // 返回隨機(jī)值
        println("The answer is $answer.")   // 此時(shí)計(jì)算出答案
    }
    else {
        println("Sometimes no answer is the answer...")
    }
//sampleEnd
}

</div>

更詳細(xì)信息請(qǐng)參閱其 KEEP。

委托屬性綁定的攔截

對(duì)于委托屬性,現(xiàn)在可以使用 provideDelegate 操作符攔截委托到屬性之間的綁定
。
例如,如果我們想要在綁定之前檢查屬性名稱,我們可以這樣寫:

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(thisRef: MyUI, property: KProperty<*>): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, property.name)
        …… // 屬性創(chuàng)建
    }

    private fun checkProperty(thisRef: MyUI, name: String) { …… }
}

fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }

class MyUI {
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

provideDelegate 方法在創(chuàng)建 MyUI 實(shí)例期間將會(huì)為每個(gè)屬性調(diào)用,并且可以立即執(zhí)行
必要的驗(yàn)證。

更詳細(xì)信息請(qǐng)參閱其文檔。

泛型枚舉值訪問(wèn)

現(xiàn)在可以用泛型的方式來(lái)對(duì)枚舉類的值進(jìn)行枚舉:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

//sampleStart
enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}
//sampleEnd

fun main(args: Array<String>) {
    printAllValues<RGB>() // 輸出 RED, GREEN, BLUE
}

</div>

對(duì)于 DSL 中隱式接收者的作用域控制

@DslMarker 注解允許限制來(lái)自 DSL 上下文中的外部作用域的接收者的使用。
考慮那個(gè)典型的 HTML 構(gòu)建器示例

table {
    tr {
        td { +"Text" }
    }
}

在 Kotlin 1.0 中,傳遞給 td 的 lambda 表達(dá)式中的代碼可以訪問(wèn)三個(gè)隱式接收者:傳遞給 table、tr
td 的。 這允許你調(diào)用在上下文中沒(méi)有意義的方法——例如在 td 里面調(diào)用 tr,從而
<td> 中放置一個(gè) <tr> 標(biāo)簽。

在 Kotlin 1.1 中,你可以限制這種情況,以使只有在 td 的隱式接收者上定義的方法
會(huì)在傳給 td 的 lambda 表達(dá)式中可用。你可以通過(guò)定義標(biāo)記有 @DslMarker 元注解的注解
并將其應(yīng)用于標(biāo)記類的基類。

更詳細(xì)信息請(qǐng)參閱其文檔及其 KEEP。

rem 操作符

mod 操作符現(xiàn)已棄用,而使用 rem 取代。動(dòng)機(jī)參見(jiàn)這個(gè)問(wèn)題

標(biāo)準(zhǔn)庫(kù)

字符串到數(shù)字的轉(zhuǎn)換

在 String 類中有一些新的擴(kuò)展,用來(lái)將它轉(zhuǎn)換為數(shù)字,而不會(huì)在無(wú)效數(shù)字上拋出異常:
String.toIntOrNull(): Int?、 String.toDoubleOrNull(): Double? 等。

val port = System.getenv("PORT")?.toIntOrNull() ?: 80

還有整數(shù)轉(zhuǎn)換函數(shù),如 Int.toString()String.toInt()、 String.toIntOrNull(),
每個(gè)都有一個(gè)帶有 radix 參數(shù)的重載,它允許指定轉(zhuǎn)換的基數(shù)(2 到 36)。

onEach()

onEach 是一個(gè)小、但對(duì)于集合和序列很有用的擴(kuò)展函數(shù),它允許對(duì)操作鏈中
的集合/序列的每個(gè)元素執(zhí)行一些操作,可能帶有副作用。
對(duì)于迭代其行為像 forEach 但是也進(jìn)一步返回可迭代實(shí)例。 對(duì)于序列它返回一個(gè)
包裝序列,它在元素迭代時(shí)延遲應(yīng)用給定的動(dòng)作。

inputDir.walk()
        .filter { it.isFile && it.name.endsWith(".txt") }
        .onEach { println("Moving $it to $outputDir") }
        .forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) }

also()、takeIf() 和 takeUnless()

這些是適用于任何接收者的三個(gè)通用擴(kuò)展函數(shù)。

also 就像 apply:它接受接收者、做一些動(dòng)作、并返回該接收者。
二者區(qū)別是在 apply 內(nèi)部的代碼塊中接收者是 this,
而在 also 內(nèi)部的代碼塊中是 it(并且如果你想的話,你可以給它另一個(gè)名字)。
當(dāng)你不想掩蓋來(lái)自外部作用域的 this 時(shí)這很方便:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

class Block {
    lateinit var content: String
}

//sampleStart
fun Block.copy() = Block().also {
    it.content = this.content
}
//sampleEnd

// 使用“apply”代替
fun Block.copy1() = Block().apply {
    this.content = this@copy1.content
}

fun main(args: Array<String>) {
    val block = Block().apply { content = "content" }
    val copy = block.copy()
    println("Testing the content was copied:")
    println(block.content == copy.content)
}

</div>

takeIf 就像單個(gè)值的 filter。它檢查接收者是否滿足該謂詞,并
在滿足時(shí)返回該接收者否則不滿足時(shí)返回 null。
結(jié)合 elvis-操作符和及早返回,它允許編寫如下結(jié)構(gòu):

val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false
// 對(duì)現(xiàn)有的 outDirFile 做些事情

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
    val input = "Kotlin"
    val keyword = "in"

//sampleStart
    val index = input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found")
    // 對(duì)輸入字符串中的關(guān)鍵字索引做些事情,鑒于它已找到
//sampleEnd
    
    println("'$keyword' was found in '$input'")
    println(input)
    println(" ".repeat(index) + "^")
}

</div>

takeUnlesstakeIf 相同,只是它采用了反向謂詞。當(dāng)它 滿足謂詞時(shí)返回接收者,否則返回 null。因此,上面的示例之一可以用 takeUnless 重寫如下:

val index = input.indexOf(keyword).takeUnless { it < 0 } ?: error("keyword not found")

當(dāng)你有一個(gè)可調(diào)用的引用而不是 lambda 時(shí),使用也很方便:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

private fun testTakeUnless(string: String) {
//sampleStart
    val result = string.takeUnless(String::isEmpty)
//sampleEnd

    println("string = \"$string\"; result = \"$result\"")
}

fun main(args: Array<String>) {
    testTakeUnless("")
    testTakeUnless("abc")
}

</div>

groupingBy()

此 API 可以用于按照鍵對(duì)集合進(jìn)行分組,并同時(shí)折疊每個(gè)組。 例如,它可以用于
計(jì)算文本中字符的頻率:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
    val words = "one two three four five six seven eight nine ten".split(' ')
//sampleStart
    val frequencies = words.groupingBy { it.first() }.eachCount()
//sampleEnd
    println("Counting first letters: $frequencies.")

    // 另一種方式是使用“groupBy”和“mapValues”創(chuàng)建一個(gè)中間的映射,
    // 而“groupingBy”的方式會(huì)即時(shí)計(jì)數(shù)。
    val groupBy = words.groupBy { it.first() }.mapValues { (_, list) -> list.size }
    println("Comparing the result with using 'groupBy': ${groupBy == frequencies}.")
}

</div>

Map.toMap() 和 Map.toMutableMap()

這倆函數(shù)可以用來(lái)簡(jiǎn)易復(fù)制映射:

class ImmutablePropertyBag(map: Map<String, Any>) {
    private val mapCopy = map.toMap()
}

Map.minus(key)

運(yùn)算符 plus 提供了一種將鍵值對(duì)添加到只讀映射中以生成新映射的方法,但是沒(méi)有一種簡(jiǎn)單的方法來(lái)做相反的操作:從映射中刪除一個(gè)鍵采用不那么直接的方式如 Map.filter()Map.filterKeys()。
現(xiàn)在運(yùn)算符 minus 填補(bǔ)了這個(gè)空白。有 4 個(gè)可用的重載:用于刪除單個(gè)鍵、鍵的集合、鍵的序列和鍵的數(shù)組。

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
//sampleStart
    val map = mapOf("key" to 42)
    val emptyMap = map - "key"
//sampleEnd
    
    println("map: $map")
    println("emptyMap: $emptyMap")
}

</div>

minOf() 和 maxOf()

這些函數(shù)可用于查找兩個(gè)或三個(gè)給定值中的最小和最大值,其中值是原生數(shù)字或 Comparable 對(duì)象。每個(gè)函數(shù)還有一個(gè)重載,它接受一個(gè)額外的 Comparator 實(shí)例,如果你想比較自身不可比的對(duì)象的話。

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
//sampleStart
    val list1 = listOf("a", "b")
    val list2 = listOf("x", "y", "z")
    val minSize = minOf(list1.size, list2.size)
    val longestList = maxOf(list1, list2, compareBy { it.size })
//sampleEnd
    
    println("minSize = $minSize")
    println("longestList = $longestList")
}

</div>

類似數(shù)組的列表實(shí)例化函數(shù)

類似于 Array 構(gòu)造函數(shù),現(xiàn)在有創(chuàng)建 ListMutableList 實(shí)例的函數(shù),并通過(guò)
調(diào)用 lambda 表達(dá)式來(lái)初始化每個(gè)元素:

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
//sampleStart
    val squares = List(10) { index -> index * index }
    val mutable = MutableList(10) { 0 }
//sampleEnd

    println("squares: $squares")
    println("mutable: $mutable")
}

</div>

Map.getValue()

Map 上的這個(gè)擴(kuò)展函數(shù)返回一個(gè)與給定鍵相對(duì)應(yīng)的現(xiàn)有值,或者拋出一個(gè)異常,提示找不到該鍵。
如果該映射是用 withDefault 生成的,這個(gè)函數(shù)將返回默認(rèn)值,而不是拋異常。

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {

//sampleStart    
    val map = mapOf("key" to 42)
    // 返回不可空 Int 值 42
    val value: Int = map.getValue("key")

    val mapWithDefault = map.withDefault { k -> k.length }
    // 返回 4
    val value2 = mapWithDefault.getValue("key2")

    // map.getValue("anotherKey") // <- 這將拋出 NoSuchElementException
//sampleEnd
    
    println("value is $value")
    println("value2 is $value2")
}

</div>

抽象集合

這些抽象類可以在實(shí)現(xiàn) Kotlin 集合類時(shí)用作基類。
對(duì)于實(shí)現(xiàn)只讀集合,有 AbstractCollection、 AbstractList、 AbstractSetAbstractMap,
而對(duì)于可變集合,有 AbstractMutableCollection、 AbstractMutableList、 AbstractMutableSetAbstractMutableMap
在 JVM 上,這些抽象可變集合從 JDK 的抽象集合繼承了大部分的功能。

數(shù)組處理函數(shù)

標(biāo)準(zhǔn)庫(kù)現(xiàn)在提供了一組用于逐個(gè)元素操作數(shù)組的函數(shù):比較
contentEqualscontentDeepEquals),哈希碼計(jì)算(contentHashCodecontentDeepHashCode),
以及轉(zhuǎn)換成一個(gè)字符串(contentToStringcontentDeepToString)。它們都支持 JVM
(它們作為 java.util.Arrays 中的相應(yīng)函數(shù)的別名)和 JS(在
Kotlin 標(biāo)準(zhǔn)庫(kù)中提供實(shí)現(xiàn))。

<div class="sample" markdown="1" data-min-compiler-version="1.1">

fun main(args: Array<String>) {
//sampleStart
    val array = arrayOf("a", "b", "c")
    println(array.toString())  // JVM 實(shí)現(xiàn):類型及哈希亂碼
    println(array.contentToString())  // 良好格式化為列表
//sampleEnd
}

</div>

JVM 后端

Java 8 字節(jié)碼支持

Kotlin 現(xiàn)在可以選擇生成 Java 8 字節(jié)碼(命令行選項(xiàng) -jvm-target 1.8或者Ant/Maven/Gradle 中
的相應(yīng)選項(xiàng))。目前這并不改變字節(jié)碼的語(yǔ)義(特別是,接口和 lambda 表達(dá)式中的默認(rèn)方法
的生成與 Kotlin 1.0 中完全一樣),但我們計(jì)劃在以后進(jìn)一步使用它。

Java 8 標(biāo)準(zhǔn)庫(kù)支持

現(xiàn)在有支持在 Java 7 和 8 中新添加的 JDK API 的標(biāo)準(zhǔn)庫(kù)的獨(dú)立版本。
如果你需要訪問(wèn)新的 API,請(qǐng)使用 kotlin-stdlib-jre7kotlin-stdlib-jre8 maven 構(gòu)件,而不是標(biāo)準(zhǔn)的 kotlin-stdlib。
這些構(gòu)件是在 kotlin-stdlib 之上的微小擴(kuò)展,它們將它作為傳遞依賴項(xiàng)帶到項(xiàng)目中。

字節(jié)碼中的參數(shù)名

Kotlin 現(xiàn)在支持在字節(jié)碼中存儲(chǔ)參數(shù)名。這可以使用命令行選項(xiàng) -java-parameters 啟用。

常量?jī)?nèi)聯(lián)

編譯器現(xiàn)在將 const val 屬性的值內(nèi)聯(lián)到使用它們的位置。

可變閉包變量

用于在 lambda 表達(dá)式中捕獲可變閉包變量的裝箱類不再具有 volatile 字段。
此更改提高了性能,但在一些罕見(jiàn)的使用情況下可能導(dǎo)致新的競(jìng)爭(zhēng)條件。如果受此影響,你需要提供
自己的同步機(jī)制來(lái)訪問(wèn)變量。

javax.scripting 支持

Kotlin 現(xiàn)在與javax.script API(JSR-223)集成。
其 API 允許在運(yùn)行時(shí)求值代碼段:

val engine = ScriptEngineManager().getEngineByExtension("kts")!!
engine.eval("val x = 3")
println(engine.eval("x + 2"))  // 輸出 5

關(guān)于使用 API 的示例項(xiàng)目參見(jiàn)這里
。

kotlin.reflect.full

為 Java 9 支持準(zhǔn)備,在 kotlin-reflect.jar 庫(kù)中的擴(kuò)展函數(shù)和屬性已移動(dòng)
kotlin.reflect.full 包中。舊包(kotlin.reflect)中的名稱已棄用,將在
Kotlin 1.2 中刪除。請(qǐng)注意,核心反射接口(如 KClass)是 Kotlin 標(biāo)準(zhǔn)庫(kù)
(而不是 kotlin-reflect)的一部分,不受移動(dòng)影響。

JavaScript 后端

統(tǒng)一的標(biāo)準(zhǔn)庫(kù)

Kotlin 標(biāo)準(zhǔn)庫(kù)的大部分目前可以從代碼編譯成 JavaScript 來(lái)使用。
特別是,關(guān)鍵類如集合(ArrayList、 HashMap 等)、異常(IllegalArgumentException 等)以及其他
幾個(gè)關(guān)鍵類(StringBuilder、 Comparator)現(xiàn)在都定義在 kotlin 包下。在 JVM 平臺(tái)上,一些名稱是相應(yīng) JDK 類的
類型別名,而在 JS 平臺(tái)上,這些類在 Kotlin 標(biāo)準(zhǔn)庫(kù)中實(shí)現(xiàn)。

更好的代碼生成

JavaScript 后端現(xiàn)在生成更加可靜態(tài)檢查的代碼,這對(duì) JS 代碼處理工具(如
minifiers、 optimisers、 linters 等)更加友好。

external 修飾符

如果你需要以類型安全的方式在 Kotlin 中訪問(wèn) JavaScript 實(shí)現(xiàn)的類,
你可以使用 external 修飾符寫一個(gè) Kotlin 聲明。(在 Kotlin 1.0 中,使用了 @native 注解。)
與 JVM 目標(biāo)平臺(tái)不同,JS 平臺(tái)允許對(duì)類和屬性使用 external 修飾符。
例如,可以按以下方式聲明 DOM Node 類:

external class Node {
    val firstChild: Node

    fun appendChild(child: Node): Node

    fun removeChild(child: Node): Node

    // 等等
}

改進(jìn)的導(dǎo)入處理

現(xiàn)在可以更精確地描述應(yīng)該從 JavaScript 模塊導(dǎo)入的聲明。
如果在外部聲明上添加 @JsModule("<模塊名>") 注解,它會(huì)在編譯期間正確導(dǎo)入
到模塊系統(tǒng)(CommonJS或AMD)。例如,使用 CommonJS,該聲明會(huì)
通過(guò) require(……) 函數(shù)導(dǎo)入。
此外,如果要將聲明作為模塊或全局 JavaScript 對(duì)象導(dǎo)入,
可以使用 @JsNonModule 注解。

例如,以下是將 JQuery 導(dǎo)入 Kotlin 模塊的方法:

external interface JQuery {
    fun toggle(duration: Int = definedExternally): JQuery
    fun click(handler: (Event) -> Unit): JQuery
}

@JsModule("jquery")
@JsNonModule
@JsName("$")
external fun jquery(selector: String): JQuery

在這種情況下,JQuery 將作為名為 jquery 的模塊導(dǎo)入?;蛘撸梢杂米?$-對(duì)象,
這取決于Kotlin編譯器配置使用哪個(gè)模塊系統(tǒng)。

你可以在應(yīng)用程序中使用如下所示的這些聲明:

fun main(args: Array<String>) {
    jquery(".toggle-button").click {
        jquery(".toggle-panel").toggle(300)
    }
}

參考資料

1.https://blog.jetbrains.com/kotlin/2016/07/first-glimpse-of-kotlin-1-1-coroutines-type-aliases-and-more/


Kotlin 開(kāi)發(fā)者社區(qū)

國(guó)內(nèi)第一Kotlin 開(kāi)發(fā)者社區(qū)公眾號(hào),主要分享、交流 Kotlin 編程語(yǔ)言、Spring Boot、Android、React.js/Node.js、函數(shù)式編程、編程思想等相關(guān)主題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,814評(píng)論 0 10
  • The Inner Game of Tennis W Timothy Gallwey Jonathan Cape ...
    網(wǎng)事_79a3閱讀 12,878評(píng)論 3 20
  • 微商,該怎么去堅(jiān)持? 功能介紹全球微商資訊,中國(guó)第一微商資訊推廣平臺(tái)。幫助微商學(xué)習(xí)、交流、互助、推廣、為微商提供優(yōu)...
    那樣花閱讀 345評(píng)論 0 0
  • 快速成功,有可能是一朝頓悟,但這需要厚積才能薄發(fā)。而且厚積也是一個(gè)長(zhǎng)期積累的過(guò)程。歸根到底,快速成功還是很難出現(xiàn),...
    咸魚(yú)也要有夢(mèng)想閱讀 229評(píng)論 0 0
  • “你站在橋上看風(fēng)景,看風(fēng)景的人在樓上看你,明月裝飾了你的窗子,你裝飾了別人的夢(mèng)”——卞之琳《斷章》 最近聽(tīng)了看了一...
    木鈴鐺閱讀 430評(píng)論 0 0

友情鏈接更多精彩內(nèi)容