Kotlin1.4-M1發佈啦,終於支持Kotlin interface SAM轉換了!

0. 前言

終於,Kotlin 1.4的第一個預覽版發佈了,在新版本1.4-M1中,Kotlin又添加了一些新的功能,同時,也有一些重大的改進。本篇文章就帶大家一起看看新版Kotlin中有哪些我們期望添加和改進的功能。

1. 如何使用新版本?

如果使用在線編程,瀏覽器打開https://play.kotlinlang.org/,然後可以選擇Kotlin版本爲1.4-M1

如果使用的是Android Studio 或者IntelliJ IDE,你可以直接升級插件到最新版本1.4-M1,步驟如下:

  1. 選擇Tools -> Kotlin ->Configure Kotlin Plugin Updates.

  1. 在更新列表中選擇Early Access Preview X,選擇對應版本

  2. 點擊install 安裝重啓,就完成配置了。

2. 功能更強大的類型推薦算法

在Kotlin1.4中,使用了一個新的功能更加強大的類型推薦算法,或許你在Kotlin1.3中已經嘗試過這個算法了,在Kotlin1.3中,通過指定編譯器選項可以實現。但是現在默認就使用它了。關於新的算法一些詳細的信息,可以查看:https://youtrack.jetbrains.com/issues/KT?q=Tag:%20fixed-in-new-inference%20&_ga=2.58428450.988595807.1586745008-1408654980.1539842787

下面只介紹一些重要的改進。

2.1. Kotlin方法和接口的SAM轉換

終於等到你,Kotlin1.4中可以支持Kotlin interface SAM轉換了,這個真的太重要的了。

什麼是SAM轉換?可能有的同學還不太瞭解,這裏先科普一下:

SAM 轉換,即 Single Abstract Method Conversions,就是對於只有單個非默認抽象方法接口的轉換 —— 對於符合這個條件的接口(稱之爲 SAM Type ),在 Kotlin 中可以直接用 Lambda 來表示 —— 當然前提是 Lambda 的所表示函數類型能夠跟接口的中方法相匹配。

在Kotlin1.4之前,Kotlin是不支持Kotlin的SAM轉換的,可以支持Java SAM轉換,官方給出的的解釋是:是 Kotlin 本身已經有了函數類型和高階函數,不需要在去SAM轉化。 這個解釋開發者並不買賬,如果你用過Java Lambda和Fuction Interface。當你切換到Kotlin時,就會很懵逼。看來Kotlin是意識到了這個,或者是看到開發者的反饋,終於支持了。

Kotlin 的SAM轉換是什麼樣子呢?一起看一個對比

1.4之前:

1.4之後:

// 注意需用fun 關鍵字聲明
fun interface Action {
    fun run()
}

fun runAction(a: Action) = a.run()

fun main() {
    // 傳遞一個對象,OK
    runAction(object : Action{
        override fun run() {
            println("run action")
        }
    })
   // 1.4-M1支持SAM,OK
    runAction {
        println("Hello, Kotlin 1.4!")
    }
}

可以看到,在1.4之前,只能傳遞一個對象,是不支持Kotlin SAM的,而在1.4之後,可以支持Kotlin SAM,但是用法有一丟丟變化。interface需要使用fun關鍵字聲明。使用fun關鍵字標記接口後,只要將此類接口作爲參數,就可以將lambda作爲參數傳遞。

2.2. 更多場景的自動類型推斷

新的推理算法在許多情況下會推斷類型,在這些情況下,舊的推理需要顯示指定它們的類型。例如,在下面的示例中,會將lambda參數的類型正確推斷爲String?

val rulesMap: Map<String, (String?) -> Boolean> = mapOf(
    "weak" to { it != null },
    "medium" to { !it.isNullOrBlank() },
    "strong" to { it != null && "^[a-zA-Z0-9]+$".toRegex().matches(it) }
)

