Освой Kotlin играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
В Java существует проблема с исключением NullPointerException и разработчику нужно постоянно устраивать проверки на null. В Kotlin существует защита от подобных ошибок. Нужно использовать вопросительный знак ? у типа.
// Не скомпилируется. Кот не может быть null
var notNullCat: Cat = null
// Кот может быть null
var cat: Cat? = null
// Не скомпилируется, кот может быть null и мы должны учитывать это
cat.print()
// Можем печатать, если cat != null
cat?.print()
// Умное приведение. Если мы проводили проверку if,
// то можно вызывать обычным способом
// checked nullity
if (cat != null) {
cat.print()
}
// Если уверены, что объект не null. Иначе выбросит исключение
cat!!.print()
// Используйте элвис-оператор, чтобы дать альтернативное значение, если объект равен null
val name = cat?.name ?: "empty"
Любой объект может быть null и мы можем явно указать это через символ вопроса ?. В этом случае Kotlin будет проверять на возможность ошибки и предупреждать на этапе разработки.
Любой тип, не поддерживающий null, является дочерним типом соответствующего типа, поддерживающего null, поэтому не путайте следующие выражения:
// Так можно
val x: Int = 9
val y: Int? = x
// Так нельзя
val x: Int? = 9
val y: Int = x
Умное приведение Smart cast позволяет превратить переменную из одного состояния в другое: cat? превратится в cat (см. пример выше) или переменная a типа Int? превратится в Int.
val a: Int? = null
if (a != null) {
a.toLong() // теперь это Int
}
Если умное приведение вам не нужно, то используйте запись с безопасным оператором ?.
val a: Int? = null
a?.toLong()
Функция будет вызвана только в том случае, если значение a отлично от null. Безопасные вызовы можно сцеплять.
Не путайте null с пустой строкой "". Выводим значение переменной типа String.
private var catName: String? = null
println(catName)
// Выводится следующее
null
Любую функцию или параметр конструктора можно объявить с null-совместимым типом. Следующий код определяет функцию printInt(), которая получает параметр типа Int? (null-совместимый Int):
fun printInt(x: Int?) {
println(x)
}
Функция может иметь null-совместимый возвращаемый тип.
fun result(): Long? {
...
}
Можно создать массив нулевых объектов. При этом массив может содержать и строки и null.
var cats: Array<String?> = arrayOf("Васька", "Мурзик", null)
Другой "Элвис-оператор" ?: (напоминает причёску Элвиса Пресли, если повернуть голову, как на смайликах) позволяет назначить альтернативное значение, если объект равен null.
val a: Int? = null
val myLong = a?.toLong() ?: 0L
var cat: Cat? = null
// Если null, то вернуть "Без имени"
val string: String = cat?.name ?: "Без имени"
println(string)
Справа от элвис-оператора можно использовать return и выбрасывать исключения.
val myLong = a?.toLong() ?: return false
val myLong = a?.toLong() ?: throw IllegalStateException()
Если вы точно уверены, что ваша переменная не null, то можете использовать оператор !!. Kotlin будет полагаться на ваш профессионализм и не станет проверять ваше предположение. Если вы ошиблись в своём предположении, то ваше приложение может грохнуться.
// скомпилируется, но приложение грохнется
val a: Int? = null
a!!.toLong()
Например, в Android часто объявляются компоненты, а инициализация происходит позже.
// объявляем
var button: Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// инициализируем
button = findViewById(R.id.button)
button?.setOnClickListener(View.OnClickListener { })
}
Подобный способ позволяет избежать null и аналогичен записи в Java.
if (button != null) {
button.setOnClickListener(/* Ваш код */);
}
При полной уверенности можете написать
button!!.setOnClickListener { /* */ }
Ещё один способ избежать проблем с null - это использовать ключевое слово let вместе с оператором ?.:
cat?.let{
println(it.name)
}
Код будет выводить имя кота только в том случае, если объект не равен null.
Этот подход удобен, когда имеются смешанные варианты и мы хотим выполнения кода только для объектов, которые не имеют значения null:
var array = arrayOf("Мурзик", "Васька", null)
for (item in array){
item?.let{
println(it)
}
}
В массиве три элемента, но на экран выводятся только два элемента.
Такой подход сокращает и упрощает код. Допустим, у нас есть функция getBestCat(), возвращающий тип Cat?.
fun getBestCat(): Cat?{
return Cat()
}
Можем вызвать с проверкой на null.
var cat = getBestCat()
if(cat != null){
cat.eat()
}
А можно обойтись без создания новой переменной, а сразу использовать let:
getBestCat()?.let{
it.eat()
}
Запись означает следующее: получить объект "Самый лучший кот", и если объект не null, то позволить ему есть (eat()).