Kotlin笔记(内联类)
内联类
有时,业务逻辑需要围绕某种类型创建包装器。但是,由于额外的堆分配,它会引入运行时开销。此外,如果包装类型是原始类型,则性能损失很糟糕,因为原始类型通常由运行时进行大量优化,而它们的包装器没有得到任何特殊处理。为了解决这些问题,Kotlin 引入了一种特殊的类,称为 内联类 。要声明内联类,请在类名前使用value修饰符( inline内联类的修饰符已弃用): value class Password(private val s: String)
要为JVM后端声明内联类,请在类声明之前使用value修饰符和@JvmInline注释: // For JVM backends @JvmInline value class Password(private val s: String)
内联类必须具有在主构造函数中初始化的单个属性。在运行时,内联类的实例将使用这个单一属性表示(请参阅 下面有关运行时表示的详细信息): // No actual instantiation of class "Password" happens // At runtime "securePassword" contains just "String" val securePassword = Password("Don"t try this in production")
这是内联类的主要特征:类的数据被 内联 到使用它的地方。 内联类的成员
内联类支持常规类的一些功能。内联类中可以声明属性和函数,并具有以下init块: @JvmInline value class Name(val s: String) { init { require(s.length > 0) { } } 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 }
内联类的属性不能拥有 backing fields, 内联类只能拥有简单的计算属性 (不能拥有延迟初始化属性或委托属性)。 内联类成的继承interface Printable { fun prettyPrint(): String } @JvmInline value 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 }内联类只允许继承接口 内联类不能继承其他类 内联类是 final 类, 不能被其他类继承 内联类的表示
在生成的代码中,Kotlin 编译器为每个内联类保留一个 包装器。 内联类实例可以在运行时表示为包装器或底层类型。 类似于Int可以表达为基本类型int, 也可以表达为包装类Integer。 Kotlin编译器更喜欢使用底层类型而不是包装器来生成最高性能和优化的代码。但是,有时需要保留包装器。根据经验,内联类在用作另一种类型时都会被装箱。 interface I @JvmInline value 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) }
因为内联类既可以表示为基础值,也可以表示为包装器,因此 引用相等对它们没有意义,因此被禁止。 内联类中的函数名称混淆
由于内联类被编译为它们的底层类型,它可能会导致各种模糊的错误,例如意外的平台签名冲突: @JvmInline value 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) { }
为了解决这种问题,使用内联类的函数会通过在函数名称中添加一些稳定的哈希码来 破坏 。因此,fun compute(x: UInt)将表示为public final void compute-(int x),从而解决了冲突问题。 从 Java 代码调用
您可以从Java代码调用接受内联类的函数。为此,您应该手动禁用混淆:在函数声明之前添加注解@JvmName: @JvmInline value class UInt(val x: Int) fun compute(x: Int) { } @JvmName("computeUInt") fun compute(x: UInt) { }内联类与类型别名
乍一看,内联类似乎与类型别名非常相似。实际上,两者似乎都引入了一种新类型,并且两者都将在运行时表示为底层类型。然而,关键的区别在于类型别名与其底层类型(以及具有相同底层类型的其他类型别名) 赋值兼容,而内联类则不然。 换句话说,内联类引入了一种真正的 新 类型,与类型别名相反,类型别名只为现有类型引入替代名称(别名): typealias NameTypeAlias = String @JvmInline value 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 }