В Kotlin inline class (начиная с Kotlin 1.5 — термин "value class"), позволяют создавать обёртки над типами с минимальными издержками. При компиляции такие классы под капотом подменяются на их внутреннее значение (value) во избежание накладных расходов на создание объектов.
Ограничения и особенности:
=== не работает как обычно).Пример:
@JvmInline value class UserId(val value: String) fun getUser(id: UserId) { println("Loading user with id: ${id.value}") } val id = UserId("XYZ") getUser(id) // Под капотом работает просто со String!
Когда использовать:
Можно ли наследовать value class или использовать его в hierarchy интерфейсов/абстрактных классов?
Ответ: Нет, value class не может наследовать другие классы (исключая интерфейсы), не может быть открыт для наследования, не допускает init-блок и других нестатических полей. Единственный доступный вариант — реализовывать интерфейсы.
Пример:
interface Validatable { fun isValid(): Boolean } @JvmInline value class Email(val raw: String) : Validatable { override fun isValid() = raw.contains("@") }
История
Android-приложение резко увеличило время старта после добавления value class к параметрам Parcelable: выяснилось, что некорректный @Parcelize с value class приводил к boxing/unboxing на каждом этапе сериализации, сбивая преимущества inline.
История
Микросервис начал активно использовать value class для UserId и ProductId ради типобезопасности, но во многих местах дженериковые функции требовали рефлексии, которая не работала с "оберткой". Unit-тесты неожиданно начали падать, появились ClassCastException.
История
Мигрированный с Java код стал заменять внутренние доменные классы на value class для оптимизации, но их использование в качестве nullable-полей приводило к неожиданным nulll pointer exception, ведь value class может быть null только если наружное значение тоже null, а это ломало старые инварианты.