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,步驟如下:
- 選擇Tools -> Kotlin ->Configure Kotlin Plugin Updates.
-
在更新列表中選擇
Early Access Preview X
,選擇對應版本
-
點擊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
變量智能地強制轉換爲特定類型的Cat
和Dog
之後,可以使用不同的成員引用animal :: meow
和animal :: 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中,編譯器會正確推斷old
和new
參數類型爲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中,我們廢棄了Double
和Float
中的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.js
和multiplatform
Gradle插件中,引入了新的重要設置。在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版吧!