更多文章可以访问我的博客Aengus | Blog
Kotlin 1.4.30是Kotlin 1.4的最后一个版本,其中包含了Kotlin 1.5中的即将发布的特性,包括inline value classes
的稳定、JVMrecord
类的实验性支持以及sealed interface
的实验性支持,如果想要体验这些特性,需要特别声明版本:
compileKotlin {
kotlinOptions {
languageVersion = "1.5"
apiVersion = "1.5"
}
}
inline value classes的稳定
在Kotlin 1.3中inline class
已经是Alpha状态,而在1.4.30版本中变为了Beta状态。在Kotlin 1.5中将确定inline classes的概念并为了更一般的特性,将其变为value class
,我们将在下面提到。
inline class
允许有一个并且只有一个val
属性,编译器会自动将内联类替换为其属性,并且将使用内联类的函数的名称进行修改,如下:
inline class Color(val rgb: Int)
fun changeBackground(color: Color)
changeBackground(Color(255))
// 编译后
fun changeBackground-euwHqFQ(color: Int)
changeBackground-euwHqFQ(255)
修改函数名称的原因是防止由于JVM中类似方法的重载导致方法冲突。若在Java中使用Kotlin中定义的内联类,只能调用其空的构造函数,无法对内联类中包裹的属性进行赋值,但是可以定义接收内联类为参数的方法:
// Kotlin
inline class Color(val rgb: Int)
// Java
Color a = new Color();
a.getRgb() // OK
a.setRgb(1) // Error
类消除只有在将内联类传给普通方法的时候才会发生,当传给泛型方法或者将内联类存储在Collection
中时并不会立刻进行类消除,这有些类似Java中的装箱,只有在进行使用时才会进行拆箱,这些都是自动的。
对于Java调用修改JVM name
从1.4.30开始,可以给调用内联类的方法修改其Java调用时的名字,默认由编译器进行修改以防止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()
不会对Kotlin生效,因为Kotlin传入的类型是内联类。
初始化代码块
从1.4.30开始可以对内联类添加init
代码块了:
inline class Name(val s: String) {
init {
require(s.isNotEmpty())
}
}
注意:内联类的init
代码块只有调用构造方法的时候才会调用
Inline value classes
Kotlin 1.5为内联类带来更具体的概念并且引入了更多的特性,其语法也变为了value class
。
对于JVM来说,内联类是对只有一个参数的类的特别优化。value class
代表了更一般的概念并且会带来更多的优化:当前的内联类、Valhalla项目原始类。
由于内联类是value class
的一种优化,所以必须要用和以往不同的方式声明:
@JvmInline
value class Color(val rgb: Int)
原来的语法inline class
还可以继续使用一段时间,但是在1.5中使用会得到一个警告并且将来会被标记为错误。
Value classes
Value class代表了不可变的数据实体,现在(Kotlin 1.5)为了支持inline class
,value class
同样也只允许一个参数,但在之后的版本中将可以接收多个只读(val
)参数:
value class Point(val x: Int, val y: Int)
Value class完全用来存储数据,没有”标识符“:===
操作符不可以被调用,==
操作符会比较其中所有的属性;在Valhalla项目引入到JVM后,没有”标识符“这一特性将允许value class
通过JVM原始类型来实现。
上面的特性也是value class
不同于data class
的一些点。
对JVM record类的支持
Java 14中引入了record class
,其目的和Kotlin中的data class
类似,都是作为数据的简单存储。
Java record并不遵循JavaBean的规范,在JavaBean中的Getter方法为getX()
和getY()
,而record class
中则变为了x()
和y()
。现在Kotlin 1.4.30中也支持了这种语法,在Kotlin中调用record class
和JavaBean类似:
// Java
record Point(int x, int y) { }
// Kotlin
fun foo(point: Point) {
point.x // 属性调用
point.x() // 也可以
}
同样也可以通过@JvmRecord
注解将Kotlin中的data class
转为record class
来给Java调用,这样生成的Getter方法就变成x()
而不是getX()
。
@JvmRecord
data class Point(val x: Int, val y: Int)
需要注意的是@JvmRecord
注解只有用JVM 15+的版本去编译Kotlin代码时才能够使用。
密封接口及密封类提升
当声明一个类为sealed
时,将会限制其子类的继承结构,这将允许when
表达式的分支检查。在1.4中,密封类有两个限制:顶层类不能是密封接口;继承密封类的所有的直接子类都必须在同一个文件中。
Kotlin 1.5移除了这两个限制:可以将接口声明为sealed
,子类(包括密封类和密封接口)可以不在同一个文件中(但是需要和父类在相同的包下或者编译单元中)。
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
}
密封接口同样可以限制子类的继承结构,除此之外,另一个用法是禁止外部库实现或继承接口。
在未来使用JVM支持
sealed classes预览版支持已经被引入到Java 15中,在将来编译Kotlin sealed classes时会提供JVM的原生支持(可能是JVM 17或此特性稳定后)。在Java中,显式的列出密封类或接口的所有子类:
// Java
public sealed interface Expression
permits Const, Sum, NotANumber { ... }
这些信息将使用新的PermittedSubclasses
属性存储在类文件中,JVM会在运行时识别sealed classes并且阻止未授权的子类的扩展。
将来在使用最新的JVM编译Kotlin时,将启动新的sealed classes的JVM原生支持,编译器会在字节码中生成允许的子类列表来确保JVM支持以及格外的运行时检查:
// JVM 17+
Expr::class.java.permittedSubclasses // [Const, Sum, NotANunmber]
在Kotlin中并不用像Java一样声明所有继承的子类,编译器将会根据同包下的所有子类自动生成。
理论上在旧的JVM版本中也可以定义Kotlin密封接口的Java子类,但是并没有相关的限制,因为旧的JVM并没有相关功能。
网友评论