譯自《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中,傳遞給td的lambda中的代碼可以訪問三個隱含的接收器:傳遞給table ,給tr和給td 。 這允許您調用在上下文中無意義的方法 - 例如在td調用tr,從而將<tr>標簽放在<td> 。
在Kotlin 1.1中,您可以限制,因此只有在td的隱式接收器上定義的方法才能在傳遞給td的lambda內部可用。 您可以通過定義標注有@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) + "^")
}
takeUnless與takeIf相同,但它需要反向預測(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)建List和MutableList實例的函數(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 , AbstractSet和AbstractMap ,對于可變集合,還有AbstractMutableCollection , AbstractMutableList, AbstractMutableSet和AbstractMutableMap 。 在JVM上,這些抽象可變集合從JDK的抽象集合繼承其大部分功能。
數(shù)組操作功能 (Array manipulation functions)
標準庫現(xiàn)在提供了一組用于逐個元素操作的函數(shù):比較( contentEquals和contentDeepEquals ),哈希碼計算( contentHashCode和contentDeepHashCode )以及轉換為字符串( contentToString和contentDeepToString )。 它們都支持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-jre7和kotlin-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代碼處理工具(比如minifier,optimizers,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)
}
}