Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Value Class — обёртка со слабой видимостью в runtime
Value Class (или inline class) в Kotlin — это класс, который оборачивает единственное свойство, но при компиляции целиком инлайнится в вызывающий код. Это позволяет добавить типизацию без оверхеда создания объектов в памяти.
Синтаксис
@JvmInline
value class UserId(val value: String)
@JvmInline
value class Email(val value: String)
@JvmInline
value class Age(val value: Int)
Аннотация @JvmInline требуется для совместимости с Java. Ключевое слово value указывает, что это value class.
Преимущество: Type Safety без оверхеда памяти
// ❌ Плохо — легко перепутать параметры
fun createUser(id: String, email: String, age: Int) {
// Легко ошибиться и передать параметры в неправильном порядке
println("$id, $email, $age")
}
creatUser("alice@example.com", "12345", "alice") // Ошибка порядка!
// ✅ Хорошо — типизация гарантирует правильный порядок
@JvmInline
value class UserId(val value: String)
@JvmInline
value class Email(val value: String)
@JvmInline
value class Age(val value: Int)
fun createUser(id: UserId, email: Email, age: Age) {
println("${id.value}, ${email.value}, ${age.value}")
}
creatUser(UserId("12345"), Email("alice@example.com"), Age(25)) // ✅ Понятно
Как это работает под капотом?
@JvmInline
value class UserId(val value: String)
val userId = UserId("12345")
В Kotlin коде:
userId— это объект типа UserId- Можно вызывать методы, расширения
При компиляции в bytecode:
UserIdцеликом заменяется наString- Нет объекта UserId в памяти!
- Это просто String
// После компиляции эквивалентно:
val userId: String = "12345"
Пример: Работа с деньгами
@JvmInline
value class Money(val cents: Long) {
operator fun plus(other: Money): Money = Money(cents + other.cents)
operator fun minus(other: Money): Money = Money(cents - other.cents)
fun toUSD(): Double = cents / 100.0
override fun toString(): String = "\$${toUSD()}"
}
@JvmInline
value class Price(val money: Money)
@JvmInline
value class Balance(val money: Money)
fun main() {
val price = Price(Money(9999)) // $99.99
val balance = Balance(Money(10000)) // $100.00
val newBalance = Balance(balance.money - price.money)
println(newBalance) // $0.01 ✅
}
Ограничения Value Class
1. Только одно свойство:
// ❌ Ошибка — больше одного свойства
@JvmInline
value class Point(val x: Int, val y: Int)
// ✅ Правильно — одно свойство
@JvmInline
value class Coordinate(val value: Pair<Int, Int>)
2. Не может наследовать классы:
// ❌ Ошибка — value class не может наследовать
open class Base
@JvmInline
value class Derived(val value: String) : Base()
// ✅ Может реализовать интерфейсы
interface Comparable
@JvmInline
value class WithComparable(val value: String) : Comparable
3. Не может быть переменной в наследовании:
// ❌ Ошибка — нельзя переопределить
open class Generic<T>
@JvmInline
value class MyValue(val value: Int) // Не может быть <T>
Практический пример: ID типизация
// Проблема: легко перепутать ID разных типов
fun getUser(userId: Long) { }
fun getPost(postId: Long) { }
getUser(postId) // ❌ Ошибка типизации не поймана!
// Решение: Value Classes
@JvmInline
value class UserId(val value: Long)
@JvmInline
value class PostId(val value: Long)
fun getUser(userId: UserId) { }
fun getPost(postId: PostId) { }
getUser(postId) // ✅ Ошибка во время компиляции!
Расширения для Value Classes
@JvmInline
value class UserId(val value: String)
// Расширение функция
fun UserId.isValid(): Boolean = value.length > 0
// Расширение свойство
val UserId.prefix: String get() = "user_$value"
val userId = UserId("12345")
println(userId.isValid()) // true
println(userId.prefix) // "user_12345"
Сравнение подходов
// 1. Без типизации (плохо)
fun processData(userId: String, email: String, phone: String) {
// Легко передать параметры в неправильном порядке
}
processData("12345", "alice", "alice@example.com") // Ошибка неочевидна!
// 2. Обычный data class (безопасно, но с оверхедом памяти)
data class UserId(val value: String)
data class Email(val value: String)
data class Phone(val value: String)
fun processData(userId: UserId, email: Email, phone: Phone) {
// Каждый параметр — отдельный объект в памяти
}
processData(UserId("12345"), Email("alice@example.com"), Phone("555-1234"))
// 3. Value Class (безопасно + без оверхеда памяти) ✅
@JvmInline
value class UserId(val value: String)
@JvmInline
value class Email(val value: String)
@JvmInline
value class Phone(val value: String)
fun processData(userId: UserId, email: Email, phone: Phone) {
// При компиляции превращается в обычные String параметры
// Никакого оверхеда!
}
processData(UserId("12345"), Email("alice@example.com"), Phone("555-1234"))
Производительность
@JvmInline
value class UserId(val value: String)
// Во время выполнения эквивалентно:
val userId: String = "12345"
// Абсолютно никакого оверхеда!
// Это просто String с дополнительной типизацией на уровне компилятора
Когда использовать Value Class
- Обёртка над примитивом (String, Int, Long, Double)
- Для типизации ID (UserId, PostId, etc)
- Для типизации специализированных значений (Email, PhoneNumber, Price)
- Когда нужна type safety без оверхеда памяти
Когда использовать data class вместо Value Class
- Когда нужно несколько свойств
- Когда нужна возможность наследования
- Когда нужна more flexibility
Вывод
Value Class — это инструмент для добавления типизации без оверхеда памяти. Идеально подходит для обёрток над примитивами, ID, специализированных значений. При компиляции целиком инлайнится, оставаясь на уровне типизации компилятора. Это отличный способ сделать код более безопасным и выразительным.