На этом шаге мы рассмотрим что это за состояние и как его избежать.
Если свойство класса одновременно изменяемое и имеет тип с поддержкой null, убедитесь, что оно не равно null, прежде чем ссылаться на него. Например, рассмотрим следующий код, который проверяет, есть ли у игрока оружие (ведь игрок может быть безоружен или разоружен). Если оружие есть, выводится его название:
class Weapon(val name: String) class Player { var weapon: Weapon? = Weapon("Ebony Kris") fun printWeaponName() { if (weapon != null) { println(weapon.name) } } } fun main() { Player().printWeaponName() }
Вы удивитесь, но этот код не компилируется. Посмотрите на ошибку ниже, чтобы понять почему (рисунок 1).
Рис.1. Умное приведение типа неприменимо к Weapon
Компилятор прерывает компиляцию кода из-за возможности состояния гонки. Состояние гонки возникает, если есть вероятность параллельного изменения состояния из другого места в программе. Это может привести к непредвиденным результатам.
В приведенном выше примере компилятор видит, что хотя weapon и проверяется на неравенство значению null, все равно существует вероятность, что свойство weapon класса Player получит значение null между моментом проверки и вывода названия оружия на экран.
То есть в отличие от других случаев, когда возможно умное приведение типа weapon при проверке на null, в этой ситуации компилятор отказывается продолжить компиляцию, так как не уверен, что weapon никогда не будет null.
Исправить эту проблему и защитить себя от null можно с помощью стандартной функции also:
class Player { var weapon: Weapon? = Weapon("Ebony Kris") fun printWeaponName() { weapon?.also { println(it.name) } } }
Рис.2. Пример использования функции also
Этот код компилируется благодаря стандартной функции also. Вместо ссылки на свойство класса код использует аргумент it функции also, который в данном случае является локальной переменной, существующей только внутри области видимости анонимной функции. Это означает, что переменная it гарантированно не будет изменена другими частями программы. Нам удалось избежать проблемы с умным приведением типа, так как вместо использования свойства с поддержкой null этот код использует локальную переменную, доступную только для чтения и не поддерживающую null. Эта переменная является локальной, потому что also вызывается после оператора безопасного вызова weapon?.also.
На следующем шаге мы рассмотрим ограничение видимости рамками пакета.