Иерархия типов 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 — тоже Any

Nothing — тип, у которого нет значений

В самом низу дерева — 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 — снизу. Это устраняет разрыв примитивов и объектов. Дальше — строки, числа и операторы.

Проверьте себя
1. Какой тип является корнем всей иерархии типов Scala?
AObject
BAny
CAnyRef
DNothing
2. Что особенного в типе Nothing?
AОн хранит null
BУ него нет значений, и он подтип любого типа
CЭто синоним Unit
DЭто тип всех чисел