Kotlin 1.1的新功能

譯自《What's New in Kotlin 1.1》

JavaScript

從Kotlin 1.1開始,JavaScript目標不再被認為是實驗性的。 支持所有語言功能,還有許多新工具可用于與前端開發(fā)環(huán)境集成。 有關更改的更詳細列表,請參閱下文(below) 。

協(xié)同程序(實驗性)

Kotlin 1.1中的關鍵新功能是協(xié)同程序(coroutines) ,支持async/await,yield和類似的編程模式。 Kotlin設計的關鍵特征是協(xié)同程序執(zhí)行(coroutine execution)的實現(xiàn)是庫的一部分,而不是語言,所以你不必受任何特定的編程范例或并發(fā)庫的約束。

協(xié)同程序實際上是一種輕質線程(light-weight thread),可以在以后暫停和恢復(suspended and resumed later)。 通過掛起函數(shù)(suspending functions)支持協(xié)同程序:調用這樣一個函數(shù)可能會暫停協(xié)同程序,而要啟動一個新的協(xié)同程序,我們通常使用一個匿名掛起函數(shù)(anonymous suspending functions)(即suspending lambdas)。

我們來看看async/await ,這是在外部庫kotlinx.coroutines中實現(xiàn)的:

// runs the code in the background thread pool
fun asyncOverlay() = async(CommonPool) {
    // start two async operations
    val original = asyncLoadImage("original")
    val overlay = asyncLoadImage("overlay")
    // and then apply overlay to both results
    applyOverlay(original.await(), overlay.await())
}

// launches new coroutine in UI context
launch(UI) {
    // wait for async overlay to complete
    val image = asyncOverlay().await()
    // and then show it in UI
    showImage(image)
}

這里,async { ... }啟動協(xié)同程序,當我們使用await()時,在正被await()的操作被執(zhí)行的同時,協(xié)同程序的執(zhí)行被暫停,并且在正被await()的操作完成時被恢復(可能在不同的線程上)。

標準庫使用協(xié)程(coroutines)來支持具有yield()yieldAll()函數(shù)的延遲生成的序列 (lazily generated sequences)。 在這樣的序列中,返回序列元素的代碼塊在每個元素被檢索之后被暫停,并且當請求下一個元素時被恢復。 以下是一個例子:

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val seq = buildSequence {
        for (i in 1..5) {
            // yield a square of i
            yield(i * i)
        }
        // yield a range
        yieldAll(26..28)
    }

    // print the sequence
    println(seq.toList())
}

運行上面的代碼查看結果。 隨意編輯它并再次運行!

有關更多信息,請參閱協(xié)程文檔(coroutine documentation)和教程(tutorial) 。

請注意,協(xié)程程序目前被認為是一個實驗功能 ,這意味著Kotlin團隊在最終的1.1版本之后不承諾支持此功能的向后兼容性。

其他語言功能

Type aliases

類型別名允許您為現(xiàn)有類型定義替代名稱。 這對于通用類型(如集合)以及函數(shù)類型最為有用。 這是一個例子:

typealias OscarWinners = Map<String, String>

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

// Note that the type names (initial and the type alias) are interchangeable:
fun checkLaLaLandIsTheBestMovie(oscarWinners: Map<String, String>) =
oscarWinners["Best picture"] == "La La Land"

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

有關詳細信息,請參閱文檔(documentation)和KEEP。

綁定的可調用引用 (Bound callable references)

現(xiàn)在可以使用::運算符來獲取指向特定對象實例的方法或屬性的成員引用 。 以前只能用lambda表示。 以下是一個例子:

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

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

閱讀文檔(documentation)和KEEP了解更多詳情。

密封和數(shù)據(jù)類 (Sealed and data classes)

Kotlin 1.1刪除了Kotlin 1.0中存在的封裝和數(shù)據(jù)類的一些限制。 現(xiàn)在,您可以在同一個文件的頂層定義頂級密封類(sealed class)的子類,而不僅僅是封裝類的嵌套類(nested classes of the sealed class)。 數(shù)據(jù)類(Data classes)現(xiàn)在可以擴展其他類。 這可以用來很好且干凈地定義表達式類的層次結構(hierarchy of expression classes):

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

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

閱讀文檔(documentation)或密封類(sealed class)和數(shù)據(jù)類(data class) 的KEEP了解更多詳細信息。

lambda表達式的解構(Destructuring in lambdas)

