有时,将一个值包装在一个类中可以创建一个更具领域特定类型的类。然而,由于额外的堆分配,这会引入运行时开销。此外,如果包装的类型是原始类型,性能损失是显著的,因为原始类型通常由运行时进行了大量优化,而它们的包装类没有得到任何特殊处理。
为了解决这些问题,kotlin
引入了一种特殊类型的类,称为内联类(inline class)。内联类是值类(value-based classes)的一个子集。它们没有身份,只能持有值。
要声明一个内联类,请在类名之前使用value
修饰符
value class Password(private val s: String)
要在JVM
后端声明一个内联类,可以在类声明之前使用value
修饰符以及@JvmInline
注解
// For JVM backends
@JvmInline
value class Password(private val s: String)
内联类的主构造函数必须具有一个初始化的单个属性。在运行时,内联类的实例将使用该单个属性来表示
// 实际上不会对Password类进行实例化
// 在运行时,securePassword只包含String
val securePassword = Password("Don't try this in production")
这就是内联类的主要特性,它启发了名称"inline":类的数据被内联到其使用的地方(类似于内联函数的内容被内联到调用站点)
成员
内联类支持常规类的某些功能。特别是,它们可以声明属性和函数,具有初始化块和辅助构造函数
@JvmInline
value class Person(private val fullName: String) {init {require(fullName.isNotEmpty()) {"Full name shouldn't be empty"}}constructor(firstName: String, lastName: String) : this("$firstName $lastName") {require(lastName.isNotBlank()) {"Last name shouldn't be empty"}}val length: Intget() = fullName.lengthfun greet() {println("Hello, $fullName")}
}fun main() {val name1 = Person("Kotlin", "Mascot")val name2 = Person("Kodee")name1.greet() // the `greet()` function is called as a static methodprintln(name2.length) // property getter is called as a static method
}
内联类的属性不能有
backing fields
。它们只能具有简单的可计算属性(不支持lateinit
或委托属性)
继承
内联类可以实现接口
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())
}
内联类不能继承其它类,也不能被其它类继承
表示方式(Representation)
在生成的代码中,kotlin
编译器会为每个内联类保留一个包装器。在运行时,内联类实例可以表示为包装器或基础类型。这类似于Int
可以表示为原始的int
类型或包装器Integer
编译器倾向于使用基础类型而不是包装器来生成性能最佳和经过优化的代码。然而,有时候需要保留包装器。一般而言,当内联类用作另一种类型时,它们会被装箱(boxed)
interface I@JvmInline
value class Foo(val i: Int) : Ifun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}fun <T> id(x: T): T = xfun main() {val f = Foo(42)asInline(f) // 不装箱: 使用Foo自己asGeneric(f) // 装箱: 使用泛型 TasInterface(f) // 装箱: 使用 IasNullable(f) // 装箱:使用 Foo?, 这和Foo不同// 调用id()方法时。首先装箱成T类型,返回时拆箱成Foo// 最后,c是未装箱的状态,和f一样val c = id(f)
}
由于内联类可以同时表示为基础值和包装器,因此,引用相等性(==)是无意义的,是被禁止的。
内联类还可以具有泛型类型参数作为基础类型。在这种情况下,编译器将其映射为Any?
或为类型参数的上界。
@JvmInline
value class UserId<T>(val value: T)fun compute(s: UserId<String>) {} // compiler generates fun compute-<hashcode>(s: Any?)
编译器会优化
compute
函数的签名,将其转换为fun compute-<hashcode>(s: Any?)
,其中<hashcode>
是一个哈希码,用于确保函数名称的唯一性。这意味着编译器将UserId<String>
类型的参数转换为Any?
类型的参数。
名称修饰(Mangling)
由于内联类被编译为其基础类型,这可能导致各种晦涩的错误,例如意外的平台签名冲突
@JvmInline
value class UInt(val x: Int)// 在jvm上会表示为`public final void compute(int x)`
fun compute(x: Int) { }// 在jvm上也会表示为`public final void compute(int x)`
fun compute(x: UInt) { }
为了避免这些问题,使用内联类的函数会通过添加的哈希码来进行名称修饰。因此,fun compute(x: UInt)
将被表示为 public final void compute-<hashcode>(int x)
从Java代码调用
接受内联类参数的函数时,可以手动禁用名称修饰。为此,需要在函数声明之前添加@JvmName
注解
@JvmInline
value class UInt(val x: Int)fun compute(x: Int) { }@JvmName("computeUInt")
fun compute(x: UInt) { }
内联类VS类行别名
两者似乎都引入了一个新的类型,并且在运行时都会表示为其基础类型
然而,关键的区别在于,类型别名可以与其基础类型(以及具有相同基础类型的其他类型别名)进行赋值兼容,而内联类则不行。
换句话说,内联类引入了一个真正的新类型,而类型别名只是为现有类型引入了一个替代名称(别名)
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) // 传递别名而不是底层类型acceptString(nameInlineClass) // 报错 -- 参数类型不匹配// And vice versa:acceptNameTypeAlias(string) // 传递基础类型而不是别名acceptNameInlineClass(string) // 报错 -- 参数类型不匹配
}
内联类和委托
委托后边讲解
使用内联类的内联值进行委托实现是允许的,可以通过接口实现
interface MyInterface {fun bar()fun foo() = "foo"
}@JvmInline
value class MyInterfaceWrapper(val myInterface: MyInterface) : MyInterface by myInterfacefun main() {val my = MyInterfaceWrapper(object : MyInterface {override fun bar() {// body}})println(my.foo()) // prints "foo"
}
MyInterfaceWrapper
类型实例可以使用委托的MyInterface
实现的方法,包括默认实现的foo()
方法。在这种方式下,通过委托实现可以将内联类的功能扩展到具体的接口实现上