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 К2 compiler 发布 Alpha 版本, 带来显著的性能提高。 目前仅有 JVM 可用,其余编译器插件,包括
kapt
均无法使用Gradle 中增量编译的新方法。现在,在依赖的非 Kotlin 模块内所做的更改也支持增量编译,并且与 Gradle 兼容。
稳定的opt-in 注解要求、明确非空类型 和 Builder 推理。
用于类型参数的下划线运算符。在指定其他类型时,可以使用它来自动推断参数类型。
内联类的内联值可以委托。现在,您可以创建在大多数情况下不分配内存的轻量级wrapper。
新的 Kotlin K2 编译器
此 Kotlin 版本引入了新的 Kotlin K2 编译器的 Alpha 版本。新的编译器旨在加快新语言功能的开发,统一Kotlin支持的所有平台,带来性能改进,并为编译器扩展提供API。
我们已经发布了一些关于我们的新编译器及其优点的详细说明:
- The Road to the New Kotlin Compiler
- K2 Compiler: a Top-Down View
需要强调的是,对于新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 {
abstract fun execute(): T
}
class SomeImplementation : SomeClass() {
override fun execute(): String = "Test"
}
class OtherImplementation : SomeClass() {
override fun execute(): Int = 42
}
object Runner {
inline fun , T> run(): T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}
fun main() {
// T 被推断为 String,因为 SomeImplementation 继承自 SomeClass
val s = Runner.run()
assert(s == "Test")
// T 被推断为 Int ,因为 OtherImplementation 继承自 SomeClass
val n = Runner.run()
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 type
fun takeMyLong(x: Long) { ... }
fun String.isMoreThat3() = length > 3
fun takeListOfStrings(x: List) { ... }
fun main() {
val result1 = buildList {
val x = get(0)
takeMyLong(x)
} // result1 has the List type
val result2 = buildList {
val x = get(0)
val isLong = x.isMoreThat3()
// ...
} // result2 has the List type
val result3 = buildList {
takeListOfStrings(this)
} // result3 has the List 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 elvisLike(x: T, y: T & Any): T & Any = x ?: y
fun main() {
// OK
elvisLike("", "").length
// 错误: 'null' 无法用于 non-null 值
elvisLike("", null).length
// OK
elvisLike(null, "").length
// 错误: 'null' 无法用于 non-null 值
elvisLike(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()
println(numbers.maxOrNull()) // "null"
println(numbers.max()) // "Exception in… Collection is empty."
}
正则表达式特定位置匹配
在 1.5.30 中引入的 Regex.matchAt()
和 Regex.matchesAt()
函数现已达到稳定版本。 它们提供了一种方式来检查正则表达式在 String
或 CharSequence
中的特定位置是否具有精确匹配。
-
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(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 { 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.Monotonic
、markNow()
、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()
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()
println(presentOptional.toList() + "," + absentOptional.toList())
// ["I'm here!"], []
println(presentOptional.toSet() + "," + absentOptional.toSet())
// ["I'm here!"], []
val myCollection = mutableListOf()
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 平台上也受支持。
若要为捕获组命名,请在正则表达式中使用 (?
) 语法。若要获取与组匹配的文本,请调用新引入的 MatchGroupCollection.get()
函数并传递组名。
按名称检索匹配的组值
fun dateReplace() {
val dateRegex = Regex("(?\\d{2})-(?\\d{2})-(?\\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
fun backRef() {
val regex = "(?\\w+), yes \\k".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("(?\\d{2})-(?\\d{2})-(?\\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 的部分没有翻译,感兴趣的可自行参阅原链接。