Красота системы набора текста Kotlin

Система набора текста Kotlin великолепно разработана. Это дает нам очень удобную поддержку обнуления, вывод типов, универсальные средства защиты и многое другое. Например:

В этой статье мы раскроем много волшебства Котлина. Мне всегда нравится говорить об этом на своих семинарах, потому что я вижу потрясающую красоту того, как это было спроектировано так, что все эти части идеально сочетаются друг с другом и дают нам отличный опыт программирования. Также есть несколько полезных советов, как можно использовать систему набора текста в разных случаях. Наслаждаться :)

Иерархия общих типов

Допустим, вы определили класс Customer. Этот класс генерирует два типа: версию, допускающую значение NULL Customer?, и версию, не допускающую значения NULL Customer. Какая между ними связь?

Ну, обнуляемое значение нельзя использовать напрямую. Сначала его нужно распаковать в не допускающее значение NULL. С другой стороны, значение, не допускающее значения NULL, всегда можно использовать как значение, допускающее значение NULL:

В любом случае, значение, не допускающее значения NULL, можно использовать как значение, допускающее значение NULL. Это возможно, потому что между этими двумя типами существует связь. Тип, допускающий значение NULL, - это надтип не допускающего значения NULL.

В иерархии типов Котлина есть еще несколько интересных фактов. Вот вся иерархия на одной картинке:

Вы можете заметить два интересных элемента:

  • Есть супертип всех типов
  • Есть подтип всех типов

Супертип всех типов: Any?

У каждого типа, не допускающего значения NULL, есть супертип с именем Any. Соответственно, его единственный супертип - Any?, который является супертипом каждого типа, включая обнуляемый. Концепция суперкласса всех классов известна из Java, где у нас есть Object. Any? можно использовать таким же образом, чтобы сказать, что мы принимаем что-либо:

Хотя вы также можете заметить, что если вам нужны только типы, не допускающие значения NULL, вы можете использовать Any. Этот факт широко используется для обобщений, чтобы сказать, что мы принимаем только не допускающие значение NULL в качестве аргумента типа:

Подтип всех типов: ничего

В Котлине есть подтип всех типов - Nothing. Его можно рассматривать как любой тип, хотя нет экземпляра, который был бы Nothing. Когда я начинаю рассказывать об этом, это становится чем-то новым для большинства разработчиков. Зачем нам это нужно?

Начнем с более простого вопроса: почему имеет смысл объявлять Nothing как возвращаемый тип? Прекратите читать на минуту и ​​подумайте об этом.

Нет экземпляров Nothing, поэтому мы не можем его вернуть. Хотя есть ли альтернативные способы выхода из функции? Ну, есть один. Мы можем выйти из функции, ничего не вернув. Вместо этого мы можем выбросить исключение. Как оказалось, объявленный тип возвращаемого значения throw - Nothing. Здесь мы объявляем Nothing как возвращаемый тип, чтобы генерировать исключения:

Обратите внимание на результаты того факта, что Nothing является подтипом всех типов. Допустим, у вас есть условие if, которое либо возвращает String, либо Nothing. Каким должен быть предполагаемый тип? Ближайший супертип как String, так и Nothing, то есть String. Вот почему здесь data предполагаемый тип String:

Эта функция также может использоваться во многих других контекстах. Например, мы можем использовать его справа от оператора Элвиса:

Контракт для оператора Элвиса заключается в том, что предполагаемый возвращаемый тип является версией левой стороны, не допускающей значения NULL, и ближайшим супертипом с правой стороны. Слева у нас есть String?, а его версия, не допускающая значения NULL, - String. Справа у нас Nothing. Замыкающий супертип String и Nothing равен String, потому что Nothing является подтипом всех типов, включая String.

По той же причине мы можем выдать ошибку с правой стороны: объявленный тип возвращаемого значения - Nothing.

Точно так же мы можем использовать throw для управляющих структур, которые можно использовать как выражения:

Где Юнит?