現(xiàn)在可以使用解構聲明(destructuring declaration)語法來解包傳遞給lambda的參數(shù)。 以下是一個例子:

fun main(args: Array<String>) {
    val map = mapOf(1 to "one", 2 to "two")
    // before
    println(map.mapValues { entry ->
                           val (key, value) = entry
                           "$key -> $value!"
                          })
    // now
    println(map.mapValues { (key, value) -> "$key -> $value!" })    
}

閱讀文檔(documentation)和KEEP了解更多詳情。

用于未使用參數(shù)的下劃線 (Underscores for unused parameters)

對于具有多個參數(shù)的lambda,可以使用_字符替換不使用的參數(shù)的名稱:

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

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

這也在解構聲明中起作用:

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

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

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

閱讀KEEP了解更多詳情。

用于數(shù)字文字的下劃線 (Underscores in numeric literals)

就像Java 8一樣,Kotlin現(xiàn)在允許在數(shù)字文字中使用下劃線來分隔數(shù)字組:

val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

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

閱讀KEEP了解更多詳情。

更短的屬性語法 (Shorter syntax for properties)

對于將getter定義為表達式主體的屬性,現(xiàn)在可以省略屬性類型:

data class Person(val name: String, val age: Int) {
    val isAdult get() = age >= 20 // Property type inferred to be 'Boolean'
}

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

內聯(lián)屬性訪問器 (Inline property accessors)

您現(xiàn)在可以使用inline修飾符標記屬性訪問器,如果屬性沒有后綴字段。 這些訪問器的編譯方式與內聯(lián)函數(shù)相同。

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

fun main(args: Array<String>) {
    val list = listOf('a', 'b')
    // the getter will be inlined
    println("Last index of $list is ${list.lastIndex}")
}

您也可以將整個屬性標記為inline - 那么就會將修飾符應用于兩個訪問器。

閱讀文檔(documentation)和KEEP 了解更多詳情。

本地委托屬性 (Local delegated properties)

您現(xiàn)在可以對局部變量使用委托屬性(delegated property )語法。 一個可能的用途是定義一個懶惰的局部變量:

import java.util.Random

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

fun main(args: Array<String>) {
    val answer by lazy {
        println("Calculating the answer...")
        42
    }
    if (needAnswer()) {                     // returns the random value
        println("The answer is $answer.")   // answer is calculated at this point
    }
    else {
        println("Sometimes no answer is the answer...")
    }
}

閱讀KEEP了解更多詳情。

攔截委托屬性綁定 (Interception of delegated property binding)

對于委托屬性(delegated properties) ,現(xiàn)在可以使用provideDelegate運算符攔截對屬性綁定的委托(intercept delegate to property binding)。 例如,如果我們要在綁定之前檢查屬性名稱,我們可以這樣寫:

class ResourceLoader<T>(id: ResourceID<T>) {
   operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {
       checkProperty(thisRef, prop.name)
       ... // property creation
   }

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

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

閱讀文檔(documentation )了解更多詳情。

通用枚舉值訪問 (Generic enum value access)

現(xiàn)在可以以通用的方式枚舉枚舉類的值。

enum class RGB { RED, GREEN, BLUE }

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

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

DSL中隱式接收器的范圍控制 (Scope control for implicit receivers in DSLs)

@DslMarker注釋允許限制在DSL上下文中來自外部范圍的接收器的使用。 考慮規(guī)范的HTML構建器示例 (HTML builder example):

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

在Kotlin 1.0中,傳遞給tdlambda中的代碼可以訪問三個隱含的接收器:傳遞給table ,給tr和給td 。 這允許您調用在上下文中無意義的方法 - 例如在td調用tr,從而將<tr>標簽放在<td> 。

在Kotlin 1.1中,您可以限制,因此只有在td的隱式接收器上定義的方法才能在傳遞給tdlambda內部可用。 您可以通過定義標注有@DslMarker元注釋的注釋并將其應用于標記類的基類來實現(xiàn)。

閱讀文檔(documentation)和KEEP了解更多詳情。

rem操作符

mod運算符現(xiàn)在已被棄用,而rem則被替代。 看到這個(this issue)問題的動機。

標準庫

字符串到數(shù)字轉換 (String to number conversions)

在String類中有一堆新的擴展將其轉換為一個數(shù)字,而不會在無效數(shù)字上拋出異常: String.toIntOrNull(): Int?, String.toDoubleOrNull(): Double?等等

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

還有整數(shù)轉換函數(shù),如Int.toString(), String.toInt(), String.toIntOrNull() ,每個都使用radix參數(shù)進行重載,這允許指定轉換的基礎(2到36)。

onEach()

onEach()是一個小而有用的擴展函數(shù),用于集合和序列,它允許在操作鏈中的集合/序列的每個元素上執(zhí)行一些可能具有副作用的操作。 在iterable上,它的行為類似于forEach()但是還可以進一步返回iterable實例。 在序列(sequences)上,它返回一個包裝序列(wrapping sequence),它在元素被迭代時懶惰地應用給定的動作。

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

這些是三種通用擴展函數(shù),適用于任何接收器(receiver)。

also 就像 apply :它接受接收器(receiver),對它做一些動作,并返回該接收器(receiver)。 不同之處在于,在apply的塊內部,接收器(receiver)是作為this可用的,而在also的塊內部,接收器(receiver)作為it可用(如果需要,可以給它另一個名稱)。 當您不想遮擋外部范圍的this時,這很方便:

class Block {
    lateinit var content: String
}

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

// using 'apply' instead
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)
}

