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 classbacking field

如果你希望在封装原始类型的同时保持高性能,inline class 是非常值得使用的工具。

完整示例代码请参考:GitHub 仓库地址


原始标题:Inline Classes in Kotlin