美文网首页
Kotlin 1.7.0 正式发布!主要新特性一览

Kotlin 1.7.0 正式发布!主要新特性一览

作者: 我爱田Hebe | 来源:发表于2022-07-14 15:18 被阅读0次

    6月9号,Kotlin 发布了1.7.0正式版。本文将大致过一遍主要的新特性。

    本文主要来自 What's new in Kotlin 1.7.0 | Kotlin,部分来自 Kotlin 1.7.0-Beta 现已发布 | The Kotlin Blog ,完整内容也请参考这些链接。受限于本人水平,难免有误,敬请谅解。

    作者:FunnySaltyFish (github.com)

    以下是此版本主要更新:

    新的 Kotlin K2 编译器

    此 Kotlin 版本引入了新的 Kotlin K2 编译器的 Alpha 版本。新的编译器旨在加快新语言功能的开发,统一Kotlin支持的所有平台,带来性能改进,并为编译器扩展提供API。

    我们已经发布了一些关于我们的新编译器及其优点的详细说明:

    需要强调的是,对于新K2编译器的Alpha版本,我们主要关注性能改进,并且它仅适用于JVM项目。它不支持Kotlin / JS,Kotlin / Native 或其他多平台项目,并且包括 kapt 在内的编译器插件都无法使用它。

    我们的基准测试显示了我们内部项目的一些杰出成果:

    Project 现版本 Kotlin 编译器 新的 K2 Kotlin 编译器 相对提升
    Kotlin 2.2 KLOC/s 4.8 KLOC/s ~ x2.2
    YouTrack 1.8 KLOC/s 4.2 KLOC/s ~ x2.3
    IntelliJ IDEA 1.8 KLOC/s 3.9 KLOC/s ~ x2.2
    Space 1.2 KLOC/s 2.8 KLOC/s ~ x2.3

    KLOC/s 为每秒钟编译器处理的源代码行数(千行)

    您可以查看 JVM 项目的性能提升,并将其与旧编译器的结果进行比较。要启用 Kotlin K2 编译器,请使用以下编译器选项:

    -Xuse-k2
    
    

    此外,K2 编译器 还包括许多错误修复: fixed-in-frontend-ir sort by: Priority, votes, updated)。请注意,此列表中以 State: Open 开头的 bugs 实际上在 K2 里也已得到修复。

    下一个 Kotlin 版本将提高 K2 编译器的稳定性并提供更多功能,敬请期待并提供您的反馈!

    语法

    内联类的内联值也能委托了

    如果要为值或类实例创建轻量级wrapper,则必须手动实现所有接口方法,委托实现解决了这个问题。但在 1.7.0 之前,它不适用于内联类。此限制已被删除,因此您现在可以创建在大多数情况下不分配内存的轻量级wrapper。

    // 接口,唯一的方法返回一个字符串
    interface Bar {
        fun foo() = "foo"
    }
    
    @JvmInline
    // kt1.7之前,对内联类不能如此写
    // 否则会报 "Value class cannot implement an interface by delegation if expression is not a parameter"
    // kt1.7 解除了这个限制
    value class BarWrapper(val bar: Bar): Bar by bar
    
    fun main() {
        val bw = BarWrapper(object: Bar {})
        println(bw.foo())
    }
    
    

    类型参数的下划线运算符

    Kotlin 1.7.0 为类型参数引入了一个下划线运算符 。可以使用它在指定其他类型时自动推断类型参数:_

    abstract class SomeClass<T> {
        abstract fun execute(): T
    }
    
    class SomeImplementation : SomeClass<String>() {
        override fun execute(): String = "Test"
    }
    
    class OtherImplementation : SomeClass<Int>() {
        override fun execute(): Int = 42
    }
    
    object Runner {
        inline fun <reified S: SomeClass<T>, T> run(): T {
            return S::class.java.getDeclaredConstructor().newInstance().execute()
        }
    }
    
    fun main() {
        // T 被推断为 String,因为 SomeImplementation 继承自 SomeClass<String>
        val s = Runner.run<SomeImplementation, _>()
        assert(s == "Test")
    
        // T 被推断为 Int ,因为 OtherImplementation 继承自 SomeClass<Int>
        val n = Runner.run<OtherImplementation, _>()
        assert(n == 42)
    }
    
    

    构建器推断变更

    构建器推断 (Builder inference) 是一种特殊的类型推断,在调用泛型构建器函数时很有帮助。 它可以帮助编译器推断调用的类型实参,方法是使用其 lambda 实参中其他调用的相关类型信息。

    在此版本中,如果常规类型推断无法获得有关类型的足够信息,即可自动激活构建器推断。(以前需额外指定 -Xenable-builder-inference 编译器选项——在 1.6.0 版本中引入)。这意味着现在您无需应用任何额外的注解或选项,即可编写自己的使用构建器类型推断的构建器。

    FunnySaltyFish:放几个栗子:

    val result = buildList {
        // Type argument is inferred into Float based on the expected type
        val x: Float = get(0)
    } // result has the List<Float> type
    
    
    fun takeMyLong(x: Long) { ... }
    
    fun String.isMoreThat3() = length > 3
    
    fun takeListOfStrings(x: List<String>) { ... }
    
    fun main() {
        val result1 = buildList {
            val x = get(0)
            takeMyLong(x)
        } // result1 has the List<Long> type
    
        val result2 = buildList {
            val x = get(0)
            val isLong = x.isMoreThat3()
        // ...
        } // result2 has the List<String> type
    
        val result3 = buildList {
            takeListOfStrings(this)
        } // result3 has the List<String> type
    }
    
    

    更多内容详见: 了解如何编写自定义通用构建器

    稳定的 opt-in requirements

    Opt-in requirements 已为 Stable ,无需添加额外的编译器参数.

    在 1.7.0 之前, opt-in 需要指定参数 -opt-in=kotlin.RequiresOptIn 以避免 warning,现在不需要了; 不过,您仍然可使用 -opt-in 选择加入其他 annotations、 module-wise.

    稳定的明确非空类型

    在 Kotlin 1.7.0 中,绝对不可为 null 的类型已提升为 Stable。它们在扩展通用 Java 类和接口时提供了更好的互操作性。

    使用新语法 T & Any 标记此为明确非空(绝对不可空)类型。 此语法来自 intersection types 的符号。在 & 左侧为可空的类型参数,右侧为不可空的Any

    fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y
    
    fun main() {
        // OK
        elvisLike<String>("", "").length
        // 错误: 'null' 无法用于 non-null 值
        elvisLike<String>("", null).length
    
        // OK
        elvisLike<String?>(null, "").length
        // 错误: 'null' 无法用于 non-null 值
        elvisLike<String?>(null, null).length
    }
    
    

    在此 KEEP 中了解有关绝对不可为 null 的类型的更多信息。

    标准库

    在 Kotlin 1.7.0 中,标准库进行了一系列更改和改进。它们引入了新功能,稳定了实验性功能,并统一了对 Native、JS 和 JVM 的命名捕获组的支持:

    min() 和 max() 集合函数回归

    Kotlin 1.4 中,我们将 min()max() 集合函数重命名为 minOrNull()maxOrNull()。 这些新的名称能够更好地反映它们的行为 – 如果接收器集合为空,则返回 null。 它还有助于使函数的行为与整个 Kotlin Collections API 中使用的命名惯例保持一致。

    minBy()maxBy()minWith()maxWith() 同样如此,在 Kotlin 1.4 中均具有自己的 *OrNull() 同义词。 受此变更影响的旧函数已逐渐弃用。

    Kotlin 1.7.0-Beta 重新引入了原始的函数名称,但加入了不可空返回类型。 现在,更新后的 min()max()minBy()maxBy()minWith()maxWith() 会严格返回集合元素或抛出异常。

    fun main() {
        val numbers = listOf<int>()
        println(numbers.maxOrNull()) // "null"
        println(numbers.max()) // "Exception in… Collection is empty."
    }
    
    

    正则表达式特定位置匹配

    在 1.5.30 中引入Regex.matchAt()Regex.matchesAt() 函数现已达到稳定版本。 它们提供了一种方式来检查正则表达式在 StringCharSequence 中的特定位置是否具有精确匹配。

    • matchesAt() 可以检查匹配并返回布尔结果:
    fun main(){
        val releaseText = "Kotlin 1.7.0 is on its way!"
        // 正则表达式: 一个数字, “.”, 一个数字, “.”, 一个或多个数字
        val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
    
        println(versionRegex.matchesAt(releaseText, 0)) // "false"
        println(versionRegex.matchesAt(releaseText, 7)) // "true"
    }
    
    
    • matchAt() 会在找到匹配的情况下返回匹配,在未找到匹配的情况下返回 null
    fun main(){
        val releaseText = "Kotlin 1.7.0 is on its way!"
        val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
    
        println(versionRegex.matchAt(releaseText, 0)) // "null"
        println(versionRegex.matchAt(releaseText, 7)?.value) // "1.7.0"
    }
    
    

    对以前语言和 API 版本的扩展支持

    为了支持库作者 开发可在各种旧 Kotlin 版本中使用的库,并解决 Kotlin 主版本更新频率增加的问题,我们扩展了对以前语言和 API 版本的支持。

    在 Kotlin 1.7.0 中,我们支持三个以前的语言和 API 版本,而不是两个。这意味着 Kotlin 1.7.0 支持针对 1.4.0 的 Kotlin 版本的库开发。有关向后兼容性的详细信息,请参阅兼容性模式

    通过反射获取注解

    1.6.0 首次引入的 拓展函数 KAnnotatedElement.findAnnotations() 现已进入 Stable. 此 反射 函数返回某元素特定类型的所有注解, 包括 独立使用 和 重复使用 的注解.

    @Repeatable
    annotation class Tag(val name: String)
    
    @Tag("First Tag")
    @Tag("Second Tag")
    fun taggedFunction() {
        println("I'm a tagged function!")
    }
    
    fun main() {
        val x = ::taggedFunction
        val foo = x as KAnnotatedElement
        println(foo.findAnnotations<Tag>())
        // [@Tag(name=First Tag), @Tag(name=Second Tag)]
    }
    
    

    稳定的深度递归函数

    深度递归函数 (DeepRecursiveFunction) 自 Kotlin 1.4.0 以来一直作为实验性功能提供,现在它们在 Kotlin 1.7.0 中是稳定的。使用DeepRecursiveFunction 可以定义一个函数,该函数将其堆栈保留在堆上,而不是实际的调用堆栈。这允许您运行非常深的递归计算。使用invoke以调用这类函数。

    在此示例中,深度递归函数用于以递归方式计算二叉树的深度。即使此示例函数以递归方式调用自身 100,000 次,也不会抛出 StackOverflowError

    class Tree(val left: Tree?, val right: Tree?)
    
    val calculateDepth = DeepRecursiveFunction<Tree?, Int> { t ->
        if (t == null) 0 else maxOf(
            callRecursive(t.left),
            callRecursive(t.right)
        ) + 1
    }
    
    fun main() {
        // 生成一颗深度为 100000 的二叉树
        val deepTree = generateSequence(Tree(null, null)) { prev ->
            Tree(prev, null)
        }.take(100_000).last()
    
        println(calculateDepth(deepTree)) // 100000
    }
    
    

    若递归深度超过1000, 请考虑在代码中使用深度递归函数。

    基于默认时间源的时间标记现在基于内联类

    Kotlin 1.7.0 通过将 返回的时间标记更改为内联类,提高了时间测量功能的性能。这意味着调用像TimeSource.MonotonicmarkNow()elapsedNow()measureTime()measureTimedValue()TimeMark 这样的函数不会为其实例分配包装类。特别是在测量作为hot path一部分的代码段时,这有助于最大限度地减少测量对性能的影响:

    @OptIn(ExperimentalTime::class)
    fun main() {
        val mark = TimeSource.Monotonic.markNow() // 返回的 `TimeMark` 为内联类
        val elapsedDuration = mark.elapsedNow()
    }
    
    

    仅当从 TimeMark 中获取的时间源对 TimeSource.Monotonic 为静态时,此优化才可用。

    Java Optionals 的新实验性扩展函数

    Kotlin 1.7.0 附带了新的便利函数,简化了 Java 中Optional类的使用。这些新功能可用于在 JVM 上拆箱和转换可选对象,并帮助使 Java API 的使用更加简洁。

    拓展函数getOrNull()getOrDefault()getOrElse() 允许您获取 Optional 的值(如果有的话)。否则,将视情况获得null、默认值或函数返回的值:

    val presentOptional = Optional.of("FunnySaltyFish")
    
    println(presentOptional.getOrNull())
    // "FunnySaltyFish"
    
    val absentOptional = Optional.empty<String>()
    
    println(absentOptional.getOrNull()) // null
    println(absentOptional.getOrDefault("给个默认值")) // "给个默认值"
    println(absentOptional.getOrElse {
        println("Optional 值缺失")
        "默认值"
    })
    // "Optional 值缺失"
    // "默认值"
    
    

    扩展函数 toList()toSet()asSequence()将现有 Optional的值转换为列表、集合或序列,否则返回空集合。扩展函数 toCollection()将值追加到已存在的目标集合:

    val presentOptional = Optional.of("I'm here!")
    val absentOptional = Optional.empty<String>()
    println(presentOptional.toList() + "," + absentOptional.toList())
    // ["I'm here!"], []
    println(presentOptional.toSet() + "," + absentOptional.toSet())
    // ["I'm here!"], []
    val myCollection = mutableListOf<String>()
    absentOptional.toCollection(myCollection)
    println(myCollection)
    // []
    presentOptional.toCollection(myCollection)
    println(myCollection)
    // ["I'm here!"]
    val list = listOf(presentOptional, absentOptional).flatMap { it.asSequence() }
    println(list)
    // ["I'm here!"]
    
    

    这些扩展函数在 Kotlin 1.7.0 中作为实验性引入。您可以在此 KEEP 中了解有关Optional扩展的更多信息。与往常一样,我们欢迎您在 Kotlin 问题跟踪器中提供反馈。

    支持 JS 和本机中的命名捕获组

    从 Kotlin 1.7.0 开始,命名捕获组不仅在 JVM 上受支持,在 JS 和 Native 平台上也受支持。

    若要为捕获组命名,请在正则表达式中使用 (?<name>group) 语法。若要获取与组匹配的文本,请调用新引入的 MatchGroupCollection.get() 函数并传递组名。

    按名称检索匹配的组值

    fun dateReplace() {
        val dateRegex = Regex("(?<dd>\\d{2})-(?<mm>\\d{2})-(?<yyyy>\\d{4})")
        val input = "Date of birth: 27-04-2022"
        println(dateRegex.replace(input, "\${yyyy}-\${mm}-\${dd}")) // "Date of birth: 2022-04-27" — by name
        println(dateRegex.replace(input, "\$3-\$2-\$1")) // "Date of birth: 2022-04-27" — by number
    }
    
    

    命名反向引用

    现在,您还可以在反向引用组时使用组名。反向引用与捕获组先前匹配的相同文本匹配。为此,请使用正则表达式中的语法:\k<name>

    fun backRef() {
        val regex = "(?<title>\\w+), yes \\k<title>".toRegex()
        val match = regex.find("Do you copy? Sir, yes Sir!")!!
        println(match.value) // "Sir, yes Sir"
        println(match.groups["title"]?.value) // "Sir"
    }
    
    

    换表达式中的命名组

    命名组引用可与替换表达式一起使用。请考虑 replace() 函数,该函数将输入中指定正则表达式的所有匹配项替换为替换表达式;以及仅替换第一个匹配项的 replaceFirst() 函数。

    替换字符串中出现的 ${name} 将替换为与具有指定名称的捕获组相对应的子序列。您可以按名称和索引比较组引用中的替换项:

    fun dateReplace() {
        val dateRegex = Regex("(?<dd>\\d{2})-(?<mm>\\d{2})-(?<yyyy>\\d{4})")
        val input = "Date of birth: 27-04-2022"
        println(dateRegex.replace(input, "\${yyyy}-\${mm}-\${dd}")) // "Date of birth: 2022-04-27" — by name
        println(dateRegex.replace(input, "\$3-\$2-\$1")) // "Date of birth: 2022-04-27" — by number
    }
    
    

    Gradle

    好多,不翻了。见 kotlinlang.org/docs/whatsn…

    除上述之外,还有一些涉及到 JS/Native 的部分没有翻译,感兴趣的可自行参阅原链接。

    相关文章

      网友评论

          本文标题:Kotlin 1.7.0 正式发布!主要新特性一览

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