case-классы: данные без шаблонного кода
Один модификатор case перед классом — и компилятор бесплатно дарит вам десяток полезных методов.
«case-класс — это объявление „здесь чистые данные“, и Scala награждает за честность удобством.»
case class предназначен для моделирования неизменяемых данных. Достаточно добавить слово case перед class — и компилятор сгенерирует кучу всего полезного автоматически.
case class Person(name: String, age: Int)
val p = Person("Аня", 25) // apply: можно без new
println(p.name) // Аня — поля публичны и неизменяемы
println(p) // Person(Аня,25) — готовый toStringЧто вы получаете бесплатно:
- apply — создание без
new. - equals и hashCode — сравнение по значению полей, а не по ссылке.
- toString — читаемое представление.
- copy — создание изменённой копии.
- unapply — поддержка паттерн-матчинга.
Сравнение по значению
val a = Person("Аня", 25)
val b = Person("Аня", 25)
println(a == b) // true! сравниваются поля, а не адресаcopy — неизменяемое обновление
Поскольку данные неизменяемы, «изменить» значит создать новую копию с изменёнными полями:
val p = Person("Аня", 25)
val older = p.copy(age = 26) // новый объект
println(older) // Person(Аня,26)
println(p) // Person(Аня,25) — оригинал целТа же идея на Python ▶
# Аналог case class на Python — dataclass
from dataclasses import dataclass, replace
@dataclass(frozen=True) # frozen = неизменяемый, как case class
class Person:
name: str
age: int
p = Person("Аня", 25)
print(p) # Person(name='Аня', age=25) — авто toString
print(p == Person("Аня", 25)) # True — сравнение по значению
older = replace(p, age=26) # аналог copy
print(older, p) # копия изменена, оригинал целcase class Person(name, age)
автоматически даёт:
apply equals hashCode toString copy unapply
\___________________________________/
ноль строк ручного кодаКак работает под капотом (JVM)
Когда компилятор видит case, он генерирует обычный класс JVM плюс companion-объект с методами apply и unapply. Поля становятся final-полями с геттерами. equals и hashCode переопределяются так, чтобы учитывать все поля — поэтому два объекта с одинаковыми полями равны. Метод unapply — это то, что позволяет «разбирать» объект в паттерн-матчинге: компилятор вызывает его, чтобы извлечь поля.
Частые ошибки
- Делать поля изменяемыми (
var). Это ломает идею неизменяемых данных; используйтеcopyдля «изменения». - Думать, что
copyменяет оригинал. Он создаёт новый объект, оригинал не трогает. - Класть в case-класс много поведения. Он для данных; сложную логику выносите в отдельные методы или сервисы.
Best practices
- Используйте
case classдля всех структур данных предметной области. - Обновляйте данные через
copy, сохраняя неизменяемость. - Полагайтесь на сгенерированный
equalsвместо ручного сравнения.
case-классы как язык предметной области
case-классы — это то, чем вы будете описывать данные предметной области изо дня в день. Заказ, пользователь, координата, событие — почти всё это естественно выражается case-классом. Бесплатные equals и hashCode по значению означают, что два объекта с одинаковым содержимым равны и одинаково ведут себя как ключи в коллекциях — именно то, чего ждёшь от данных.
Метод copy заслуживает отдельного упоминания, потому что он меняет привычку «изменить объект» на «создать обновлённую копию». Поначалу это кажется расточительным, но благодаря структурному разделению под капотом копии дешёвы, а выгода огромна: вы никогда не получите неожиданно изменённый объект, на который ссылается ещё кто-то. Состояние программы становится цепочкой неизменяемых снимков, и это резко упрощает отладку — каждый снимок можно изучить, и он не исчезнет под рукой.
В реальных проектах case-классы часто складываются в небольшие иерархии вместе с enum и sealed, образуя точную модель предметной области. Такая модель становится словарём, на котором говорит вся программа: данные описаны явно, варианты перечислены, а компилятор следит за их корректной обработкой. Это превращает структуру данных из технической детали в опору всей архитектуры.
Итоги. case class бесплатно даёт apply, equals, hashCode, toString, copy и unapply, сравнивает по значению и идеален для неизменяемых данных. Дальше — деконструкция через паттерн-матчинг.