Kotlin 2.2.0 现已发布!又有哪些万众瞩目的特性横空出世?
大家吼!就在昨天,Kotlin v2.2.0 发布了!撒花撒花*★,°:.☆( ̄▽ ̄)/$:.°★* 。 更新的内容也已经在官网上更新:What's new in Kotlin 2.2.0 。 那么接下来,就让我来看看哪些是我最喜欢的新特性吧~!
注意!这里主要阐述的是一些我认为不错的或感兴趣的标准库和语言特性中的变化,如果你想了解完整而全面的更新内容,可以去官网看看喔~
语言特性
context parameters
首先来看看语言特性,上来第一个就是大家期待已久的新特性:context parameters
!我记得它最早的时候是 context receivers
,
随后然后在大概 2.1.x 的时候调整为了现在的 context parameters
,也就是上下文参数。
(写编译器插件的时候总是能看到这东西变来变去的)
我们来看看官方给的示例:
// UserService defines the dependency required in the context
interface UserService {
fun log(message: String)
fun findUserById(id: Int): String
}
// Declares a function with a context parameter
context(users: UserService)
fun outputMessage(message: String) {
// Uses log from the context
users.log("Log: $message")
}
// Declares a property with a context parameter
context(users: UserService)
val firstUser: String
// Uses findUserById from the context
get() = users.findUserById(1)
当然,也支持使用无效的占位名称:
// Uses "_" as context parameter name
context(_: UserService)
fun logWelcome() {
// Finds the appropriate log function from UserService
outputMessage("Welcome!")
}
这个特性可以大大增加对于特定作用域进行扩展的灵活性和便捷性。举个典型的例子,在 Jetpack Compose 或者 Compose Multiplatform 中, 原本的很多特定作用域函数都是通过带有 receiver 的函数和接口实现完成的,比如这个:
@LayoutScopeMarker
@Immutable
@JvmDefaultWithCompatibility
interface RowScope {
@Stable
fun Modifier.weight(
weight: Float,
fill: Boolean = true
): Modifier
// ...
}
这是 compose 中的 RowScope
中的一部分,可以看到它约束只有在 Row { }
的作用域范围之内才可以使用 Modifier.weigth
。
在此 之前,这类对作用域有要求的函数总是通过接口的 receiver function 实现的。
而现在,只需要通过 context parameters
就可以轻松地对某些具体的作用域进行扩展了!
当然,这里只是举一个'限制作用域'的例子,至于到底是内部实现还是通过
context parameters
则需要根据实际需求和情况来具体问题具体分析。
目前这个特性还在实验阶段,只要添加编译器参数 -Xcontext-parameters
就可以启用这个特性了,快来试试吧!
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xcontext-parameters")
}
}
context-sensitive resolution
老实说,这是一个之前没怎么注意过的特性。也许之前的确没提过?总而言之,这也是一个用来简化代码、提高使用体验的一个特性,简单的翻译一下, 这个特性或许可以被称为 上下文敏感的解析。
怎么一回事儿呢?来看看官方的示例:
enum class Problem {
CONNECTION, AUTHENTICATION, DATABASE, UNKNOWN
}
fun message(problem: Problem): String = when (problem) {
Problem.CONNECTION -> "connection"
Problem.AUTHENTICATION -> "authentication"
Problem.DATABASE -> "database"
Problem.UNKNOWN -> "unknown"
}
假设原本有上面这么一段代码,在 context-sensitive resolution
的加持下,它则可以在已知的作用于下为你省略掉一些不必要的类型引用:
enum class Problem {
CONNECTION, AUTHENTICATION, DATABASE, UNKNOWN
}
// Resolves enum entries based on the known type of problem
fun message(problem: Problem): String = when (problem) {
CONNECTION -> "connection"
AUTHENTICATION -> "authentication"
DATABASE -> "database"
UNKNOWN -> "unknown"
}
按照描述,这个特性可以在下述的这些场景中使用:
when
的子域(也就是上面的示例情况)- 显式返回类型
- 声明的变量类 型
- 类型检测 (
is
) 和类型转化 (as
) sealed class
的已知类型结构- 参数的声明类型
不过老实说,它就只是这么列举出来,一时间还真没法一下子反应过来所有场景下的应用案例。不过无所谓,只从上面的代码示例来看也能看出来它的主要作用。
目前这个特性还在实验阶段,需要添加编译器参数 -Xcontext-sensitive-resolution
启用,感兴趣的朋友可以试试喔!
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xcontext-sensitive-resolution")
}
}
annotation use-site targets
这也是一个意料之外的特性,注解的作用位置。不知道各位还记不记得以前在一个属性上标记注解,想要标记在字段、getter或参数等位置上需要怎么写?
data class User(
val username: String,
@param:Email // Constructor parameter
@field:Email // Backing field
@get:Email // Getter method
@property:Email // Kotlin property reference
val email: String,
) {
@field:Email
@get:Email
@property:Email
val secondaryEmail: String? = null
}
而现在这个特性就在某些特定场景下简化了这种繁琐操作:使用 @all
。
data class User(
val username: String,
// Applies @Email to param, property, field,
// get, and set_param (if var)
@all:Email val email: String,
) {
// Applies @Email to property, field, and getter
// (no param since it's not in the constructor)
@all:Email val secondaryEmail: String? = null
}
使用 @all
之后便会根据实际情况,为所有可能的位置都添加上指定注解了。至于具体的逻辑与限制,感兴趣的小伙伴可以去官网详细阅读喔。
目前这个特性还在实验阶段,需要添加编译器参数 -Xannotation-target-all
启用!
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xannotation-target-all")
}
}
defaulting rules for use-site annotation targets
和上一个一样,这也是一个为 use-site annotation target
也就是注解作用位置相关的特性,它可以用来指定注解默认情况下生效的位置。
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xannotation-default-target=param-property")
}
}
或
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xannotation-default-target=first-only")
}
}
Support for nested type aliases
嗯~!如标题所述,这个新特性支持了在一个嵌套的结构层次中使用 alias
创建别名类型了。
讲道理,之前倒是真没怎么尝试过这种用法,所以不知道以前不行。
class Dijkstra {
typealias VisitedNodes = Set<Node>
private fun step(visited: VisitedNodes, ...) = ...
}
当然,这个特性也有一些限制存在,感兴趣的朋友可以前往官方详细阅读。
同样的,这是一个实验性特性,通过添加编译器参数 -Xnested-type-aliases
启用。
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xnested-type-aliases")
}
}
稳定特性
接下来,文档中列举了几个当前版本转为稳定的特性:
when
子域的守卫条件。 也就是在when
的条件中使用 if 编写更多灵活的条件啦,比如:sealed interface Animal {
data class Cat(val mouseHunter: Boolean) : Animal {
fun feedCat() {}
}
data class Dog(val breed: String) : Animal {
fun feedDog() {}
}
}
fun feedAnimal(animal: Animal) {
when (animal) {
// Branch with only the primary condition. Calls `feedDog()` when `animal` is `Dog`
is Animal.Dog -> animal.feedDog()
// Branch with both primary and guard conditions. Calls `feedCat()` when `animal` is `Cat` and is not `mouseHunter`
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
// Prints "Unknown animal" if none of the above conditions match
else -> println("Unknown animal")
}
}- 非本地的
break
和continue
。 换句话说,就是在一个没有纯粹的inline
DSL 中也可以使用break
和continue
。 - 双
$
字符串插值 它们真的很喜欢美元符号。
Kotlin/JVM
跳过了一些关于编译器的更新,本来这部分非标准库的 Kotlin/JVM 部分也想跳过的,但是我似乎看到了一些我感兴趣的东西。
接口函数更改使用 default
这个其实没什么好说的,早期版本 Kotlin/JVM 实现接口中的函数并不是直接使用 Java 的 default function
特性的,随着版本的不断更迭,
这个行为也在逐步变化,现在编译器参数 -Xjvm-default
被废弃并变成了默认行为。
支持在 Kotlin metadata 中读写注解
这个其实也没什么特别需要说的,Kotlin metadata JVM library
这个库我也的确没用过,大概意思就是更新了这个库的内容,现在可以通过它来读写注解相关的内容了。
嗯...嗯?🤔
结合之前 Kotlin 和 Spring 两个团队宣布的合作的事情来看,也许这是在为更高效的反射库做准备?