《Kotlin 程序设计》第十二章 Kotlin的多线程:协程(Coroutines)

简介: 第十二章 Kotlin的多线程:协程(Coroutines)正式上架:《Kotlin极简教程》Official on shelves: Kotlin Programming minimalist tutorial京东JD:https://item.

第十二章 Kotlin的多线程:协程(Coroutines)

正式上架:《Kotlin极简教程》Official on shelves: Kotlin Programming minimalist tutorial
京东JD:https://item.jd.com/12181725.html
天猫Tmall:https://detail.tmall.com/item.htm?id=558540170670

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 开始,JavaScript 目标平台不再当是实验性的。所有语言功能都支持,
并且有许多新的工具用于与前端开发环境集成。更详细改动列表,请参见下文

协程(实验性的)

Kotlin 1.1 的关键新特性是协程,它带来了 future/awaityield 以及类似的编程模式的
支持。Kotlin 的设计中的关键特性是协程执行的实现是语言库的一部分,
而不是语言的一部分,所以你不必绑定任何特定的编程范式或并发库。

协程实际上是一个轻量级的线程,可以挂起并稍后恢复。协程通过挂起函数支持:对这样的函数的调用可能会挂起协程,并启动一个新的协程,我们通常使用匿名挂起函数(即挂起 lambda 表达式)。

我们来看看在外部库 kotlinx.coroutines 中实现的 async/await

// 在后台线程池中运行该代码
fun asyncOverlay() = async(CommonPool) {
    // 启动两个异步操作
    val original = asyncLoadImage("original")
    val overlay = asyncLoadImage("overlay")
    // 然后应用叠加到两个结果
    applyOverlay(original.await(), overlay.await())
}

// 在 UI 上下文中启动新的协程
launch(UI) {
    // 等待异步叠加完成
    val image = asyncOverlay().await()
    // 然后在 UI 中显示
    showImage(image)
}

这里,async { …… } 启动一个协程,当我们使用 await() 时,挂起协程的执行,而执行正在等待的操作,并且在等待的操作完成时恢复(可能在不同的线程上) 。

标准库通过 yieldyieldAll 函数使用协程来支持惰性生成序列
在这样的序列中,在取回每个元素之后挂起返回序列元素的代码块,
并在请求下一个元素时恢复。这里有一个例子:

<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) {
          // 产生一个 i 的平方
          yield(i * i)
      }
      // 产生一个区间
      yieldAll(26..28)
  }
  
  // 输出该序列
  println(seq.toList())
//sampleEnd
}

</div>

运行上面的代码以查看结果。随意编辑它并再次运行!

更多信息请参见协程文档教程

请注意,协程目前还是一个实验性的功能,这意味着 Kotlin 团队不承诺
在最终的 1.1 版本时保持该功能的向后兼容性。

其他语言功能

类型别名

类型别名允许你为现有类型定义备用名称。
这对于泛型类型(如集合)以及函数类型最有用。
这里有几个例子:

<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") }

// 请注意,类型名称(初始名和类型别名)是可互换的:
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>

更详细信息请参阅其 KEEP

已绑定的可调用引用

现在可以使用 :: 操作符来获取指向特定对象实例的方法或属性的成员引用
以前这只能用 lambda 表达式表示。
这里有一个例子:

<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>

更详细信息请参阅其 KEEP

密封类和数据类

Kotlin 1.1 删除了一些对 Kotlin 1.0 中已存在的密封类和数据类的限制。
现在你可以在同一个文件中的任何地方定义一个密封类的子类,而不只是以作为密封类嵌套类的方式。
数据类现在可以扩展其他类。
这可以用来友好且清晰地定义一个表达式类的层次结构:

<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>

更详细信息请参阅其文档或者
密封类
数据类的 KEEP。

lambda 表达式中的解构

现在可以使用解构声明语法来解开传递给 lambda 表达式的参数。
这里有一个例子:

