11月16日僧鲁,Kotlin 1.6 正式對外發(fā)布虐呻,這個版本中都有哪些新的語法特性?
- 更安全的when語句(exhaustive when statements)
- 掛起函數(shù)類型可作父類 (suspending functions as supertypes )
- 普通函數(shù)轉(zhuǎn)掛起函數(shù)(suspend conversion)
- Builder函數(shù)更加易用
- 遞歸泛型的類型推導(dǎo)
- 注解相關(guān)的一些優(yōu)化
1. 更安全的 when 語句
Kotlin 的 when 關(guān)鍵字允許我們在 case 分支中寫表達(dá)式或者語句寞秃。1.6 之前在 case 分支寫語句時存在安全隱患:
// 定義枚舉
enum class Mode { ON, OFF }
val x: Mode = Mode.ON
// when表達(dá)式
val result = when(x) {
Mode.ON -> 1 // case 中是一個表達(dá)式
Mode.OFF -> 2
}
// when語句
when(x) {
Mode.ON -> println("ON") // case 是一個語句
Mode.OFF -> println("OFF")
}
下表說明了編譯器針對 when 關(guān)鍵字的檢查內(nèi)容
x 的類型 | 枚舉斟叼、密封類/接口、Bool型等(可窮舉類型) | 不可窮舉類型 |
---|---|---|
when表達(dá)式 | case 必須窮舉所有分支蜕该,或者添加 else犁柜,否則編譯出錯 | Case 分支必須包含 else,否則編譯出錯 |
when語句 | case 可以不窮舉所有分支堂淡,不會報錯 | 同上 |
可見馋缅,當(dāng) x 是可窮舉類型時,編譯器對when表達(dá)式
的檢查比較嚴(yán)謹(jǐn)绢淀,如果 case
不能窮舉所有分支或者缺少 else
萤悴,編譯器會報錯如下:
ERROR: 'when' expression must be exhaustive, add necessary 'is TextMessage' branch or 'else' branch instead
但編譯器對于 when語句
的檢查卻不夠嚴(yán)謹(jǐn),即使沒有窮舉所有分支也不會報錯皆的,不利于開發(fā)者寫出安全的代碼:
// when語句
when(x) { // WARNING: [NON_EXHAUSTIVE_WHEN] 'when' expression on enum is recommended to be exhaustive, add 'OFF' branch or 'else' branch instead
Mode.ON -> println("ON") // case 是一個語句
}
Kotlin 1.6 起覆履,當(dāng)你在 When語句
中是可窮舉類型時必須處理所有分支,不能遺漏∠跞考慮到歷史代碼可能很多栖雾,為了更平穩(wěn)的過渡,1.6 對 when語句
中沒有窮舉的 case
會首先給出 Warning
伟众,從 1.7 開始 Warning 將變?yōu)?Error 要求開發(fā)者強(qiáng)制解決析藕。
2. 掛起函數(shù)類型可作父類
Kotlin 中一個函數(shù)類型可以作為父類被繼承。
class MyFun<T>(var param: P): () -> Result<T> {
override fun invoke(): Result<T> {
// 基于成員 param 自定義邏輯
}
}
fun <T> handle(handler: () -> Result<T>) {
//...
}
Kotlin 代碼中大量使用各種函數(shù)類型凳厢,許多方法都以函數(shù)類型作為參數(shù)账胧。當(dāng)你需要調(diào)用這些方法時,需要傳入一個函數(shù)類型的實例先紫。而當(dāng)你想在實例中封裝一些可復(fù)用的邏輯時治泥,可以使用函數(shù)類型作為父類創(chuàng)建子類。
但是這種做法目前不適用于掛起函數(shù)遮精,你無法繼承一個 suspend
函數(shù)類型的父類
class C : suspend () -> Unit { // Error: Suspend function type is not allowed as supertypes
}
C().startCoroutine(completion = object : Continuation<Unit> {
override val context: CoroutineContext
get() = TODO("Not yet implemented")
override fun resumeWith(result: Result<Unit>) {
TODO("Not yet implemented")
}
})
但是以掛起函數(shù)作為參數(shù)或者 recevier 的方法還挺多的居夹,所以 Kotlin 1.5.30 在 Preveiw 中引入了此 feature,這次 1.6 將其 Stable本冲。
class MyClickAction : suspend () -> Unit {
override suspend fun invoke() { TODO() }
}
fun launchOnClick(action: suspend () -> Unit) {}
如上吮播,你可以現(xiàn)在可以像這樣調(diào)用了 launchOnClick(MyClickAction())
。
需要注意普通函數(shù)類型作為父類是可以多繼承的
class MyClickAction : () -> Unit, (View) -> Unit {
override fun invoke() {
TODO("Not yet implemented")
}
override fun invoke(p1: View) {
TODO("Not yet implemented")
}
}
但是目前掛起函數(shù)作為父類不支持多繼承眼俊,父類列表中意狠,既不能出現(xiàn)多個 suspend 函數(shù)類型,也不能有普通函數(shù)類型和suspend函數(shù)類型共存疮胖。
3. 普通函數(shù)轉(zhuǎn)掛起函數(shù)
這個 feature 也是與函數(shù)類型有關(guān)环戈。
Kotlin 中為一個普通函數(shù)添加 suspend 是無害的,雖然編譯器會提示你沒必要這么做澎灸。當(dāng)一個函數(shù)簽名有一個 suspend 函數(shù)類型參數(shù)院塞,但是也允許你傳入一個普通函數(shù),在某些場景下是非常方便的性昭。
//combine 的 transform 參數(shù)是一個 suspend 函數(shù)
public fun <T1, T2, R> combine(
flow: Flow<T1>, flow2: Flow<T2>,
transform: suspend (a: T1, b: T2) -> R): Flow<R>
= flow.combine(flow2, transform)
suspend fun before4_1() {
combine(
flowA, flowB
) { a, b ->
a to b
}.collect { (a: Int, b: Int) ->
println("$a and $b")
}
}
如上述代碼所示拦止,flow
的 combine
方法其參數(shù) transform
類型是一個 suspend
函數(shù),我們希望再次完成一個 Pair
的創(chuàng)建糜颠。這個簡單的邏輯本無需使用 suspend
汹族,但在 1.4 之前只能像上面這樣寫。
Kotlin 1.4 開始其兴,普通函數(shù)的引用可以作為 suspend
函數(shù)傳參顶瞒,所以 1.4 之后可以改成下面的寫法,代碼更簡潔:
suspend fun from1_4() {
combine(
flowA, flowB, ::Pair
).collect { (a: Int, b: Int) ->
println("$a and $b")
}
}
1.4 之后仍然有一些場景中元旬,普通函數(shù)不能直接轉(zhuǎn)換為 suspend 函數(shù)使用
fun getSuspending(suspending: suspend () -> Unit) {}
fun suspending() {}
fun test(regular: () -> Unit) {
getSuspending { } // OK
getSuspending(::suspending) // OK from 1.4
getSuspending(regular) // NG before 1.6
}
比如上面 getSuspending(regular)
會報錯如下:
ERROR:The feature "suspend conversion" is disabled
Kotlin 1.6 起榴徐,所有場景的普通函數(shù)類型都可以自動轉(zhuǎn)換為 suspend 函數(shù)傳參使用守问,不會再看到上述錯誤。
4. Builder 函數(shù)更加易用
我們在構(gòu)建集合時會使用一些 Builder函數(shù)
坑资,比如 buildList
耗帕,buildMap
之類。
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun <E> buildList(@BuilderInference builderAction: MutableList<E>.() -> Unit): List<E> {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return buildListInternal(builderAction)
}
@kotlin.ExperimentalStdlibApi
val list = buildList<String> {
add("a")
add("b")
}
buildList
的實現(xiàn)中使用 @BuilderInterface
注解了 builderAction
這個 lambda 袱贮。這樣可以在調(diào)用時 buildList
通過 builderAction
內(nèi)部的方法調(diào)用智能推導(dǎo)出泛型參數(shù)的類型兴垦,從而減少模板代碼
//<String> 可省略
val list = buildList {
add("a")
add("b")
}
//<String> 不可省略
val list = buildList<String> {
add("a")
add("b")
val x = get(1)
}
但是 BuilderInterface
的類型推導(dǎo)限制比較多,比如 lambda 中調(diào)用的方法的簽名要求比較嚴(yán)格字柠,必須參數(shù)是泛型且返回值沒有泛型,破壞了規(guī)則狡赐,類型推導(dǎo)失敗了窑业。所以上面代碼中 lambda 有 get()
調(diào)用時,就必須清楚的標(biāo)記泛型類型枕屉。這使得集合類的 builder 函數(shù)使用起來不那么靈活常柄。
Kotlin 1.6 起 BuilderInterface 沒有了類似限制,對我們來說最直觀好處就是 Builder 函數(shù)內(nèi)怎樣的調(diào)用都不會受限制搀擂,使用更加自由
val list = buildList {
add("a")
add("b")
set(1, null) //OK
val x = get(1) //OK
if (x != null) {
removeAt(1) //OK
}
}
val map = buildMap {
put("a", 1) //OK
put("b", 1.1) //OK
put("c", 2f) //OK
}
此 feature 在 1.5.30 也可以通過 添加 -Xunrestricted-builder-inference
編譯器選項生效西潘,1.6 已經(jīng)是默認(rèn)生效了。
5. 遞歸泛型的類型推導(dǎo)
這個 feature 我們平常需求比較少哨颂。
Java 或者 Kotlin 中我們可以像下面這樣定義有遞歸關(guān)系的泛型喷市,即泛型的上限是它本身
public class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
//...
}
這種情況下的類型推導(dǎo)比較困難,Kotlin 1.5.30 開始可以只基于泛型的上線進(jìn)行類型推導(dǎo)威恼。
// Before 1.5.30
val containerA = PostgreSQLContainer<Nothing>(DockerImageName.parse("postgres:13-alpine")).apply {
withDatabaseName("db")
withUsername("user")
withPassword("password")
withInitScript("sql/schema.sql")
}
// With compiler option in 1.5.30 or by default starting with 1.6.0
val containerB = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine"))
.withDatabaseName("db")
.withUsername("user")
.withPassword("password")
.withInitScript("sql/schema.sql")
1.5.30 支持此 feature 需要添加 -Xself-upper-bound-inference
編譯選項品姓, 1.6 開始默認(rèn)支持。
6. 注解相關(guān)的一些優(yōu)化
Kotlin 1.6 中對注解進(jìn)行了諸多優(yōu)化箫措,在編譯器注解處理過程中將發(fā)揮作用
支持注解的實例化
annotation class InfoMarker(val info: String)
fun processInfo(marker: InfoMarker) = ...
fun main(args: Array<String>) {
if (args.size != 0)
processInfo(getAnnotationReflective(args))
else
processInfo(InfoMarker("default"))
}
Java 的注解本質(zhì)是實現(xiàn)了 Annotation
的接口腹备,可以被繼承使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaClassAnno {
String[] value();
}
public interface JavaClassAnno extends Annotation{
//...
}
class MyAnnotation implements JavaClassAnno { // <--- works in Java
//...
}
但是在 Kotlin 中無法繼承使用,這導(dǎo)致有一些接受注解類的 API 在 Kotlin 側(cè)無法調(diào)用斤蔓。
class MyAnnotationLiteral : JavaClassAnno { // <--- doesn't work in Kotlin (annotation can not be inherited)
//...
}
注解類可以實例化之后植酥,可以調(diào)用接收注解類參數(shù)的 API,能夠與 Java 代碼進(jìn)行更好地兼容
泛型參數(shù)可添加注解
@Target(AnnotationTarget.TYPE_PARAMETER)
annotation class BoxContent
class Box<@BoxContent T> {}
Kotlin 1.6 之后可以為泛型參數(shù)添加注解弦牡,這將為 KAPT / KSP 等注解處理器中提供方便友驮。
可重復(fù)的運行時注解
Jdk 1.8 引入了 @java.lang.annotation.Repetable
元注解,允許同一個注解被添加多次驾锰。 Kotlin 也相應(yīng)地引入了 @kotlin.annotation.Repeatable
喊儡,不過 1.6之前只能注解 @Retention(RetentionPolicy.SOURCE)
的注解,當(dāng)非 SOURCE 的注解出現(xiàn)多次時稻据,會報錯
ERROR: [NON_SOURCE_REPEATED_ANNOTATION] Repeatable annotations with non-SOURCE retention are not yet supported
此外艾猜,Kotlin 側(cè)代碼也不能使用 Java 的 @Repeatable
注解來注解多次买喧。
Kotlin1.6 開始,取消了只能用在 SOURCE 類注解的限制匆赃,任何類型的注解都可以出現(xiàn)多次淤毛,而且 Kotlin 側(cè)支持使用 Java 的 @Repeatable
注解
@Repeatable(AttributeList.class)
@Target({ElementType.TYPE})
@Retentioin(RetentionPolicy.RUNTIME) //雖然是 RUNTIME 注解
annotation class Attribute(val name: String)
@Attribute("attr1") //OK
@Attribute("attr2") //OK
class MyClass {}
最后
上述介紹的是 Kotlin1.6 在語法方面的一些新特性,大部分在 1.5.30 中作為 preview 功能已經(jīng)出現(xiàn)過算柳,這次在 1.6 中進(jìn)行了轉(zhuǎn)正低淡。除了新的語法特性,1.6 在各平臺 Compiler 上有諸多新內(nèi)容瞬项,我們在平日開發(fā)中接觸不到本文就不介紹了蔗蹋。
更多內(nèi)容參考:kotlinlang.org/docs/whatsn…
【2021最新版】Kotlin語言教程——Kotlin入門到精通全系列_嗶哩嗶哩_bilibili
本文轉(zhuǎn)自 https://juejin.cn/post/7031722045075374088,如有侵權(quán)囱淋,請聯(lián)系刪除猪杭。