Вы только что закончили создание приложения на базе Android и пытаетесь его выполнить. Насколько вы знаете, приложение в порядке, синтаксических ошибок нет, и код должен работать нормально. Но когда вы запускаете его, приложение завершает работу, сообщая, что возникло не пойманное исключение RuntimeException. Пытаясь докопаться до причины, вы находите нечто, что дает вам подсказку: произошло NullPointerException.
С этого вы начинаете свое путешествие в мир обработки исключений в Android, в частности, обработки NullPointerException. В этой статье мы обсудим, как исправить NullPointerException в приложениях Android.
Оглавление
Что такое NullPointerException?
Во-первых, давайте быстро освежим в памяти информацию об исключениях. Это события или аномальные условия в программе, которые возникают во время выполнения и нарушают нормальный ход программы.
Исключение может возникнуть по разным причинам, например:
- пользователь вводит недопустимые данные в поле
- файл, который должен быть открыт, не может быть найден
- Сетевое соединение потеряно в середине коммуникации
- в JVM закончилась память.
Когда внутри метода возникает ошибка, он выбрасывает исключение. Исключение NullPointerException является одним из наиболее распространенных исключений во время выполнения.
В Java null - это специальное значение, которое представляет собой отсутствие значения. Когда вы пытаетесь использовать нулевое значение, вы получаете исключение NullPointerException, потому что операция, которую вы пытаетесь выполнить, не может быть завершена для нулевого значения.
В Kotlin null - это не значение, а собственный тип, называемый nullable. По умолчанию каждый объект в Kotlin не имеет null, что означает, что он не может иметь нулевое значение.
Почему возникают исключения NullPointerException?
Вы можете столкнуться с NullPointerException при попытке получить доступ к представлению, ресурсу или данным, которые еще не были должным образом инициализированы или загружены. Согласно спецификации языка Java, некоторые из ситуаций, в которых может возникнуть NullPointerException, включают:
- Попытка доступа к элементам нулевого массива
- Использование switch с нулевым выражением
- Доступ к полям экземпляра нулевых ссылок
- вызов методов экземпляра нулевой ссылки
- использование целочисленного оператора или оператора с плавающей точкой, один из операндов которого является нулевой ссылкой в квадрате
- Попытка преобразования без вставки с вложенным значением в виде null
- Вызов super для нулевой ссылки
Избегание исключений NullPointerExceptions в Java
Ниже приведены некоторые лучшие практики для предотвращения NullPointerExceptions в Java:
- Сравнение строк с литералами
- Избегайте возврата null из ваших методов
- Постоянно проверяйте аргументы методов
- Используйте String.valueOf(), а не toString()
- Используйте примитивные типы данных как можно чаще
- Избегайте цепочек вызовов методов
- Используйте тернарный оператор
В отличие от него, Kotlin является более умным и современным языком, который был разработан для предотвращения NullPointerExceptions с помощью нескольких механизмов, таких как:
- использование нуллируемых и ненуллируемых типов
- использование функции SmartCast
- Безопасные вызовы
- Оператор Elvis
В Kotlin все регулярные типы являются ненулевыми, если только вы явно не пометите их как нулевые с помощью вопросительного знака ?, например, String?
Рассмотрим приведенный ниже код Kotlin:
fun getlen(name: String) = name.length
Имя параметра имеет тип String, что означает, что оно всегда должно содержать экземпляр String и не может содержать null. Этот код гарантирует, что исключение NullPointerException во время выполнения вряд ли произойдет.
Вместо этого любая попытка передать нулевое значение в функцию getlen(name: String) приведет к ошибке компиляции: Null не может быть значением ненулевого типа String. Это происходит потому, что компилятор ввел правило, согласно которому аргументы функции getlen() не могут быть нулевыми.
Рассмотрим приведенный ниже фрагмент, в котором код очевиден для нас, но может быть не сразу очевиден для компилятора:
class TestNPE {
companion object {
@JvmStatic
fun main(args: Array<String>) {
var m : String? // here, m is declared as nullable
println("m is : $m")
var x: Int
x = 150
if (x == 150)
println("Value of m is : $m")
}
}
}

Компилятор выдает ошибку компилятора, поскольку m не инициализирован:
Таким образом, вместо того, чтобы перейти к выполнению и затем выдать исключение, он останавливается на этапе компиляции с ошибкой компилятора.
Использование SmartCast
Для того чтобы использовать типы с нулевыми значениями, в Kotlin есть возможность, называемая safe cast, или smart cast. Благодаря этой функции компилятор Kotlin будет отслеживать ситуации внутри if и других условных выражений. Таким образом, если компилятор обнаружит переменную, принадлежащую к ненулевому типу, он позволит вам безопасно получить доступ к этой переменной.
В некоторых случаях компилятор не может выполнить приведение типов, в этом случае он выбросит исключение; это называется небезопасным приведением. Рассмотрим строку с нулевым типом (String?), которую нельзя привести к строке без нуля (String). Это приведет к исключению.
Kotlin решает эту проблему, предоставляя оператор safe cast as? для безопасного приведения к другому типу. Если приведение невозможно, он возвращает null, а не выбрасывает исключение ClassCastException.
Пример:
val aInt: Int? = a as? Int
Использование оператора Elvis
В Kotlin также есть расширенный оператор, называемый оператором Elvis (?:), который возвращает либо ненулевое значение, либо значение по умолчанию, даже если условное выражение равно null. Он также проверяет нулевую безопасность значений.
Рассмотрим пример:
val count = attendance?.length ?: -1
Это значит:
val count: Int = if (attendance != null) attendance.length else -1
Несмотря на это, исключение NullPointerException все равно может возникнуть в Android-приложениях на базе Kotlin.
Рассмотрим предыдущий пример класса TestNPE. Теперь код изменен таким образом, что m инициализируется, но используется с оператором утверждения non-null (!!), который преобразует заданное значение к типу non-null и выбрасывает исключение, если значение равно null.
class TestNPE {
companion object {
@JvmStatic
fun main(args: Array<String>) {
var m: String?=null // here, m is declared
//as nullable
var x: Int
x = 150
if (x == 150)
println("m is : $m")
var mlen = m!!.length
println("length of m is : $mlen")
}
}
}