<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!"
    })
    // 现在
    println(map.mapValues { (key, value) -> "$key -> $value!" })
//sampleEnd    
}

</div>

更详细信息请参阅其文档及其 KEEP

下划线用于未使用的参数

对于具有多个参数的 lambda 表达式,可以使用 _ 字符替换不使用的参数的名称:

<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>

这也适用于解构声明

<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>

更详细信息请参阅其 KEEP

数字字面值中的下划线

正如在 Java 8 中一样,Kotlin 现在允许在数字字面值中使用下划线来分隔数字分组:

<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>

更详细信息请参阅其 KEEP

对于属性的更短语法

对于没有自定义访问器、或者将 getter 定义为表达式主体的属性,现在可以省略属性的类型:

<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>

内联属性访问器

如果属性没有幕后字段,现在可以使用 inline 修饰符来标记该属性访问器。
这些访问器的编译方式与内联函数相同。

<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 会内联
    println("Last index of $list is ${list.lastIndex}")
}

</div>

你也可以将整个属性标记为 inline——这样修饰符应用于两个访问器。

更详细信息请参阅其文档及其 KEEP

局部委托属性

现在可以对局部变量使用委托属性语法。
一个可能的用途是定义一个延迟求值的局部变量:

<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()) {                     // 返回随机值
        println("The answer is $answer.")   // 此时计算出答案
    }
    else {
        println("Sometimes no answer is the answer...")
    }
//sampleEnd
}

</div>

更详细信息请参阅其 KEEP

委托属性绑定的拦截

对于委托属性,现在可以使用 provideDelegate 操作符拦截委托到属性之间的绑定

例如,如果我们想要在绑定之前检查属性名称,我们可以这样写:

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

    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 方法在创建 MyUI 实例期间将会为每个属性调用,并且可以立即执行
必要的验证。

更详细信息请参阅其文档

泛型枚举值访问

现在可以用泛型的方式来对枚举类的值进行枚举:

<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>

对于 DSL 中隐式接收者的作用域控制

@DslMarker 注解允许限制来自 DSL 上下文中的外部作用域的接收者的使用。
考虑那个典型的 HTML 构建器示例

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

在 Kotlin 1.0 中,传递给 td 的 lambda 表达式中的代码可以访问三个隐式接收者:传递给 tabletr
td 的。 这允许你调用在上下文中没有意义的方法——例如在 td 里面调用 tr,从而
<td> 中放置一个 <tr> 标签。

在 Kotlin 1.1 中,你可以限制这种情况,以使只有在 td 的隐式接收者上定义的方法
会在传给 td 的 lambda 表达式中可用。你可以通过定义标记有 @DslMarker 元注解的注解
并将其应用于标记类的基类。

更详细信息请参阅其文档及其 KEEP

rem 操作符

mod 操作符现已弃用,而使用 rem 取代。动机参见这个问题

标准库

字符串到数字的转换

在 String 类中有一些新的扩展,用来将它转换为数字,而不会在无效数字上抛出异常:
String.toIntOrNull(): Int?String.toDoubleOrNull(): Double? 等。

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

还有整数转换函数,如 Int.toString()String.toInt()String.toIntOrNull()
每个都有一个带有 radix 参数的重载,它允许指定转换的基数(2 到 36)。

onEach()

onEach 是一个小、但对于集合和序列很有用的扩展函数,它允许对操作链中
的集合/序列的每个元素执行一些操作,可能带有副作用。
对于迭代其行为像 forEach 但是也进一步返回可迭代实例。 对于序列它返回一个
包装序列,它在元素迭代时延迟应用给定的动作。

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()

这些是适用于任何接收者的三个通用扩展函数。

also 就像 apply:它接受接收者、做一些动作、并返回该接收者。
二者区别是在 apply 内部的代码块中接收者是 this
而在 also 内部的代码块中是 it(并且如果你想的话,你可以给它另一个名字)。
当你不想掩盖来自外部作用域的 this 时这很方便:

