更多文章可以訪問我的博客Aengus | Blog
Kotlin 1.4.30是Kotlin 1.4的最后一個(gè)版本,其中包含了Kotlin 1.5中的即將發(fā)布的特性,包括inline value classes的穩(wěn)定、JVMrecord類的實(shí)驗(yàn)性支持以及sealed interface的實(shí)驗(yàn)性支持,如果想要體驗(yàn)這些特性,需要特別聲明版本:
compileKotlin {
kotlinOptions {
languageVersion = "1.5"
apiVersion = "1.5"
}
}
inline value classes的穩(wěn)定
在Kotlin 1.3中inline class已經(jīng)是Alpha狀態(tài),而在1.4.30版本中變?yōu)榱薆eta狀態(tài)。在Kotlin 1.5中將確定inline classes的概念并為了更一般的特性,將其變?yōu)?code>value class,我們將在下面提到。
inline class允許有一個(gè)并且只有一個(gè)val屬性,編譯器會(huì)自動(dòng)將內(nèi)聯(lián)類替換為其屬性,并且將使用內(nèi)聯(lián)類的函數(shù)的名稱進(jìn)行修改,如下:
inline class Color(val rgb: Int)
fun changeBackground(color: Color)
changeBackground(Color(255))
// 編譯后
fun changeBackground-euwHqFQ(color: Int)
changeBackground-euwHqFQ(255)
修改函數(shù)名稱的原因是防止由于JVM中類似方法的重載導(dǎo)致方法沖突。若在Java中使用Kotlin中定義的內(nèi)聯(lián)類,只能調(diào)用其空的構(gòu)造函數(shù),無(wú)法對(duì)內(nèi)聯(lián)類中包裹的屬性進(jìn)行賦值,但是可以定義接收內(nèi)聯(lián)類為參數(shù)的方法:
// Kotlin
inline class Color(val rgb: Int)
// Java
Color a = new Color();
a.getRgb() // OK
a.setRgb(1) // Error
類消除只有在將內(nèi)聯(lián)類傳給普通方法的時(shí)候才會(huì)發(fā)生,當(dāng)傳給泛型方法或者將內(nèi)聯(lián)類存儲(chǔ)在Collection中時(shí)并不會(huì)立刻進(jìn)行類消除,這有些類似Java中的裝箱,只有在進(jìn)行使用時(shí)才會(huì)進(jìn)行拆箱,這些都是自動(dòng)的。
對(duì)于Java調(diào)用修改JVM name
從1.4.30開始,可以給調(diào)用內(nèi)聯(lián)類的方法修改其Java調(diào)用時(shí)的名字,默認(rèn)由編譯器進(jìn)行修改以防止Java重載沖突,用法如下:
// Kotlin declarations
inline class Timeout(val millis: Long)
val Int.millis get() = Timeout(this.toLong())
val Int.seconds get() = Timeout(this * 1000L)
@JvmName("greetAfterTimeoutMillis")
fun greetAfterTimeout(timeout: Timeout)
// Kotlin usage
greetAfterTimeout(2.seconds)
// Java usage
greetAfterTimeoutMillis(2000);
@JvmName()不會(huì)對(duì)Kotlin生效,因?yàn)镵otlin傳入的類型是內(nèi)聯(lián)類。
初始化代碼塊
從1.4.30開始可以對(duì)內(nèi)聯(lián)類添加init代碼塊了:
inline class Name(val s: String) {
init {
require(s.isNotEmpty())
}
}
注意:內(nèi)聯(lián)類的init代碼塊只有調(diào)用構(gòu)造方法的時(shí)候才會(huì)調(diào)用
Inline value classes
Kotlin 1.5為內(nèi)聯(lián)類帶來更具體的概念并且引入了更多的特性,其語(yǔ)法也變?yōu)榱?code>value class。
對(duì)于JVM來說,內(nèi)聯(lián)類是對(duì)只有一個(gè)參數(shù)的類的特別優(yōu)化。value class代表了更一般的概念并且會(huì)帶來更多的優(yōu)化:當(dāng)前的內(nèi)聯(lián)類、Valhalla項(xiàng)目原始類。
由于內(nèi)聯(lián)類是value class的一種優(yōu)化,所以必須要用和以往不同的方式聲明:
@JvmInline
value class Color(val rgb: Int)
原來的語(yǔ)法inline class還可以繼續(xù)使用一段時(shí)間,但是在1.5中使用會(huì)得到一個(gè)警告并且將來會(huì)被標(biāo)記為錯(cuò)誤。
Value classes
Value class代表了不可變的數(shù)據(jù)實(shí)體,現(xiàn)在(Kotlin 1.5)為了支持inline class,value class同樣也只允許一個(gè)參數(shù),但在之后的版本中將可以接收多個(gè)只讀(val)參數(shù):
value class Point(val x: Int, val y: Int)
Value class完全用來存儲(chǔ)數(shù)據(jù),沒有”標(biāo)識(shí)符“:===操作符不可以被調(diào)用,==操作符會(huì)比較其中所有的屬性;在Valhalla項(xiàng)目引入到JVM后,沒有”標(biāo)識(shí)符“這一特性將允許value class通過JVM原始類型來實(shí)現(xiàn)。
上面的特性也是value class不同于data class的一些點(diǎn)。
對(duì)JVM record類的支持
Java 14中引入了record class,其目的和Kotlin中的data class類似,都是作為數(shù)據(jù)的簡(jiǎn)單存儲(chǔ)。
Java record并不遵循JavaBean的規(guī)范,在JavaBean中的Getter方法為getX()和getY(),而record class中則變?yōu)榱?code>x()和y()。現(xiàn)在Kotlin 1.4.30中也支持了這種語(yǔ)法,在Kotlin中調(diào)用record class和JavaBean類似:
// Java
record Point(int x, int y) { }
// Kotlin
fun foo(point: Point) {
point.x // 屬性調(diào)用
point.x() // 也可以
}
同樣也可以通過@JvmRecord注解將Kotlin中的data class轉(zhuǎn)為record class來給Java調(diào)用,這樣生成的Getter方法就變成x()而不是getX()。
@JvmRecord
data class Point(val x: Int, val y: Int)
需要注意的是@JvmRecord注解只有用JVM 15+的版本去編譯Kotlin代碼時(shí)才能夠使用。
密封接口及密封類提升
當(dāng)聲明一個(gè)類為sealed時(shí),將會(huì)限制其子類的繼承結(jié)構(gòu),這將允許when表達(dá)式的分支檢查。在1.4中,密封類有兩個(gè)限制:頂層類不能是密封接口;繼承密封類的所有的直接子類都必須在同一個(gè)文件中。
Kotlin 1.5移除了這兩個(gè)限制:可以將接口聲明為sealed,子類(包括密封類和密封接口)可以不在同一個(gè)文件中(但是需要和父類在相同的包下或者編譯單元中)。
sealed interface Expr
data class Const(val number: Double) : Expr
data class Sum(val e1: Expr, val e2: Expr) : Expr
object NotANumber : Expr
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
密封接口同樣可以限制子類的繼承結(jié)構(gòu),除此之外,另一個(gè)用法是禁止外部庫(kù)實(shí)現(xiàn)或繼承接口。
在未來使用JVM支持
sealed classes預(yù)覽版支持已經(jīng)被引入到Java 15中,在將來編譯Kotlin sealed classes時(shí)會(huì)提供JVM的原生支持(可能是JVM 17或此特性穩(wěn)定后)。在Java中,顯式的列出密封類或接口的所有子類:
// Java
public sealed interface Expression
permits Const, Sum, NotANumber { ... }
這些信息將使用新的PermittedSubclasses屬性存儲(chǔ)在類文件中,JVM會(huì)在運(yùn)行時(shí)識(shí)別sealed classes并且阻止未授權(quán)的子類的擴(kuò)展。
將來在使用最新的JVM編譯Kotlin時(shí),將啟動(dòng)新的sealed classes的JVM原生支持,編譯器會(huì)在字節(jié)碼中生成允許的子類列表來確保JVM支持以及格外的運(yùn)行時(shí)檢查:
// JVM 17+
Expr::class.java.permittedSubclasses // [Const, Sum, NotANunmber]
在Kotlin中并不用像Java一樣聲明所有繼承的子類,編譯器將會(huì)根據(jù)同包下的所有子類自動(dòng)生成。
理論上在舊的JVM版本中也可以定義Kotlin密封接口的Java子類,但是并沒有相關(guān)的限制,因?yàn)榕f的JVM并沒有相關(guān)功能。