Kotlin 内联类
Kotlin 从 Kotlin 1.3 版本开始引入内联类,以克服传统包装器围绕某些类型的缺点。这些内联类将 Typealiases 的优点与原始数据类型的值范围相结合。
假设我们正在销售一些商品,并且成本定义为浮点类型。这在以下数据类中进行了描述
data class Items(val itemno: Int, val cost: float, val qty: Int)
如果我们支持美元和卢比两种货币,我们需要在另一个类中重构成本。
Java
data class Items(val itemno: Int, val cost: Cost, val qty: Int)
data class Cost(val value: Float, val currency: Currency)
enum class Currency {
RUPEE,
DOLLAR
}
Java
data class Item(val id: Int, val price: RupeePrice, val qty: Int)
inline class RupeePrice(val price: Float) {
inline fun toDollars(): Float = price * 71.62f
}
Java
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // method `greet` is called as a static method
println(name.length) // property getter is called as a static method
}
Java
interface Printable {
fun prettyPrint(): String
}
inline class Name(val s: String) : Printable {
override fun prettyPrint(): String = "Let's $s!"
}
fun main() {
val name = Name("Kotlin")
println(name.prettyPrint()) // Still called as a static method
}
Java
interface I
inline class Foo(val i: Int) : I
fun asInline(f: Foo) {}
fun asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun id(x: T): T = x
fun main() {
val f = Foo(42)
asInline(f) // unboxed: used as Foo itself
asGeneric(f) // boxed: used as generic type T
asInterface(f) // boxed: used as type I
asNullable(f) // boxed: used as Foo?, which is different from Foo
// below, 'f' first is boxed (while being passed to 'id') and then unboxed (when returned from 'id')
// In the end, 'c' contains unboxed representation (just '42'), as 'f'
val c = id(f)
}
Java
inline class UInt(val x: Int)
// Represented as 'public final void compute(int x)' on the JVM
fun compute(x: Int) { }
// Also represented as 'public final void compute(int x)' on the JVM!
fun compute(x: UInt) { }
Java
typealias NameTypeAlias = String
inline class NameInlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}
fun main() {
val nameAlias: NameTypeAlias = ""
val nameInlineClass: NameInlineClass = NameInlineClass("")
val string: String = ""
acceptString(nameAlias) // OK: pass alias instead of underlying type
acceptString(nameInlineClass) // Not OK: can't pass inline class instead of underlying type
// And vice versa:
acceptNameTypeAlias(string) // OK: pass underlying type instead of alias
acceptNameInlineClass(string) // Not OK: can't pass underlying type instead of inline class
}
上述方法有两个问题:
1.内存开销
2.复杂性
内联类克服了这两个问题
Java
data class Item(val id: Int, val price: RupeePrice, val qty: Int)
inline class RupeePrice(val price: Float) {
inline fun toDollars(): Float = price * 71.62f
}
内联类必须具有在主构造函数中初始化的单个属性。在运行时,内联类的实例将使用这个单一属性来表示:类的数据被“内联”到它的使用中(这就是名称“内联类”的原因)。
成员
它们在允许声明属性和函数的意义上类似于常规类。但是,它们也有一定的限制。内联类不能有初始化块,也不能有复杂的可计算属性,如后期初始化/委托属性。
Java
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // method `greet` is called as a static method
println(name.length) // property getter is called as a static method
}
遗产
这些类允许从接口继承,但不能扩展其他类,并且必须是最终类
Java
interface Printable {
fun prettyPrint(): String
}
inline class Name(val s: String) : Printable {
override fun prettyPrint(): String = "Let's $s!"
}
fun main() {
val name = Name("Kotlin")
println(name.prettyPrint()) // Still called as a static method
}
表示
内联类可以表示为包装器或底层类型。虽然后者是首选,但有时保留包装器很有用。当用作其他类型时,它们必然被装箱。引用相等是没有意义的,因为它既可以表示为基础值,也可以表示为包装器。
Java
interface I
inline class Foo(val i: Int) : I
fun asInline(f: Foo) {}
fun asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun id(x: T): T = x
fun main() {
val f = Foo(42)
asInline(f) // unboxed: used as Foo itself
asGeneric(f) // boxed: used as generic type T
asInterface(f) // boxed: used as type I
asNullable(f) // boxed: used as Foo?, which is different from Foo
// below, 'f' first is boxed (while being passed to 'id') and then unboxed (when returned from 'id')
// In the end, 'c' contains unboxed representation (just '42'), as 'f'
val c = id(f)
}
作为底层类型,这些内联类可能会导致诸如平台签名崩溃之类的模糊错误。
Java
inline class UInt(val x: Int)
// Represented as 'public final void compute(int x)' on the JVM
fun compute(x: Int) { }
// Also represented as 'public final void compute(int x)' on the JVM!
fun compute(x: UInt) { }
为了防止出现此类错误,我们使用了一个名为Mangling的过程,我们在函数名称中添加了一些哈希码。因此, fun compute(x: UInt)将表示为public final void compute-(int x) ,从而解决了问题。
内联类与类型别名
尽管两者可能看起来相似,但类型别名与底层类型的赋值兼容。内联类也引入了一个全新的类型,而类型别名为现有类型提供了一个替代名称
Java
typealias NameTypeAlias = String
inline class NameInlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}
fun main() {
val nameAlias: NameTypeAlias = ""
val nameInlineClass: NameInlineClass = NameInlineClass("")
val string: String = ""
acceptString(nameAlias) // OK: pass alias instead of underlying type
acceptString(nameInlineClass) // Not OK: can't pass inline class instead of underlying type
// And vice versa:
acceptNameTypeAlias(string) // OK: pass underlying type instead of alias
acceptNameInlineClass(string) // Not OK: can't pass underlying type instead of inline class
}
The design of inline classes is new and no compatibility guarantees are given.In Kotlin 1.3+, a warning will be reported, indicating that this feature is experimental.To remove this we have to opt in to the usage of this experimental feature by passing the compiler argument -Xinline-classes.