fun main() {
    println(rulesMap.getValue("weak")("abc!"))
    println(rulesMap.getValue("strong")("abc"))
    println(rulesMap.getValue("strong")("abc!"))
}

在1.3版本中,上面的代碼IDE是會報錯的,需要引入一個顯式的lambda參數,或將to替換爲具有顯式泛型參數的Pair構造函數以使其起作用。改爲像下面這樣:

//需要顯示的lambda 參數
val rulesMap: Map<String, (String?) -> Boolean> = mapOf(
    "weak" to { it -> it != null },
    "medium" to { it -> !it.isNullOrBlank() },
    "strong" to { it ->  it != null && "^[a-zA-Z0-9]+$".toRegex().matches(it) }
)

fun main() {
    println(rulesMap.getValue("weak")("abc!"))
    println(rulesMap.getValue("strong")("abc"))
    println(rulesMap.getValue("strong")("abc!"))
}

打印結果如下:

true
true
false

Process finished with exit code 0
2.3. Lambda內最後一個表達式的智能類型轉換

在Kotlin 1.3中,除非指定類型,否則lambda內的最後一個表達式不能智能強制轉換。因此,在以下示例中,Kotlin 1.3推斷String?作爲結果變量的類型:

val result = run {
    var str = currentValue()
    if (str == null) {
        str = "test"
    }
    str // Kotlin編譯器知道str在這裏不爲null
}
// result的類型在kotlin1.3中推斷爲String?,在Kotlin1.4中爲String

但在Kotlin 1.4中,由於使用了新的推理算法,lambda內部的最後一個表達式得到了智能轉換,並且此新的更精確的類型用於推斷所得的lambda類型。因此,結果變量的類型變爲String。而在Kotlin 1.3中,通常需要添加顯式強制轉換(!!或鍵入諸如String之類的強制轉換)以使這種情況起作用,現在這些強制轉換已不再需要了。

2.4. 可調用類型(Callable)引用的智能轉換

請看下面的示例代碼:

sealed class Animal
class Cat : Animal() {
    fun meow() {
        println("meow")
    }
}

class Dog : Animal() {
    fun woof() {
        println("woof")
    }
}

fun perform(animal: Animal) {
    val kFunction: KFunction<*> = when (animal) {
        is Cat -> animal::meow
        is Dog -> animal::woof
    }
    kFunction.call()
}

fun main() {
    perform(Cat())
}

在kotlin 1.3中,你無法訪問智能轉換類型引用的成員,但是現在可以了。
在將 Animal變量智能地強制轉換爲特定類型的CatDog之後,可以使用不同的成員引用animal :: meowanimal :: woof。在檢查類型之後,就可以訪問與子類型相對應的成員引用了。

2.5. 可調用(Callable)引用優化

比如下面這個列子:

fun foo(i: Int = 0): String = "$i!"

fun apply1(func: () -> String): String = func()
fun apply2(func: (Int) -> String): String = func(42)

fun main() {
    println(apply1(::foo))
    println(apply2(::foo))
}

在Kotlin 1.3中,foo函數解釋爲一個帶Int參數的函數,因此,apply1 會報類型錯誤。

而現在,使用具有默認參數值的函數的可調用引用得到優化,foo函數的可調用引用可以解釋爲採用一個Int參數不採用任何參數。因此就不會報上面的類型錯誤了。

2.6.委託屬性優化

先來看一段代碼:

fun main() {
    var prop: String? by Delegates.observable(null) { p, old, new ->
        println("$old$new")
    }
    prop = "abc"
    prop = "xyz"
}

以上代碼在Kotlin 1.3 上編譯不過,因爲在分析by後面的委託表達式時,不會考慮委託屬性的類型,因此會報類型錯誤。但是現在的kotlin 1.4-M1中,編譯器會正確推斷oldnew參數類型爲String?

3.標準庫更改

3.1. 廢棄試驗性的協程API

