Иерархия типов Scala
В Scala все типы образуют одно стройное дерево — от всеобъемлющего Any наверху до загадочного Nothing внизу.
«Единая иерархия типов — это обещание: что бы вы ни написали, у этого есть место в системе типов.»
В Java примитивы (int, double) и объекты живут в разных мирах и не дружат. Scala чинит этот разрыв: абсолютно всё — от целого числа до сложного объекта — наследуется от общего корня Any.
Карта типов
Any
/ \
AnyVal AnyRef (= java Object)
/ | \ | \ \
Int Double Boolean String List ваши классы
Unit Char ...
\ /
\ /
Nothing (наследник ВСЕГО)
Null (под всеми AnyRef)Дерево делится на две большие ветви:
- AnyVal — типы-значения:
Int,Double,Boolean,Char,Unit. Это аналоги примитивов, хранятся эффективно. - AnyRef — ссылочные типы:
String, коллекции, ваши классы. Это в точностиjava.lang.Object.
Unit вместо void
Тип Unit — это «ничего полезного», аналог void. Его единственное значение — (). Функции, которые что-то делают, но не возвращают результат (например, println), имеют тип Unit.
val nothing: Unit = ()
val x: Any = 42 // Int — это тоже Any
val y: Any = "строка" // и String — тоже AnyNothing — тип, у которого нет значений
В самом низу дерева — Nothing. У него вообще нет значений, и он является подтипом всего. Зачем такое нужно? Чтобы описывать выражения, которые никогда не возвращают результат нормально — например, бросают исключение.
def fail(msg: String): Nothing =
throw new RuntimeException(msg)
// Nothing подходит под любой тип:
val n: Int = if false then 1 else fail("упс")Та же идея на Python ▶
# В Python тоже всё — объект, и есть общий предок object
print(isinstance(42, object)) # True
print(isinstance("hi", object)) # True
print(type(42).__mro__) # цепочка наследования: int -> object
# Аналог Unit/void — функция возвращает None
def do_print(x):
print(x) # неявно возвращает None
result = do_print(5)
print(result) # NoneКак работает под капотом (JVM)
Здесь Scala делает изящный трюк. AnyRef — это буквально java.lang.Object JVM. А AnyVal-типы вроде Int при компиляции превращаются в обычные примитивы JVM (int), когда это возможно — ради скорости. Но если Int нужно положить в коллекцию объектов, Scala автоматически «упаковывает» его в объект (boxing). Единая иерархия — это удобная иллюзия для программиста, которую компилятор аккуратно раскладывает в две реальности JVM.
Частые ошибки
- Случайно получить тип
Any. Если в одной структуре смешатьIntиString, общий тип станетAny, и вы потеряете методы. Следите за выводом типов. - Путать
Unitи пустоту.Unit— это полноценный тип с единственным значением(), а не «отсутствие типа». - Бояться
Nothing. Он не для повседневного кода, а для выражений, которые не возвращаются нормально.
Best practices
- Старайтесь, чтобы выведенные типы были конкретными, а не
Any— это ловит ошибки рано. - Используйте
Unitявно в сигнатурах функций-действий, чтобы показать: возврата нет. - Помните:
Nullв Scala 3 лучше избегать, заменяя его наOption(увидим позже).
Зачем программисту знать карту типов
Понимание иерархии типов — это не академическая роскошь, а практический навык. Когда компилятор выводит для выражения неожиданный тип Any, вы сразу понимаете причину: где-то смешались несовместимые типы, и компилятор нашёл их общего предка. Зная карту, вы быстро находите место ошибки вместо того, чтобы гадать. Точно так же знание про Nothing объясняет, почему ветка с throw не ломает вывод типов в if-выражении.
Эта единая иерархия — одно из решений, которое делает Scala концептуально цельной. В языках с разрывом между примитивами и объектами приходится помнить кучу частных правил: что можно положить в коллекцию, а что нет, где нужна обёртка. В Scala правило одно: всё — это Any, а дальше просто две ветви. Компилятор берёт на себя заботу о том, чтобы эффективно разложить эту модель на реальность JVM.
На практике вам редко придётся явно работать с Any или Nothing, но знание их места в дереве делает сообщения компилятора понятными. Когда инструмент жалуется, что выражение имеет тип Any, вы сразу знаете, что искать — точку, где встретились несовместимые типы. Эта способность читать вывод типов отличает уверенного разработчика от того, кто борется с компилятором вслепую.
Итоги. Все типы Scala — одно дерево: Any сверху, AnyVal (значения) и AnyRef (объекты) — две ветви, Nothing — снизу. Это устраняет разрыв примитивов и объектов. Дальше — строки, числа и операторы.