Мы привыкли думать, что
value class - это серебряная пуля для Type Safety без оверхеда. Обернули Int в UserId, и в рантайме это просто int, верно?Не всегда. Есть сценарии, где
value class начинает вести себя хуже, чем обычный data class, порождая скрытый боксинг на ровном месте.Рассмотрим классический кейс: попытка написать обобщенный обработчик.
@JvmInline
value class UserId(val id: Int)
fun <T> handle(item: T) {
// Какая-то логика
println(item)
}
fun main() {
val uid = UserId(42)
handle(uid) // <--- Здесь происходит магия (плохая)
}
Что происходит под капотом?
В момент вызова
handle(uid) компилятор сталкивается с проблемой. Функция handle ожидает T (который в JVM стирается до Object), а UserId в скомпилированном виде - это примитив int.Чтобы передать
int туда, где ожидается Object, JVM обязана его упаковать.1. Создается экземпляр-обертка
UserId в куче (heap allocation).2. Этот объект передается в функцию.
3. Если внутри функции мы снова кастуем его к конкретному типу, происходит анбоксинг.
Еще хуже: Интерфейсы
Если ваш
value class реализует интерфейс, и вы работаете с ним через ссылку на этот интерфейс - вы гарантированно получаете боксинг.
interface Identity {
fun raw(): Int
}
@JvmInline
value class GroupId(val id: Int) : Identity {
override fun raw() = id
}
fun process(id: Identity) { ... } // 100% боксинг при вызове
Почему это важно?
Если вы используете value classes в горячих циклах (hot paths) или высоконагруженных стримах, думая, что экономите память, использование их в качестве дженериков или через интерфейсы приведет к обратному эффекту:
🖤GC Pressure: Вы создаете короткоживущие объекты-обертки тысячами.
🖤Снижение производительности: Аллокация + боксинг/анбоксинг стоят дороже, чем передача ссылки на обычный объект.
Как лечить?
1. Избегайте интерфейсов на value classes, если критична производительность.
2. Специализируйте функции. Вместо
fun <T> handle(t: T) пишите перегрузки для конкретных value типов, если это возможно.3. Ждите Project Valhalla. Настоящие примитивные классы в JVM решат эту проблему фундаментально, но пока мы живем с ограничениями Type Erasure.
Итог:
value class идеален для доменной модели и сигнатур функций, но будьте предельно осторожны, как только они попадают в полиморфный контекст.#kotlin #performance #jvm #senior
✍️ @kotlin_lib