1. 概述
在 Kotlin 中,有时我们需要将某个值封装成一个类,以便提供更清晰的抽象,例如封装一个计数器。但这种封装通常会带来额外的运行时开销,因为包装类会占用堆内存。
如果被封装的是原始类型(primitive type),这种性能损耗会更加明显,因为 JVM 对原始类型有专门优化,而包装类则没有。
为了解决这个问题,Kotlin 提供了 inline class(内联类)机制。它本质上是一种基于值而非身份的类,不具有独立的身份标识,仅用于存储数据。
2. 什么是 Inline Class?
Inline class 的最大优势在于:相比普通包装类,它在运行时几乎不产生额外开销。这是因为编译器会将它的值直接“内联”到使用的地方,避免了对象的创建。
下面是一个简单的 inline class 示例,封装了一个表示圆半径的 Double
值:
@JvmInline
value class Circle(private val radius: Double) {
val circumference: Double
get() = radius * 2.0 * 3.14
}
使用方式如下:
val circle = Circle(5.0)
val circumference = circle.circumference
我们可以通过反编译 Kotlin 生成的 Java 字节码来验证其运行时行为。使用 CFR 反编译器 后,可以看到如下 Java 代码:
public final class Circle {
private final double radius;
public static double constructor_impl(double radius) {
return radius;
}
public static final double getCircumference_impl(double arg0) {
return arg0 * 2.0 * 3.14;
}
}
调用时的 Java 代码如下:
double circle = Circle.constructor_impl(5.0);
double circumference = Circle.getCircumference_impl(circle);
可以看到,Circle
类并没有被实例化,而是通过静态方法返回一个 double
值。这说明 inline class 在运行时被“内联”为原始类型,避免了对象创建的开销。
2.1 使用规范
一个 inline class 必须满足以下条件:
- 必须使用
value
关键字声明 - 必须加上
@JvmInline
注解(适用于 JVM 平台) - 主构造函数中只能有一个属性(即只能封装一个值)
正确示例如下:
@JvmInline
value class InlineDoubleWrapper(val value: Double)
使用方式如下:
@Test
fun whenInclineClassIsUsed_ThenPropertyIsReadCorrectly() {
val pi = InlineDoubleWrapper(3.14)
assertEquals(3.14, pi.value)
}
3. 类成员定义
Inline class 不只是简单的封装,它也可以像普通类一样拥有属性、方法和初始化块。
例如,我们可以为 Circle
添加直径属性和面积计算方法,并加入验证逻辑:
@JvmInline
value class Circle(private val radius: Double) {
val diameterOfCircle: Double
get() = 2.0 * radius
fun areaOfCircle(): Double = 3.14 * radius * radius
init {
require(radius > 0) { "Circle radius must be positive" }
}
}
测试直径属性:
@Test
fun givenRadius_ThenDiameterIsCorrectlyCalculated() {
val circle = Circle(5.0)
assertEquals(10.0, circle.diameterOfCircle)
}
测试面积方法:
@Test
fun givenRadius_ThenAreaIsCorrectlyCalculated() {
val circle = Circle(5.0)
assertEquals(78.5, circle.areaOfCircle())
}
还可以测试初始化块的验证逻辑:
@Test
fun givenNegativeRadius_ThenThrowsIllegalArgumentException() {
assertThrows<IllegalArgumentException> {
Circle(-5.0)
}
}
⚠️ 注意:inline class 有以下限制:
- ✅ 支持属性、方法、初始化块
- ❌ 不支持内部类、
backing field
、继承其他类
4. 继承支持
Inline class 可以实现接口,但不能继承其他类,也不能被继承。它本质上是 final 的。
4.1 接口继承示例
定义一个 Drawable
接口,并在 inline class 中实现:
interface Drawable {
fun draw()
}
@JvmInline
value class Circle(private val radius: Double) : Drawable {
val diameterOfCircle get() = 2 * radius
fun areaOfCircle() = 3.14 * radius * radius
init {
require(radius > 0) { "Circle radius must be a positive number" }
}
override fun draw() {
println("Draw my circle")
}
}
4.2 继承注意事项
虽然 inline class 支持接口继承,但要注意:只有在静态类型为 inline class 时,编译器才会进行内联优化。
例如下面这些方法:
fun useAsDrawable(drawableInline: Drawable) { }
fun useAsNullableDrawable(drawableNullableInline: Drawable?) { }
fun <T> useAsGeneric(genericInline: T) { }
如果以接口类型传入 inline class 实例,JVM 会进行装箱操作,失去内联优化带来的性能优势。所以:
✅ 推荐:
fun useAsCircle(circle: Circle) { }
❌ 不推荐:
fun useAsDrawable(drawable: Drawable) { }
5. 总结
Kotlin 的 inline class 是一种轻量级的封装机制,能够在不牺牲类型安全的前提下,避免普通包装类带来的性能开销。
✅ 主要优势:
- 避免堆内存分配
- 提高运行时性能
- 支持方法、属性、接口继承
❌ 注意事项:
- 不支持多属性封装
- 继承接口时可能失去内联特性
- 不能使用
inner class
、backing field
如果你希望在封装原始类型的同时保持高性能,inline class 是非常值得使用的工具。
完整示例代码请参考:GitHub 仓库地址