本文是对发布在 Medium 上的文章 👨🏼💻 All About Kotlin Inline(Value) Class 的翻译。
请注意,虽然本翻译按照
CC BY-SA 4.0
分发,但没有原作者的授权,我本质上不享有翻译权。所以如果想转载本文,你首先应该取得原作者的同意。
大家好✋。在这篇文章中,我将细致入微地解释 Kotlin 内联类(Inline Class)的细节。
什么是内联类❓
译注:内联类(Inline Class)也叫值类(Value Class),Inline 是仅在 JVM 平台上的做法,实际上在 Kotlin/Native 平台,可以通过 struct 来实现值类
在某些情况下,我们创建 Wrapper 提供特定的业务逻辑。但这可能导致性能损失,因为 Wrapper 需要在运行时创建额外的对象。如果你封装的是原始类型,对性能的打击是毁灭性的。为了解决这种情况。Kotlin 引入了一种名为内联类(Inline Class)的新类。
使用内联类,不仅提供编译时类型安全,而且在运行时充当原始类型,节省了不必要的性能损失。它还提供了更清晰、更易于理解、更可读的代码结构。
性能损失在哪里? 🤔
当创建原始变量时,它存储在 JVM 栈内存中。但是创建一个新对象时,它存储在堆内存中。在存储和使用对象时,操作将更加昂贵。可以查看链接(译注:原文的外部链接失效了)以了解堆栈的更多信息。此处的损失微不足道。但如果在项目视野考量,后者明显会产生额外开销。此外,作为程序员,不应该忘记我们需要更干净、更清晰、更高效地编写代码。定义原始类型一定比定义对象更高效。
因此,若在为原始类型编写包装类时使用 Inline 类。我们即可受益于包装类的好处,例如更灵活、可读性和类型安全,也不会损失性能。
额外地,上图显示了做同一件事的 4 种方法,以及速度。
用例和限制
// Inline Class Definition
@JvmInline
class Distance(val valueAsMeter: Int) value
- value 关键字足以定义一个内联类。
- 如果编译目标包括 JVM,还需要添加
@JvmInline
注解,否则无法通过编译。
@JvmInline
class Distance(val valueAsMeter: Int, val otherValue: String) // ❌
value
@JvmInlineclass Distance1() // ❌
value
@JvmInlineclass Distance2(val valueAsMeter: Int) {
value constructor() // ❌
}
@JvmInline
(var valueAsMeter: Int) // ❌ value clas Distance3
- 只能提供主构造器。
- 和 data class 类似,需要定义构造器参数
- 可以在主构造器中定义有且仅有一个只读属性 (
var
不行,只能val
)
@JvmInline
class Distance1(val valueAsMeter: Int): Measure() // ❌
value
class Measure: Distance1() // ❌
interface Calculate {
fun calculate()
}
// 编译通过JvmInline
@value class Distance2(val valueAsMeter: Int) : Calculate {
override fun calculate() {
("")
TODO}
}
- 内联类不能继承或被继承
- 内联类可以实现接口
Mangling
由于原文比较难翻译,这里解释下,Mangling 可以理解为去语法糖,或者真正发生的事情。
上方图片中有两个函数。一个采用值类参数,另一个采用 int
类型的参数。可以看到反编译后的代码有一点不同。值类首先被编译为原始类型。这可能会导致某些错误。就比如这里的两个函数,如果不做任何处理,会导致编译错误,因为类型签名和函数名一样。为了避免这些错误,在编译时,内联类的函数名被打乱为
getPrice<hashCode>
。
如果想从 Java 中调用第一个函数,我们需要禁用 Mangling
行为。可以通过添加 @JvmName
注解来做到这一点。我们给出的
name
参数将是编译后的函数名。可以用该名称从 Java
中调用。
类型别名(typealias) vs 内联类
内联类与类型别名非常相似,但也存在差异。尽管两者似乎都创建了新类型,但类型别名仅仅替换类型的名称。他们的使用场景是不同的,例如,我们可以考虑为 String 类型分配一个不同的名称。在使用内联类时,我们创建了一个新类型。 这为我们提供了更清晰、更易理解的代码结构,以及类型安全。
typealias NameTypeAlias = String
@JvmInline
class NameInlineClass(val s: String)
value
fun accpetString(s: String) {}
fun accpetNameTypeAlias(s: NameTypeAlias) {}
fun accpetNameInlineClass(s: NameInlineClass) {}
fun main() {
val nameAlias: NameTypeAlias = ""
val nameInlineClass: NameInlineClass = NameInlineClass("")
val string = ""
(nameAlias) // OK
acceptString(nameInlineClass) // ❌
acceptString(nameInlineClass.s) // OK
acceptString
// 反之亦然
(string) // OK
acceptNameTypeAlias(string) // ❌
acceptNameInlineClass}
结论
在本文中,我们尝试了解有关内联类的每一个细节。内联类的使用提供了一些好处,可以更清晰易读地编写代码,并在性能和类型安全方面更具优势。下一篇文章见👋👋。