<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 就像单个值的 filter。它检查接收者是否满足该谓词,并
在满足时返回该接收者否则不满足时返回 null
结合 elvis-操作符和及早返回,它允许编写如下结构:

val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false
// 对现有的 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")
    // 对输入字符串中的关键字索引做些事情,鉴于它已找到
//sampleEnd
    
    println("'$keyword' was found in '$input'")
    println(input)
    println(" ".repeat(index) + "^")
}

</div>

takeUnlesstakeIf 相同,只是它采用了反向谓词。当它 满足谓词时返回接收者,否则返回 null。因此,上面的示例之一可以用 takeUnless 重写如下:

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

当你有一个可调用的引用而不是 lambda 时,使用也很方便:

<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 可以用于按照键对集合进行分组,并同时折叠每个组。 例如,它可以用于
计算文本中字符的频率:

<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”创建一个中间的映射,
    // 而“groupingBy”的方式会即时计数。
    val groupBy = words.groupBy { it.first() }.mapValues { (_, list) -> list.size }
    println("Comparing the result with using 'groupBy': ${groupBy == frequencies}.")
}

</div>

Map.toMap() 和 Map.toMutableMap()

这俩函数可以用来简易复制映射:

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

Map.minus(key)

运算符 plus 提供了一种将键值对添加到只读映射中以生成新映射的方法,但是没有一种简单的方法来做相反的操作:从映射中删除一个键采用不那么直接的方式如 Map.filter()Map.filterKeys()
现在运算符 minus 填补了这个空白。有 4 个可用的重载:用于删除单个键、键的集合、键的序列和键的数组。

<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()

这些函数可用于查找两个或三个给定值中的最小和最大值,其中值是原生数字或 Comparable 对象。每个函数还有一个重载,它接受一个额外的 Comparator 实例,如果你想比较自身不可比的对象的话。

<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>

类似数组的列表实例化函数

类似于 Array 构造函数,现在有创建 ListMutableList 实例的函数,并通过
调用 lambda 表达式来初始化每个元素:

<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 上的这个扩展函数返回一个与给定键相对应的现有值,或者抛出一个异常,提示找不到该键。
如果该映射是用 withDefault 生成的,这个函数将返回默认值,而不是抛异常。

<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>

抽象集合

这些抽象类可以在实现 Kotlin 集合类时用作基类。
对于实现只读集合,有 AbstractCollectionAbstractListAbstractSetAbstractMap
而对于可变集合,有 AbstractMutableCollectionAbstractMutableListAbstractMutableSetAbstractMutableMap
在 JVM 上,这些抽象可变集合从 JDK 的抽象集合继承了大部分的功能。

数组处理函数

标准库现在提供了一组用于逐个元素操作数组的函数:比较
contentEqualscontentDeepEquals),哈希码计算(contentHashCodecontentDeepHashCode),
以及转换成一个字符串(contentToStringcontentDeepToString)。它们都支持 JVM
(它们作为 java.util.Arrays 中的相应函数的别名)和 JS(在
Kotlin 标准库中提供实现)。

<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 实现:类型及哈希乱码
    println(array.contentToString())  // 良好格式化为列表
//sampleEnd
}

</div>

JVM 后端

Java 8 字节码支持

Kotlin 现在可以选择生成 Java 8 字节码(命令行选项 -jvm-target 1.8或者Ant/Maven/Gradle 中
的相应选项)。目前这并不改变字节码的语义(特别是,接口和 lambda 表达式中的默认方法
的生成与 Kotlin 1.0 中完全一样),但我们计划在以后进一步使用它。

Java 8 标准库支持

现在有支持在 Java 7 和 8 中新添加的 JDK API 的标准库的独立版本。
如果你需要访问新的 API,请使用 kotlin-stdlib-jre7kotlin-stdlib-jre8 maven 构件,而不是标准的 kotlin-stdlib
这些构件是在 kotlin-stdlib 之上的微小扩展,它们将它作为传递依赖项带到项目中。