Здесь меня часто спрашивают, где же Unit. В этом нет ничего особенного с точки зрения системы набора текста. Это просто объявление объекта (синглтон), и в системе типизации это, как и все другие типы, подтип Any и супертип Nothing.

Когда код недоступен?

Когда элемент объявляет Nothing как возвращаемый тип, это означает, что все после этого недостижимо. Так должно быть, потому что нет экземпляров Nothing. Вот почему, когда мы используем fail или throw, все, что находится после этих выражений, будет недоступно:

По этой причине у нас может быть все слева, и нам не нужно использовать оператор возврата:

Этот факт часто используется самой популярной функцией, возвращающей Nothing, TODO:

Я часто использую его на задачах в своих мастерских, когда даю функции для реализации:

Хотя throw - не единственная конструкция, которая ничего не объявляет как возвращаемый тип. Сможете угадать, какой второй?

Это return. Return возвращается из функции, поэтому все, что находится после return, недостижимо. Он ускользает от функции, поэтому можно сказать, что внутри этой функции он ничего не возвращает. Это согласуется с throw, и именно так система набора текста знает, что все после return недостижимо.

По той же причине мы можем использовать return во множестве управляющих структур:

Обратите внимание, что это наконец объясняет, почему мы можем использовать return в правой части оператора Элвиса:

Тип null

Представьте, что вы объявили переменную или список и установили для нее значение null, но забыли объявить тип. В результате вы видите следующую ошибку:

Вы можете это объяснить? Потратьте немного времени и подумайте об этом.

Причина этой ошибки в том, что, когда мы не объявляем тип переменной или свойства, тип будет взят с правой стороны. Вот что здесь произошло, и тип оказался Nothing?. Итак, вы можете видеть, что тип null - Nothing?. Это почему?

Начнем с того, что заметим, что null - единственный возможный экземпляр Nothing?. Не существует объекта типа Nothing, а версия любого типа, допускающая значение NULL, может быть null. Поэтому, когда мы видим Nothing?, мы можем быть уверены, что там может быть только null. Этот факт используется, когда мы хотим, чтобы делегат свойства использовался только на верхнем уровне, где thisRef равно null:

Еще одна вещь, на которую следует обратить внимание, - это то, что это дает нам идеальный вывод типов. Взглянем еще раз на иерархию типизации:

Теперь подумайте, какой будет предполагаемый тип a и b:

В if мы ищем ближайший супертип типов из обеих веток. Ближайший супертип String и Nothing? - String?. То же самое и с when: ближайший супертип для String, String и Nothing? - String?. Все имеет смысл.

По той же причине всякий раз, когда нам требуется String?, мы можем передать либо String, либо null, то есть Nothing?. Это становится ясно, если взглянуть на иерархию типов. Это два подтипа String?.

Резюме

В этой статье я привел несколько фактов:

  • Каждый класс генерирует тип, допускающий и не допускающий значения NULL.
  • Тип, допускающий значение NULL, является надтипом не допускающего значения NULL
  • Супертип всех типов Any?
  • Супертип типов, не допускающих значения NULL, Any
  • Подтип всех типов Nothing
  • Подтип всех типов, допускающих значение NULL Nothing?
  • Когда функция объявляет Nothing в качестве возвращаемого типа, это означает, что она будет бесконечно вызывать ошибку или веселье.
  • И throw, и return объявляют Nothing как возвращаемый тип
  • Когда выражение объявляет Nothing как возвращаемый тип, все, что выполняется после этого, будет недоступно
  • Тип null - Nothing?, что дает нам ожидаемый вывод типа и благодаря этому null может использоваться всякий раз, когда ожидается тип, допускающий значение NULL.

Картинка, которую следует запомнить, является иллюстрацией иерархии типов Котлина:

Нажмите 👏, чтобы сказать «спасибо!» и помогите другим найти эту статью.

Чтобы быть в курсе отличных новостей о Kt. Academy, подписывайтесь на рассылку новостей, следите за Твиттером и следите за нами в среде.