美文网首页
Kotlin 1.1的新功能

Kotlin 1.1的新功能

作者: 公子小水 | 来源:发表于2017-08-01 04:12 被阅读408次

    译自《What's New in Kotlin 1.1》

    JavaScript

    从Kotlin 1.1开始,JavaScript目标不再被认为是实验性的。 支持所有语言功能,还有许多新工具可用于与前端开发环境集成。 有关更改的更详细列表,请参阅下文(below) 。

    协同程序(实验性)

    Kotlin 1.1中的关键新功能是协同程序(coroutines) ,支持async/awaityield和类似的编程模式。 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中,传递给tdlambda中的代码可以访问三个隐含的接收器:传递给table ,给tr和给td 。 这允许您调用在上下文中无意义的方法 - 例如在td调用tr,从而将<tr>标签放在<td>

    在Kotlin 1.1中,您可以限制,因此只有在td的隐式接收器上定义的方法才能在传递给tdlambda内部可用。 您可以通过定义标注有@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) + "^")
    }
    

    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)每个组。 例如,它可以用于计算从每个字母开始的单词数:

    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构造函数,现在有了创建ListMutableList实例的函数,并通过调用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集合类时用作基类。 为了实现只读集合,有AbstractCollectionAbstractListAbstractSetAbstractMap ,对于可变集合,还有AbstractMutableCollectionAbstractMutableListAbstractMutableSetAbstractMutableMap 。 在JVM上,这些抽象可变集合从JDK的抽象集合继承其大部分功能。

    数组操作功能 (Array manipulation functions)

    标准库现在提供了一组用于逐个元素操作的函数:比较( contentEqualscontentDeepEquals ),哈希码计算( contentHashCodecontentDeepHashCode )以及转换为字符串( contentToStringcontentDeepToString )。 它们都支持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-jre7kotlin-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包下定义了关键类,如集合( ArrayListHashMap等),异常( IllegalArgumentException等)和其他几个( StringBuilderComparator)。 在JVM上,这些名称是相应JDK类的类型别名(type aliases),而在JS上,这些类在Kotlin标准库中实现。

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

    JavaScript后端现在可以生成更多的静态可检查代码,这对JS代码处理工具(比如minifieroptimizerslinters等)来说更为友善。

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

    相关文章

      网友评论

          本文标题:Kotlin 1.1的新功能

          本文链接:https://www.haomeiwen.com/subject/iqerlxtx.html