字节码中的参数名

Kotlin 现在支持在字节码中存储参数名。这可以使用命令行选项 -java-parameters 启用。

常量内联

编译器现在将 const val 属性的值内联到使用它们的位置。

可变闭包变量

用于在 lambda 表达式中捕获可变闭包变量的装箱类不再具有 volatile 字段。
此更改提高了性能,但在一些罕见的使用情况下可能导致新的竞争条件。如果受此影响,你需要提供
自己的同步机制来访问变量。

javax.scripting 支持

Kotlin 现在与javax.script API(JSR-223)集成。
其 API 允许在运行时求值代码段:

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

关于使用 API 的示例项目参见这里

kotlin.reflect.full

为 Java 9 支持准备,在 kotlin-reflect.jar 库中的扩展函数和属性已移动
kotlin.reflect.full 包中。旧包(kotlin.reflect)中的名称已弃用,将在
Kotlin 1.2 中删除。请注意,核心反射接口(如 KClass)是 Kotlin 标准库
(而不是 kotlin-reflect)的一部分,不受移动影响。

JavaScript 后端

统一的标准库

Kotlin 标准库的大部分目前可以从代码编译成 JavaScript 来使用。
特别是,关键类如集合(ArrayListHashMap 等)、异常(IllegalArgumentException 等)以及其他
几个关键类(StringBuilderComparator)现在都定义在 kotlin 包下。在 JVM 平台上,一些名称是相应 JDK 类的
类型别名,而在 JS 平台上,这些类在 Kotlin 标准库中实现。

更好的代码生成

JavaScript 后端现在生成更加可静态检查的代码,这对 JS 代码处理工具(如
minifiers、 optimisers、 linters 等)更加友好。

external 修饰符

如果你需要以类型安全的方式在 Kotlin 中访问 JavaScript 实现的类,
你可以使用 external 修饰符写一个 Kotlin 声明。(在 Kotlin 1.0 中,使用了 @native 注解。)
与 JVM 目标平台不同,JS 平台允许对类和属性使用 external 修饰符。
例如,可以按以下方式声明 DOM Node 类:

external class Node {
    val firstChild: Node

    fun appendChild(child: Node): Node

    fun removeChild(child: Node): Node

    // 等等
}

改进的导入处理

现在可以更精确地描述应该从 JavaScript 模块导入的声明。
如果在外部声明上添加 @JsModule("<模块名>") 注解,它会在编译期间正确导入
到模块系统(CommonJS或AMD)。例如,使用 CommonJS,该声明会
通过 require(……) 函数导入。
此外,如果要将声明作为模块或全局 JavaScript 对象导入,
可以使用 @JsNonModule 注解。

例如,以下是将 JQuery 导入 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 的模块导入。或者,它可以用作 $-对象,
这取决于Kotlin编译器配置使用哪个模块系统。

你可以在应用程序中使用如下所示的这些声明:

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/