В этом случае возникнет исключение NullPointerException, как показано здесь:

Избегание исключений NullPointerException в Kotlin
Несколько причин возникновения NullPointerException в Kotlin:
- Явный вызов throw NullPointerException()
- Использование оператора !!!
- Несогласованность данных при инициализации
- Взаимодействие с Java
Чтобы предотвратить NullPointerException, вы всегда должны убедиться, что ваши переменные и объекты правильно инициализированы перед их использованием. Вы также можете использовать проверки на нулевые значения или блоки try … catch для обработки возможных нулевых значений и предотвращения сбоя вашего приложения.
Ниже приведен чрезвычайно упрощенный пример использования try … catch:
class TestNPE {
companion object {
@JvmStatic
fun main(args: Array<String>) {
var m: String?=null // here, m is declared
//as nullable
try {
var x: Int
x = 150
if (x == 150)
println("m is : $m")
var mlen = m!!.length
println("length of m is : $mlen")
}catch( ne: NullPointerException)
{
println("Null Pointer Exception has
occurred. ")
}
}
}
}
Код, который может вызвать NullPointerException, заключен в блок try … catch.
Преимущество этого блока в том, что разработчик контролирует, что должно быть сделано, когда исключение будет выброшено. Здесь выводится простое сообщение. В практических сценариях перед завершением программы можно закрыть все открытые в данный момент ресурсы, например, файлы.
Использование logcat для обнаружения и устранения NullPointerException в Android Studio
При сбое приложения Android в консоль записывается трассировка стека, содержащая важную информацию, которая может помочь определить и решить проблему. Есть два способа получить эту трассировку:
С помощью утилиты adb shell от Google получить файл logcat, который может помочь объяснить причину сбоя приложения:
adb logcat > logcat.txt
Откройте файл logcat.txt и найдите имя приложения. В нем будет информация о том, почему приложение потерпело неудачу, а также другие подробности, такие как номер строки, имя класса и т.д.
В Android Studio либо нажмите Alt+6, либо нажмите кнопку Logcat в строке состояния. Убедитесь, что ваш эмулятор или устройство выбраны на панели ”Устройства”, затем найдите трассировку стека.
В журнале может быть много записей, поэтому вам, возможно, придется немного прокрутить журнал, или вы можете очистить журнал через опцию ”Корзина” и снова дать приложению упасть, чтобы самый последний след стека в журнале оказался наверху.
Важно отметить, что если ваше приложение уже работает, то вы не сможете использовать logcat.
Последняя версия Android Studio Electric Eel имеет обновленный logcat, который облегчает разбор, запрос и отслеживание журналов. Новый logcat также:
- Форматирует журналы для удобного сканирования на наличие тегов, сообщений и другой полезной информации
- Определяет различные типы журналов, такие как предупреждения и ошибки.
- упрощает отслеживание журналов вашего приложения при сбоях и перезагрузках приложения.
Когда logcat заметит, что процесс вашего приложения останавливался и перезапускался, вы увидите в выводе сообщение, подобное приведенному ниже:
PROCESS ENDED
Or:
PROCESS STARTED
Разработчики могут точно настроить команду, чтобы, например, указать временную метку сообщения:
adb logcat -v time
С помощью logcat можно определить, объявлен ли виджет или компонент, но еще не определен, или переменная является нулевой и используется. Иногда может случиться так, что контекст становится нулевым во время перехода между экранами, и вы пытаетесь использовать этот контекст, не понимая, что он нулевой.
Установка точек останова для отладки NullPointerExceptions
Если у вас большое приложение, его отладка может быть довольно сложной. Вы можете установить в коде точки останова, которые позволят вам отлаживать код блок за блоком.
Точка останова служит знаком остановки для отмеченного фрагмента кода. Когда точка останова встречается во время отладки приложения, она приостанавливает выполнение, что позволяет разработчикам детально изучить происходящее и при необходимости использовать другие инструменты отладки.
Чтобы использовать точки останова:
- Добавьте точку останова, щелкнув по желобу в редакторе кода рядом с номером строки, на которой вы хотите приостановить выполнение.Рядом с номером строки появится точка, а сама строка будет выделена. См. ниже; добавлены две точки останова:
- Click Run > Debug ‘app’

Программа останавливается на первой точке останова, и вы можете просмотреть значения в окне Debug в нижней части Android Studio.

Существуют различные кнопки, такие как Step Over и Step Into, которые помогут вам ориентироваться дальше.
Помимо изучения текущих значений определенных операндов и выражений, вы также можете оценивать выражения с помощью опции Evaluate.

В приведенном ниже примере я хотел узнать, каким будет значение x, прибавленное к 100. В окне отображается результат, основанный на текущем значении x:
Здесь приведено подробное объяснение различных терминов, связанных с отладкой в Android Studio.
Заключение
В заключение следует отметить, что в разработке Android существуют различные механизмы, доступные в Java и Kotlin, которые призваны помочь разработчикам избежать NullPointerExceptions. В тех случаях, когда эти исключения все же возникают, у вас теперь должен быть целый ряд инструментов, которые помогут определить причину и отладить код.