takeIf就像一個單一值的filter 。 它檢查接收器(receiver)是否滿足預測(predicate),且如果它滿足則返回接收器(receiver),或者如果沒有滿足則返回null 。 結合一個elvis操作符和早期的返回,它允許編寫如下結構:

val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false
// do something with existing outDirFile
fun main(args: Array<String>) {
    val input = "Kotlin"
    val keyword = "in"

    val index = input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found")
    // do something with index of keyword in input string, given that it's found
    
    println("'$keyword' was found in '$input'")
    println(input)
    println(" ".repeat(index) + "^")
}

takeUnlesstakeIf相同,但它需要反向預測(predicate)。 當它不符合預測(predicate)時返回接收器(receiver),否則返回null 。 所以上面的例子之一可以使用takeUnless重寫如下:

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

當您具有可調用引用(callable reference)而不是lambda時也很方便:

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

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

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

groupingBy()

此API可用于按鍵(key)分組,同時折疊(fold)每個組。 例如,它可以用于計算從每個字母開始的單詞數(shù):

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

    // The alternative way that uses 'groupBy' and 'mapValues' creates an intermediate map, 
    // while 'groupingBy' way counts on the fly.
    val groupBy = words.groupBy { it.first() }.mapValues { (_, list) -> list.size }
    println("Comparing the result with using 'groupBy': ${groupBy == frequencies}.")
}

Map.toMap() 以及Map.toMutableMap()

這些功能可用于輕松復制映射:

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

Map.minus(key)

操作符plus提供了一種方法來添加鍵值對到只讀映射生成一個新的映射,但是沒有一個簡單的方法來做相反的事情:從映射中刪除一個鍵你必須訴諸不太直接的方法,例如Map.filter()Map.filterKeys()。 現(xiàn)在操作符minus彌補了這個差距。 有4個重載可用:用于刪除單個鍵(a single key),一合集鍵(a collection of keys),一系列鍵(a sequence of keys)和一數(shù)組鍵(an array of keys)。

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

minOf() 以及 maxOf()

這些函數(shù)可用于查找兩個或三個給定值的最小和最大值,其中值是原始數(shù)字或Comparable對象。 如果要比較本身不可比較的對象,那么每個函數(shù)也會占用一個附加的Comparator實例。

fun main(args: Array<String>) {
    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 })
    
    println("minSize = $minSize")
    println("longestList = $longestList")
}

類似Array的List實例化函數(shù) (Array-like List instantiation functions)

類似于Array構造函數(shù),現(xiàn)在有了創(chuàng)建ListMutableList實例的函數(shù),并通過調用lambda初始化每個元素:

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

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

Map.getValue()

Map上的這個擴展返回與給定鍵對應的現(xiàn)有值或引發(fā)異常,并提及未找到哪個鍵。 如果映射是使用withDefault生成,則此函數(shù)將返回默認值,而不是拋出異常。

fun main(args: Array<String>) {


    val map = mapOf("key" to 42)
    // returns non-nullable Int value 42
    val value: Int = map.getValue("key")

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

    // map.getValue("anotherKey") // <- this will throw NoSuchElementException
    
    println("value is $value")
    println("value2 is $value2")
}

