Что такое smart cast в Kotlin
Что проверяют
Понимаете ли вы, что за этим сахаром стоит анализ потока управления, а не магия компилятора. На собеседовании ценится умение назвать случаи, где приведение не срабатывает: именно они превращаются в непонятную ошибку компиляции в живом коде и в лишние восклицательные знаки после неё.
Как отвечать
Smart cast это автоматическое приведение типа после проверки, которую компилятор сумел доказать. После is, после отрицательной проверки с выходом из функции, после сравнения с null компилятор дальше видит переменную уже в суженном типе.
fun length(x: Any?): Int {
if (x !is String) return 0
return x.length // x здесь уже String
}Работает и внутри логических выражений: в конъюнкции правая часть видит тип, суженный левой.
Условие ровно одно: значение между проверкой и использованием не могло измениться. Отсюда правила. Локальная неизменяемая переменная и параметр сужаются всегда. Локальная изменяемая сужается, если её не меняет захватывающая лямбда. Изменяемое свойство класса не сужается никогда: между проверкой и чтением его может поменять другой поток или чужой код. Свойство с пользовательским геттером, открытое для наследования свойство и свойство из другого модуля не сужаются, потому что повторное чтение вправе вернуть другое значение.
Обходные пути: скопировать в локальную переменную, использовать безопасный вызов с let, применить безопасное приведение с элвисом. Собственную функцию-проверку можно научить сужать тип через контракты.
Где спотыкаются
Видят сообщение о невозможности приведения из-за изменяемого свойства и дописывают двойной восклицательный знак. Правильный ход это копия в локальную переменную: она и компилируется, и убирает ту самую гонку, ради которой запрет введён.
Объясняют отказ тем, что переменная изменилась, хотя в коде она нигде не меняется. Компилятор рассуждает о возможности, а не о факте: для изменяемого свойства достаточно того, что изменение в принципе допустимо.
Считают приведение по проверке заменой явному. Явное нужно там, где проверки нет, и брать стоит безопасный вариант, чтобы получить null вместо исключения на несовпадении типов.