前言
嘿,各位Kotlin爱好者们!就在最近,Kotlin v2.2.0像一颗超级新星般闪耀发布啦!撒花庆祝🎉🎉🎉 更新内容已经在官网闪亮登场:What's new in Kotlin 2.2.0 。接下来,就让我化身“特性探险家”,带大家瞧瞧那些让我眼前一亮的新特性吧!
不过先提醒一下哈,这里主要聚焦一些我觉得超棒或者超有趣的标准库和语言特性变化。要是你想来个全面大揭秘,官网的大门随时为你敞开哟~
语言特性大揭秘
context parameters:作用域扩展的“魔法钥匙”
先来看看语言特性里的“重头戏”——context parameters!它可是从context receivers“进化”而来的,在2.1.x版本的时候就悄悄变身为现在的模样啦。(写编译器插件的时候,感觉它就像个调皮的小精灵,老是变来变去😜)
来看看官方给的示例代码:
// UserService 定义了上下文所需的依赖
interface UserService {
fun log(message: String)
fun findUserById(id: Int): String
}
// 声明一个带有上下文参数的函数
context(users: UserService)
fun outputMessage(message: String) {
// 从上下文中使用 log
users.log("Log: $message")
}
// 声明一个带有上下文参数的属性
context(users: UserService)
val firstUser: String
// 从上下文中使用 findUserById
get() = users.findUserById(1)
它还支持用无效的占位名称呢:
// 用 "_" 作为上下文参数名
context(_: UserService)
fun logWelcome() {
// 从 UserService 中找到合适的 log 函数
outputMessage("Welcome!")
}
这个特性就像是给特定作用域的扩展装上了“超级引擎”,让扩展变得更加灵活和便捷。就拿Jetpack Compose或者Compose Multiplatform来说,以前很多特定作用域函数都是靠带有receiver的函数和接口来实现的。比如Compose里的RowScope:
@LayoutScopeMarker
@Immutable
@JvmDefaultWithCompatibility
interface RowScope {
@Stable
fun Modifier.weight(
weight: Float,
fill: Boolean = true
): Modifier
// ...
}
可以看到,它限制了只有在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
}
// 根据已知的 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 // 构造参数
@field:Email // 后端字段
@get:Email // Getter方法
@property:Email // Kotlin属性引用
val email: String,
) {
@field:Email
@get:Email
@property:Email
val secondaryEmail: String? = null
}
现在好了,新特性在特定场景下简化了这个操作,用上了@all:
data class User(
val username: String,
// 将@Email应用到param、property、field、
// get和set_param(如果是var)
@all:Email val email: String,
) {
// 将@Email应用到property、field和getter
// (没有param,因为它不在构造器中)
@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) {
// 只有主要条件的分支。当animal是Dog时调用feedDog()
is Animal.Dog -> animal.feedDog()
// 有主要条件和守卫条件的分支。当animal是Cat且不是mouseHunter时调用feedCat()
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
// 如果以上条件都不匹配,打印"Unknown animal"
else -> println("Unknown animal")
}
}
非本地的break和continue
也就是说,在一个没有纯粹inline DSL的地方也能用break和continue啦,是不是很方便!
双$字符串插值
看来Kotlin团队真的很喜欢美元符号😜
Kotlin/JVM的惊喜
本来想跳过一些编译器更新和非标准库的Kotlin/JVM部分,不过我发现了一些超有趣的东西。
接口函数更改使用default
早期版本的Kotlin/JVM实现接口中的函数可不是直接用Java的default function特性的,随着版本不断更新,这个行为也在慢慢变化。现在,编译器参数 -Xjvm-default 被废弃,变成了默认行为。
支持在Kotlin metadata中读写注解
这个其实也没啥特别要说的,就是更新了Kotlin metadata JVM library库的内容,现在可以通过它来读写注解相关的东西啦。
不过结合之前Kotlin和Spring团队宣布合作的事情,难道这是在为更高效的反射库做准备?🤔
改进Java互操作,支持内联值类
这个特性可太让我兴奋啦!它改进了Java对@JvmInline value class的互操作性,还新增了一个实验性注解@JvmExposeBoxed,能让value class在Java中更好用。
看看官方例子:
@JvmInline
value class MyInt(val value: Int)
fun MyInt.timesTwoBoxed(): MyInt = MyInt(this.value * 2)
以前,函数timesTwoBoxed()的receiver是个value class,会被编译成Java不可访问的函数形式。现在,加上注解:
@JvmExposeBoxed
@JvmInline
value class MyInt(val value: Int)
@JvmExposeBoxed
fun MyInt.timesTwoBoxed(): MyInt = MyInt(this.value * 2)
再回到Java中调用:
MyInt input = new MyInt(5);
MyInt output = ExampleKt.timesTwoBoxed(input);
哇塞,这代码简直太丝滑了!回想起以前写库的时候,为了把value class相关的函数巧妙转化或暴露,那叫一个头疼,现在这个问题轻松解决啦!当然,如果不想每个地方都加注解,也可以加上编译器参数 -Xjvm-expose-boxed 来标记整个模块。而且这种变化只对外的Java符号生效,Kotlin模块内部还是保持高效内联,完全不用担心性能问题。
改进JVM records的注解支持
这和之前提到的注解可以用@all的内容有点关系。在Java中,注解的Target有RECORD_COMPONENT选项,而Kotlin没有。以前想让Kotlin的注解支持RECORD_COMPONENT可麻烦了。
现在,用@all在一个@JvmRecord的属性上,就能在支持RECORD_COMPONENT的前提下附加注解啦。
@JvmRecord
data class Person(val name: String, @all:Positive val age: Int)
Kotlin/Native、Kotlin/Wasm和Kotlin/JS的动态
Kotlin/Native
native相关的内容,主要还是在内存管理、性能优化等方面努力,还顺便废弃了Window 7 target。要是你感兴趣,就去官方文档看看吧~
Kotlin/Wasm & Kotlin/JS
希望Kotlin/Wasm和Kotlin/JS能快速发展呀,我以后转全栈可就靠它们啦😆 这两个端的更新不是很多,感兴趣的小伙伴可以去官方文档简单了解一下。
标准库的稳定更新
跳过中间的一些内容,比如Gradle的更新,来看看标准库。标准库主要稳定了两个多平台API:
- Base64的编码/解码
- HexFormat相关的hex API
这些都是很常用的东西,稳定了可太好了!
Compose compiler的小变化
看来Compose并入Kotlin编译器之后,每次更新都得带着它啦。我对这方面不太擅长,就不多说啦,有需要的小伙伴自己去官网看看吧。
尾声
总的来说,2.2.0版本的更新可真不少,有不少我觉得超有用的东西,尤其是context parameters这个实验性特性的亮相,还有Gradle中Binary compatibility validation的整合。
你有什么看法呢?欢迎留言讨论哟~ 😎