在1.3.0版中,我們不推薦使用kotlin.coroutines.experimental API,而推薦使用kotlin.coroutines。在1.4-M1中,我們將從標準庫中刪除kotlin.coroutines.experimental完成棄用。對於那些仍然在JVM上使用它的,我們提供了一個兼容庫: kotlin-coroutines-experimental-compat.jar來替換它。我們將其與Kotlin 1.4-M1一起發佈到了Bintray上。

3.2. 刪除已廢棄的mod操作符

另一個不建議使用的函數是數字類型的mod運算符,該運算符可計算除法運算後的餘數。在Kotlin 1.1中,它被rem()函數取代。現在,將其從標準庫中完全刪除。

3.3. 廢棄從浮點類型到Byte和Short的轉換

標準庫中包含了一些將浮點類型的轉換爲整數類型的方法,如:toInt(), toShort(), toByte()。但是由於數值範圍狹小且變量大小較小,將浮點數轉換爲Short和Byte可能會導致意外結果。爲了解決這個問題,在1.4-M1中,我們廢棄了DoubleFloat中的toShort()toByte()方法。如果你仍然想吧浮點類型轉化爲Short或者Byte,該怎麼辦呢?那也好辦,進行兩步轉換,先將浮點類型轉爲Int,然後再將Int轉爲目標類型就可以了。

3.4. 通用的發射API

我們修改了通用反射API。現在,它包含所有三個目標平臺(JVM,JS,Native)上可用的成員,因此現在可以確保相同的代碼可在其中任何一個平臺上上工作了。

3.5. 用於Kotlin反射的Proguard配置

從1.4-M1開始,我們在kotlin-reflect.jar中嵌入了Kotlin Reflection的Proguard / R8配置, 有了這個更改,大多數使用了R8或者Proguard的Android項目在不用其他任何配置的情況下使用kotlin-reflect。你不再需要複製粘貼Kotlin反射的Proguard規則。但是請注意,你仍然需要明確列出所有要考慮反射的API。

4. Kotlin/JVM

從1.3.70版開始,Kotlin能夠在JVM字節碼(目標版本1.8+)中生成類型註解,以便它們在運行時可用。社區要求此功能已有一段時間,因爲它使使用某些現有Java庫變得更加容易,併爲新庫的作者提供了更多的擴展能力。

在以下示例中,可以在字節碼中發出String類型的@Foo批註,然後由庫代碼使用:

@Target(AnnotationTarget.TYPE)
annotation class Foo

class A {
    fun foo(): @Foo String = "OK"
}

關於具體如何使用,可以看一下這篇博客:https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-3-70-released/#kotlin-jvm

5. 其他一些改動

除了上面的一些改動之外,對於Kotlin/Js和Kotlin/iOS 平臺也有一些優化和改進,大致列出來看一下:

5.1 Kotlin/JS
5.1.1. Gradle DSL 更改

kotlin.jsmultiplatformGradle插件中,引入了新的重要設置。在build.gradle.kts文件的目標塊內,如果您想在構建過程中生成.js工件,則可以配置並使用produceExecutable()

kotlin {
    target {
        useCommonJs()

        produceExecutable()
        
        browser {}
    }
}
  • 如果您正在編寫Kotlin / JS庫,則可以省略ProduceExecutable()配置。

  • 當使用新的IR編譯器後端(有關此內容的更多詳細信息,在下文中)時,省略此設置意味着將不會生成可執行的JS文件(因此,構建過程將運行得更快)。將在build / libs文件夾中生成一個klib文件,該文件可從其他Kotlin / JS項目使用,也可作爲同一項目中的依賴項。如果您未明確指定produceExecutable(),則默認情況下會發生這種情況。

使用produceExecutable()將生成可從JavaScript生態系統執行的代碼,無論其具有自己的入口點還是作爲JavaScript庫,這將生成實際的JavaScript文件,該文件可以在節點解釋器中運行,可以嵌入HTML頁面中並在瀏覽器中執行,或用作JavaScript項目的依賴項。

5.1.2. 新後端