抽象集合 (Abstract collections)

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

數(shù)組操作功能 (Array manipulation functions)

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

fun main(args: Array<String>) {
    val array = arrayOf("a", "b", "c")
    println(array.toString())  // JVM implementation: type-and-hash gibberish
    println(array.contentToString())  // nicely formatted as list
}

JVM后端

Java 8字節(jié)碼支持 (Java 8 bytecode support)

Kotlin現(xiàn)在可以選擇生成Java 8字節(jié)碼( -jvm-target 1.8命令行選項或Ant/Maven/Gradle中的相應選項)。 現(xiàn)在這并不會改變字節(jié)碼的語義(特別是接口和lambdas中的默認方法與Kotlin 1.0完全相同),但是我們打算進一步利用這一點。

Java 8標準庫支持 (Java 8 standard library support)

現(xiàn)在有標準庫的單獨版本,支持在Java 7和8中添加的新JDK API。如果需要訪問新的API,請使用kotlin-stdlib-jre7kotlin-stdlib-jre8 maven 工件(artifacts),而不是標準的kotlin-stdlib 。 這些工件(artifacts)是在kotlin-stdlib之上的微型擴展,它們將它作為傳遞依賴關系( transitive dependency)帶入您的項目。

字節(jié)碼中的參數(shù)名稱 (Parameter names in the bytecode)

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

常量內聯(lián) (Constant inlining)

現(xiàn)在編譯器將const val屬性的值嵌入到使用它們的位置。

可變閉包變量 (Mutable closure variables)

用于捕獲不再具有volatile字段的lambdas中的可變閉包變量(mutable closure variables)的框類(box classes)。 這種改變提高了性能,但是在一些罕見的使用情況下可能會導致新的競爭條件。 如果受此影響,您需要提供自己的同步方式來訪問變量。

javax.script支持 (javax.script support)

Kotlin現(xiàn)在與javax.script API(JSR-223)集成。 API允許在運行時評估代碼段:

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

請參閱這里( here)使用API??的更大的示例項目。

kotlin.reflect.full

為了準備Java 9支持(prepare for Java 9 support) , kotlin-reflect.jar庫中的擴展函數(shù)和屬性已被移動到包kotlin.reflect.full。 舊包中的名稱( kotlin.reflect )已被棄用,將在Kotlin 1.2中刪除。 請注意,核心反射接口(如KClass )是Kotlin標準庫的一部分,不是kotlin-reflect的部分 ,不受此次移動的影響。

JavaScript后端

統(tǒng)一標準庫 (Unified standard library)

Kotlin標準庫的大部分現(xiàn)在可以從編譯為JavaScript的代碼中使用。 特別地, kotlin包下定義了關鍵類,如集合( ArrayList , HashMap等),異常( IllegalArgumentException等)和其他幾個( StringBuilder , Comparator)。 在JVM上,這些名稱是相應JDK類的類型別名(type aliases),而在JS上,這些類在Kotlin標準庫中實現(xiàn)。

更好的代碼生成 (Better code generation)

JavaScript后端現(xiàn)在可以生成更多的靜態(tài)可檢查代碼,這對JS代碼處理工具(比如minifieroptimizers,linters等)來說更為友善。

external修飾符 (The external modifier)

如果您需要以類型安全的方式訪問Kotlin中以JavaScript實現(xiàn)的類,則可以使用external修飾符編寫Kotlin聲明。 (在Kotlin 1.0中,使用@native注釋。) 與JVM目標不同,JS允許對類和屬性使用external修飾符(use external modifier with classes and properties)。 例如,下面是如何聲明DOM Node類:

external class Node {
    val firstChild: Node

    fun appendChild(child: Node): Node

    fun removeChild(child: Node): Node

    // etc
}

改進的import處理 (Improved import handling)

您現(xiàn)在可以更精確地描述應該從JavaScript模塊導入的聲明。 如果在外部聲明中添加了@JsModule("<module-name>")注釋,那么在編譯期間它將被正確地導入模塊系統(tǒng)(CommonJS或AMD)。 例如,使用CommonJS,聲明將通過require(...)函數(shù)導入。 另外,如果要將聲明導入為模塊或全局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編譯器配置使用的模塊系統(tǒng)。

您可以在應用程序中使用這些聲明,如下所示:

fun main(args: Array<String>) {
    jquery(".toggle-button").click {
        jquery(".toggle-panel").toggle(300)
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容