前置知识: Kotlin

Kotlin内联类

00:00
4 min Intermediate 2026/6/14

value class与内联优化

概述

内联类(Inline Class),在 Kotlin 1.5 之后称为 value class,是一种特殊的类,它在编译时会被”内联”为其包装的底层类型,运行时没有任何额外开销。简单来说,value class 让你在编写代码时享受类型安全的好处,而在运行时不会产生额外的对象分配。

为什么需要它?假设你有一个用 String 表示的用户 ID 和一个用 String 表示的邮箱地址,它们都是 String 类型,但语义完全不同。如果不小心把邮箱传给了需要用户 ID 的函数,编译器不会报错,但逻辑上是错误的。value class 可以让编译器帮你检查这类错误。

基础概念

  • value class:用 @JvmInline value class 标记的类,只能有一个只读属性(底层值)
  • 内联:编译器会在使用处将 value class 替换为其底层值,避免对象分配
  • 类型安全:在编译期区分不同的 value class,防止混用
  • 运行时开销:在大数场景下,value class 不会创建额外的对象

快速上手

// 定义 value class
@JvmInline
value class UserId(val value: String)

@JvmInline
value class Email(val value: String)

// 现在编译器会阻止你把 Email 传给需要 UserId 的函数
fun findUser(id: UserId) {
    println("查找用户: ${id.value}")
}

fun main() {
    val userId = UserId("123")
    val email = Email("alice@example.com")

    findUser(userId)     // 正确
    // findUser(email)   // 编译错误:类型不匹配

    // 底层值通过 .value 访问
    println(userId.value)  // "123"
}

详细用法

基本规则

value class 有几个必须遵守的规则

@JvmInline
value class Seconds(val value: Int) {
    // 可以定义方法
    fun toMinutes(): Double = value / 60.0

    // 可以定义属性(不能有 backing field)
    val isPositive: Boolean
        get() = value > 0
}

fun main() {
    val duration = Seconds(120)
    println(duration.toMinutes())  // 2.0
    println(duration.isPositive)   // true
}

