Kotlin 1.6 正式發(fā)布食铐,帶來哪些新特性?

11月16日僧鲁,Kotlin 1.6 正式對外發(fā)布虐呻,這個版本中都有哪些新的語法特性?

  1. 更安全的when語句(exhaustive when statements)
  2. 掛起函數(shù)類型可作父類 (suspending functions as supertypes )
  3. 普通函數(shù)轉(zhuǎn)掛起函數(shù)(suspend conversion)
  4. Builder函數(shù)更加易用
  5. 遞歸泛型的類型推導(dǎo)
  6. 注解相關(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")
    }
}

如上述代碼所示拦止,flowcombine 方法其參數(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)系刪除猪杭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市妥衣,隨后出現(xiàn)的幾起案子皂吮,更是在濱河造成了極大的恐慌,老刑警劉巖税手,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜂筹,死亡現(xiàn)場離奇詭異,居然都是意外死亡芦倒,警方通過查閱死者的電腦和手機(jī)艺挪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兵扬,“玉大人闺属,你說我怎么就攤上這事≈苊梗” “怎么了掂器?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俱箱。 經(jīng)常有香客問我国瓮,道長,這世上最難降的妖魔是什么狞谱? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任乃摹,我火速辦了婚禮,結(jié)果婚禮上跟衅,老公的妹妹穿的比我還像新娘孵睬。我一直安慰自己,他們只是感情好伶跷,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般树姨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烁试,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機(jī)與錄音拢肆,去河邊找鬼减响。 笑死,一個胖子當(dāng)著我的面吹牛郭怪,可吹牛的內(nèi)容都是我干的支示。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼鄙才,長吁一口氣:“原來是場噩夢啊……” “哼颂鸿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咒循,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绞愚,沒想到半個月后叙甸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡位衩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年裆蒸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糖驴。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡僚祷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贮缕,到底是詐尸還是另有隱情辙谜,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布感昼,位于F島的核電站装哆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏定嗓。R本人自食惡果不足惜蜕琴,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宵溅。 院中可真熱鬧凌简,春花似錦、人聲如沸恃逻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至畔派,卻和暖如春铅碍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背线椰。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工胞谈, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人憨愉。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓烦绳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親配紫。 傳聞我的和親對象是個殘疾皇子径密,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容