相关文章
|
1月前
|
安全 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美结合
【2月更文挑战第30天】在移动开发领域,性能优化和流畅的用户体验是关键。本文深入探讨了如何通过结合Kotlin语言和协程技术来提升Android应用的性能和响应能力。我们将分析Kotlin的优势,介绍协程的基本概念,并通过实际案例展示如何在应用中实现协程以简化异步编程,从而提供更加高效的解决方案。
|
1月前
|
移动开发 Java Android开发
构建高效Android应用:Kotlin与协程的完美融合
【2月更文挑战第25天】 在移动开发领域,性能优化和应用响应性的提升是永恒的追求。随着Android Jetpack组件库的不断丰富,Kotlin语言已经成为Android开发的首选。而Kotlin协程作为一种新的并发处理方案,它以轻量级线程的形式,为开发者提供了简洁高效的异步编程手段。本文将深入探讨Kotlin协程在Android应用中的实践运用,以及如何通过这种技术改善用户界面的流畅度和后台任务的处理能力,进而构建出更高效、更稳定的Android应用。
|
1月前
|
Java 调度 Android开发
构建高效Android应用:探究Kotlin多线程编程
【2月更文挑战第17天】 在现代移动开发领域,性能优化一直是开发者关注的焦点。特别是在Android平台上,合理利用多线程技术可以显著提升应用程序的响应性和用户体验。本文将深入探讨使用Kotlin进行Android多线程编程的策略与实践,旨在为开发者提供系统化的解决方案和性能提升技巧。我们将从基础概念入手,逐步介绍高级特性,并通过实际案例分析如何有效利用Kotlin协程、线程池以及异步任务处理机制来构建一个更加高效的Android应用。
41 4
|
1月前
|
API 数据库 Android开发
构建高效Android应用:探究Kotlin多线程优化策略
【2月更文挑战第14天】随着移动设备性能的日益强大,用户对应用程序的响应速度和流畅性要求越来越高。在Android开发中,合理利用多线程技术是提升应用性能的关键手段之一。Kotlin作为一种现代的编程语言,其协程特性为开发者提供了更为简洁高效的多线程处理方式。本文将深入探讨使用Kotlin进行Android多线程编程的最佳实践,包括协程的基本概念、优势以及在实际项目中的应用场景和性能优化技巧,旨在帮助开发者构建更加高效稳定的Android应用。
|
6天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
26 0
|
1月前
|
移动开发 调度 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【2月更文挑战第21天】随着移动开发技术的不断进步,Android平台正寻求更高效、更简洁的编程解决方案。Kotlin作为一种现代编程语言,以其简洁性和对函数式编程的支持赢得了开发者的青睐。而协程作为处理异步任务的强大工具,在提升应用性能方面显示出巨大潜力。本文将深入探讨Kotlin语言和协程技术如何相辅相成,帮助开发者构建更加流畅和高效的Android应用。
|
1月前
|
API 数据库 Android开发
构建高效安卓应用:Kotlin与协程的完美融合
【2月更文挑战第20天】在移动开发领域,性能优化和资源管理始终是开发者关注的焦点。随着Kotlin语言在Android平台的普及,协程作为其核心特性之一,提供了一种全新的异步编程范式。本文将深入探讨如何通过Kotlin协程提升安卓应用的性能,减少内存消耗,并实现流畅的用户体验。我们将分析协程的原理,展示其在实际应用中的效能,并提供实践指南,帮助开发者掌握这一强有力的工具。
14 0
|
1月前
|
Java Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【2月更文挑战第17天】 随着移动设备性能的不断提升,用户对应用的响应速度和稳定性要求越来越高。在Android开发中,Kotlin语言以其简洁、安全的特点受到开发者青睐。然而,面对复杂的多线程任务,如何有效利用Kotlin进行优化,以提升应用性能,是本文探讨的重点。通过分析Kotlin并发工具的使用场景与限制,结合实例演示其在Android开发中的实践,旨在为开发者提供实用的多线程处理指南。
|
1月前
|
安全 Java Android开发
构建高效Android应用:Kotlin与协程的完美融合
【2月更文挑战第15天】随着移动开发技术的不断进步,Android平台的开发者寻求更高效的编程解决方案以提升应用性能和用户体验。Kotlin作为官方推荐的开发语言,以其简洁性和功能安全性受到青睐。而协程,作为一种轻量级的线程管理机制,在异步编程和网络请求等方面展现出巨大潜力。本文将深入探讨Kotlin语言结合协程如何优化Android应用的性能,包括提高响应性、简化异步代码结构以及减少资源消耗等方面,旨在为开发者提供实战指南,帮助他们构建更加流畅和高效的Android应用。
|
14天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信

热门文章

最新文章