Kotlin 1.4-M1是第一個包含針對Kotlin / JS目標的新IR編譯器後端的版本。此後端是極大改進的基礎,也是Kotlin / JS與JavaScript和TypeScript交互方式發生某些變化的決定性因素。以下突出顯示的幾個功能均針對新的IR編譯器後端。雖然默認情況下尚未啓用它,我們鼓勵你在項目中嘗試以下它。

(1)如何使用新的後端?

gradle.properties配置文件中添加以下配置:

kotlin.js.compiler=ir // or both

如果需要爲IR編譯器後端和默認後端生成庫,則可以選擇將此標誌設置爲both

關於both 的作用請看下面的章節介紹。

(2)無二進制兼容

新的IR編譯器後端與原來默認的後端相比主要的變換是沒有二進制兼容,Kotlin / JS的兩個後端之間缺乏這種兼容性,這意味着使用新的IR編譯器後端創建的庫無法從默認後端使用,反之亦然。

(3)DCE 優化

與默認後端相比,新的IR編譯器後端進行了很多優化。生成的代碼與靜態分析器配合使用效果更好了,甚至可以通過Google的Closure Compiler從新的IR編譯器後端運行生成的代碼,並使用其高級優化模式。

#####(4)支持聲明導出到JavaScript

現在,標記爲public的聲明不再自動導出,要使頂級聲明能在JavaScript或TypeScript中使用,請使用@JsExport註解。

package blogpost

@JsExport
class KotlinGreeter(private val who: String) {
    fun greet() = "Hello, $who!"
}

@JsExport
fun farewell(who: String) = "Bye, $who!"

fun secretGreeting(who: String) = "Sup, $who!" // only from Kotlin!
(5)支持TypeScript定義

新的編譯器支持從Kotlin代碼生成TypeScript定義,對於配置produceExecutable()配置項,並且使用了上面的@JsExport的頂級聲明,將生成帶有TypeScript定義的.d.ts文件。如上面的代碼,生成的文件如下所示:

// [...]
namespace blogpost {
    class KotlinGreeter {
        constructor(who: string)
        greet(): string
    }
    function farewell(who: string): string
}
// [...]
6. Kotlin/Native的一些變更
6.1. Objective-C默認支持泛型

Kotlin的早期版本爲Objective-C互操作中的泛型提供了實驗性支持。要從Kotlin代碼生成具有泛型的框架頭,必須使用-Xobjc-generics選項。在1.4-M1中,默認就支持範型了。但在某些情況下,這可能會破壞現有的調用Kotlin框架的Objective-C或Swift代碼。如果不想使用範型,請添加-Xno-objc-generics選項

binaries.framework {
     freeCompilerArgs += "-Xno-objc-generics"
}
6.2. Objective-C/Swift 互操作中異常處理變化

在1.4中,我們略微更改了從Kotlin生成Swift API異常處理的方式。Kotlin和Swift的錯誤處理存在根本的不同,所有Kotlin異常均未經檢查,而Swift僅檢查錯誤。因此,爲了使Swift代碼感知異常,需使用@Throws註解標記Kotlin函數,該註解指定潛在異常類的列表。

當編譯爲Swift或Objective-C框架時,具有或正在繼承@Throws註解的函數在Objective-C中表示爲NSError *處理方法,而在Swift中表示爲throws方法。

6.3. 性能提升

我們一直在努力提高Kotlin / Native編譯和執行的整體性能。1.4-M1中,我們爲提供了新的對象分配器,在某些基準測試中,它的運行速度提高了兩倍。當前,新的分配器是實驗性的,默認情況下不使用。您可以使用-Xallocator = mimalloc切換至該選項。

7.總結

以上就是Kotlin1.4-M1的一些變化,其中最令我驚喜的一個功能是:終於支持Kotlin interface SAM 轉換了。其他的一些功能大家都可以去試一下,更多更詳細的信息請去官網瞭解,期待早點出release版吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章