Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Защита от null, ключевое слово let

В 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 { /* */ }

Ключевое слово let

Ещё один способ избежать проблем с 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()).

Реклама