译自《What's New in Kotlin 1.1》
JavaScript
从Kotlin 1.1开始,JavaScript目标不再被认为是实验性的。 支持所有语言功能,还有许多新工具可用于与前端开发环境集成。 有关更改的更详细列表,请参阅下文(below) 。
协同程序(实验性)
Kotlin 1.1中的关键新功能是协同程序(coroutines) ,支持async
/await
,yield
和类似的编程模式。 Kotlin设计的关键特征是协同程序执行(coroutine execution)的实现是库的一部分,而不是语言,所以你不必受任何特定的编程范例或并发库的约束。
协同程序实际上是一种轻质线程(light-weight thread),可以在以后暂停和恢复(suspended and resumed later)。 通过挂起函数(suspending functions)支持协同程序:调用这样一个函数可能会暂停协同程序,而要启动一个新的协同程序,我们通常使用一个匿名挂起函数(anonymous suspending functions)(即suspending lambdas)。
我们来看看async
/await
,这是在外部库kotlinx.coroutines中实现的:
// 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 { ... }
启动协同程序,当我们使用await()
时,在正被await()
的操作被执行的同时,协同程序的执行被暂停,并且在正被await()
的操作完成时被恢复(可能在不同的线程上)。
标准库使用协程(coroutines)来支持具有yield()
和yieldAll()
函数的延迟生成的序列 (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())
}
运行上面的代码查看结果。 随意编辑它并再次运行!
有关更多信息,请参阅协程文档(coroutine documentation)和教程(tutorial) 。
请注意,协程程序目前被认为是一个实验功能 ,这意味着Kotlin团队在最终的1.1版本之后不承诺支持此功能的向后兼容性。
其他语言功能
Type aliases
类型别名允许您为现有类型定义替代名称。 这对于通用类型(如集合)以及函数类型最为有用。 这是一个例子:
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)
现在可以使用::
运算符来获取指向特定对象实例的方法或属性的成员引用 。 以前只能用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了解更多详情。
密封和数据类 (Sealed and data classes)
Kotlin 1.1删除了Kotlin 1.0中存在的封装和数据类的一些限制。 现在,您可以在同一个文件的顶层定义顶级密封类(sealed class)的子类,而不仅仅是封装类的嵌套类(nested classes of the sealed class)。 数据类(Data classes)现在可以扩展其他类。 这可以用来很好且干净地定义表达式类的层次结构(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)和数据类(data class) 的KEEP了解更多详细信息。
lambda表达式的解构(Destructuring in lambdas)
现在可以使用解构声明(destructuring declaration)语法来解包传递给lambda的参数。 以下是一个例子:
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了解更多详情。
用于未使用参数的下划线 (Underscores for unused parameters)
对于具有多个参数的lambda,可以使用_
字符替换不使用的参数的名称:
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了解更多详情。
用于数字文字的下划线 (Underscores in numeric literals)
就像Java 8一样,Kotlin现在允许在数字文字中使用下划线来分隔数字组:
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定义为表达式主体的属性,现在可以省略属性类型:
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}")
}
内联属性访问器 (Inline property accessors)
您现在可以使用inline
修饰符标记属性访问器,如果属性没有后缀字段。 这些访问器的编译方式与内联函数相同。
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)
您现在可以对局部变量使用委托属性(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) ,现在可以使用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)
}
在创建MyUI实例期间,将为每个属性调用provideDelegate
方法,并且可以立即执行必要的验证。
阅读文档(documentation )了解更多详情。
通用枚举值访问 (Generic enum value access)
现在可以以通用的方式枚举枚举类的值。
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上下文中来自外部范围的接收器的使用。 考虑规范的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
元注释的注释并将其应用于标记类的基类来实现。
阅读文档(documentation)和KEEP了解更多详情。
rem操作符
mod
运算符现在已被弃用,而rem
则被替代。 看到这个(this issue)问题的动机。
标准库
字符串到数字转换 (String to number conversions)
在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()
是一个小而有用的扩展函数,用于集合和序列,它允许在操作链中的集合/序列的每个元素上执行一些可能具有副作用的操作。 在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()
这些是三种通用扩展函数,适用于任何接收器(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)每个组。 例如,它可以用于计算从每个字母开始的单词数:
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()
。 现在操作符minus
弥补了这个差距。 有4个重载可用:用于删除单个键(a single key),一合集键(a collection of keys),一系列键(a sequence of keys)和一数组键(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()
这些函数可用于查找两个或三个给定值的最小和最大值,其中值是原始数字或Comparable
对象。 如果要比较本身不可比较的对象,那么每个函数也会占用一个附加的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实例化函数 (Array-like List instantiation functions)
类似于Array
构造函数,现在有了创建List
和MutableList
实例的函数,并通过调用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
上的这个扩展返回与给定键对应的现有值或引发异常,并提及未找到哪个键。 如果映射是使用withDefault
生成,则此函数将返回默认值,而不是抛出异常。
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)
这些抽象类可以在实现Kotlin集合类时用作基类。 为了实现只读集合,有AbstractCollection
, AbstractList
, AbstractSet
和AbstractMap
,对于可变集合,还有AbstractMutableCollection
, AbstractMutableList
, AbstractMutableSet
和AbstractMutableMap
。 在JVM上,这些抽象可变集合从JDK的抽象集合继承其大部分功能。
数组操作功能 (Array manipulation functions)
标准库现在提供了一组用于逐个元素操作的函数:比较( contentEquals
和contentDeepEquals
),哈希码计算( contentHashCode
和contentDeepHashCode
)以及转换为字符串( contentToString
和contentDeepToString
)。 它们都支持JVM(它们作为java.util.Arrays的相应函数的别名)和JS(在Kotlin标准库中提供实现)。
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字节码支持 (Java 8 bytecode support)
Kotlin现在可以选择生成Java 8字节码( -jvm-target 1.8
命令行选项或Ant/Maven/Gradle中的相应选项)。 现在这并不会改变字节码的语义(特别是接口和lambdas中的默认方法与Kotlin 1.0完全相同),但是我们打算进一步利用这一点。
Java 8标准库支持 (Java 8 standard library support)
现在有标准库的单独版本,支持在Java 7和8中添加的新JDK API。如果需要访问新的API,请使用kotlin-stdlib-jre7
和kotlin-stdlib-jre8
maven 工件(artifacts),而不是标准的kotlin-stdlib
。 这些工件(artifacts)是在kotlin-stdlib
之上的微型扩展,它们将它作为传递依赖关系( transitive dependency)带入您的项目。
字节码中的参数名称 (Parameter names in the bytecode)
Kotlin现在支持在字节码中存储参数名称。 这可以使用-java-parameters
命令行选项启用。
常量内联 (Constant inlining)
现在编译器将const val
属性的值嵌入到使用它们的位置。
可变闭包变量 (Mutable closure variables)
用于捕获不再具有volatile字段的lambdas中的可变闭包变量(mutable closure variables)的框类(box classes)。 这种改变提高了性能,但是在一些罕见的使用情况下可能会导致新的竞争条件。 如果受此影响,您需要提供自己的同步方式来访问变量。
javax.script支持 (javax.script support)
Kotlin现在与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
库中的扩展函数和属性已被移动到包kotlin.reflect.full
。 旧包中的名称( kotlin.reflect
)已被弃用,将在Kotlin 1.2中删除。 请注意,核心反射接口(如KClass
)是Kotlin标准库的一部分,不是kotlin-reflect
的部分 ,不受此次移动的影响。
JavaScript后端
统一标准库 (Unified standard library)
Kotlin标准库的大部分现在可以从编译为JavaScript的代码中使用。 特别地, kotlin包下定义了关键类,如集合( ArrayList
, HashMap
等),异常( IllegalArgumentException
等)和其他几个( StringBuilder
, Comparator
)。 在JVM上,这些名称是相应JDK类的类型别名(type aliases),而在JS上,这些类在Kotlin标准库中实现。
更好的代码生成 (Better code generation)
JavaScript后端现在可以生成更多的静态可检查代码,这对JS代码处理工具(比如minifier
,optimizers
,linters
等)来说更为友善。
external修饰符 (The external modifier)
如果您需要以类型安全的方式访问Kotlin中以JavaScript实现的类,则可以使用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)
您现在可以更精确地描述应该从JavaScript模块导入的声明。 如果在外部声明中添加了@JsModule("<module-name>")
注释,那么在编译期间它将被正确地导入模块系统(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)
}
}