规则总结:

  • 只能有一个主构造器参数(底层
  • 底层必须是只读属性(val)
  • 不能有 init
  • 不能有 backing field 的属性
  • 不能继承其他,也不能被继承
  • 可以实现接口

实现接口

interface Printable {
    fun formatted(): String
}

@JvmInline
value class Name(val value: String) : Printable {
    override fun formatted(): String = "[$value]"
}

fun printIt(item: Printable) {
    println(item.formatted())
}

fun main() {
    val name = Name("Alice")
    println(name.formatted())  // [Alice]

    // 当 value class 被当作接口使用时,会发生装箱
    printIt(name)  // 这里 name 会被装箱为对象
}

与数据类对比

// 数据类:运行时创建对象,有额外开销
data class UserIdData(val value: String)

// value class:运行时内联为 String,无额外开销
@JvmInline
value class UserIdValue(val value: String)

fun main() {
    // 数据类:每次创建都是新对象
    val data1 = UserIdData("123")
    val data2 = UserIdData("123")
    println(data1 == data2)  // true(比较内容)

    // value class:比较底层值
    val value1 = UserIdValue("123")
    val value2 = UserIdValue("123")
    println(value1 == value2)  // true(比较底层值)
}

无符号整数的实现

Kotlin 的无符号整数类型(UInt、ULong 等)本身就是用 value class 实现的:

// Kotlin 标准库中 UInt 的简化实现
@JvmInline
value class UInt(val data: Int) : Comparable<UInt> {
    override fun compareTo(other: UInt): Int =
        compareValues(data.toLong() and 0xFFFFFFFFL, other.data.toLong() and 0xFFFFFFFFL)
}

在集合中使用

@JvmInline
value class ProductId(val value: Long)

fun main() {
    // 在 List 中使用,底层就是 List<Long>
    val ids = listOf(
        ProductId(1L),
        ProductId(2L),
        ProductId(3L)
    )

    // 遍历时也是类型安全的
    ids.forEach { id ->
        println("产品ID: ${id.value}")
    }

    // Map 的 key 也可以用 value class
    val productNames = mapOf(
        ProductId(1L) to "手机",
        ProductId(2L) to "电脑"
    )
}

常见场景

防止参数混淆

@JvmInline
value class Latitude(val value: Double)

@JvmInline
value class Longitude(val value: Double)

// 没有 value class 时,经纬度容易传反
fun getDistance(lat1: Latitude, lon1: Longitude, lat2: Latitude, lon2: Longitude): Double {
    // 计算两点之间的距离
    val dLat = lat2.value - lat1.value
    val dLon = lon2.value - lon1.value
    return Math.sqrt(dLat * dLat + dLon * dLon)
}

fun main() {
    // 类型安全,不会传反
    val distance = getDistance(
        Latitude(39.9),
        Longitude(116.4),
        Latitude(31.2),
        Longitude(121.5)
    )
}

类型安全的度量单位

@JvmInline
value class Kilometers(val value: Double) {
    fun toMiles(): Miles = Miles(value * 0.621371)
}

@JvmInline
value class Miles(val value: Double) {
    fun toKilometers(): Kilometers = Kilometers(value * 1.60934)
}

@JvmInline
value class Celsius(val value: Double) {
    fun toFahrenheit(): Fahrenheit = Fahrenheit(value * 9 / 5 + 32)
}

@JvmInline
value class Fahrenheit(val value: Double) {
    fun toCelsius(): Celsius = Celsius((value - 32) * 5 / 9)
}

fun main() {
    val distance = Kilometers(100.0)
    println("${distance.value}公里 = ${distance.toMiles().value}英里")

    val temp = Celsius(37.0)
    println("${temp.value}摄氏度 = ${temp.toFahrenheit().value}华氏度")
}

领域建模中的类型安全 ID

@JvmInline
value class OrderId(val value: String)

@JvmInline
value class CustomerId(val value: String)

data class Order(
    val id: OrderId,
    val customerId: CustomerId,
    val amount: Double
)

fun findOrdersByCustomer(customerId: CustomerId): List<Order> {
    // 查询逻辑
    return emptyList()
}

fun cancelOrder(orderId: OrderId) {
    // 取消逻辑
}

fun main() {
    val customerId = CustomerId("C001")
    val orderId = OrderId("O001")

    // 不会把 orderId 误传给需要 customerId 的函数
    findOrdersByCustomer(customerId)
    cancelOrder(orderId)
}

注意事项

  • 必须加 @JvmInline:在 JVM 平台上,value class 必须加 @JvmInline 注解,否则编译错误
  • 装箱场景:当 value class 被当作其实现接口类型使用、作为泛型类参数、或放入可空位置时,会发生装箱(boxing),产生运行时开销
  • 不能有构造器参数:value class 只能包装一个,如果需要,应该用 data class
  • 序列化需要额外处理:value class 默认序列化为其底层,如果需要不同行为,要自定义序列化器
  • 反射限制通过反射访问 value class 时,可能会遇到一些边界情况

进阶用法

与序列化配合

import kotlinx.serialization.*

@Serializable
@JvmInline
value class Password(val value: String)

@Serializable
data class LoginRequest(
    val username: String,
    val password: Password
)

fun main() {
    val request = LoginRequest("alice", Password("secret123"))
    val json = kotlinx.serialization.json.Json.encodeToString(request)
    // password 字段直接序列化为字符串值,不是对象
    println(json)  // {"username":"alice","password":"secret123"}
}

条件初始化

虽然 value class 不能有 init ,但可以通过伴生对象工厂方法实现验证:

@JvmInline
value class PositiveInt private constructor(val value: Int) {
    companion object {
        // 工厂方法,带验证逻辑
        fun create(value: Int): PositiveInt {
            require(value > 0) { "值必须为正数,当前值: $value" }
            return PositiveInt(value)
        }
    }
}

fun main() {
    val valid = PositiveInt.create(42)    // 正确
    // PositiveInt.create(-1)             // 抛出 IllegalArgumentException
}

多平台项目中的 value class

value class 在 Kotlin Multiplatform 中特别有用,因为它们在各平台都没有额外开销:

// 在共享模块中定义
@JvmInline
value class NetworkResult(val statusCode: Int) {
    val isSuccess: Boolean get() = statusCode in 200..299
    val isClientError: Boolean get() = statusCode in 400..499
    val isServerError: Boolean get() = statusCode in 500..599
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式