Nullable java что это
Перейти к содержимому

Nullable java что это

Приемы и советы. Как избежать NullPointerException в Java приложениях

Приемы и советы. Как избежать NullPointerException в Java приложениях - 1

Сегодня я покажу вам простые приемы того как избежать NullPointerException в ваших приложениях. Им легко следовать, но при этом они заметно повышают надежность и качество вашего кода. Более того, по моему опыту, один первый совет окажет заметное влияние на качество вашего кода. Если вы знаете какие-то еще приемы программирования на Java, не стесняйтесь делиться ими в комментариях. alt=»Приемы и советы. Как избежать NullPointerException в Java приложениях — 2″ width=»625″ />

Учимся избегать null-значений в современном Java. Часть 2

Java

В предыдущей статье мы разобрали, почему в некоторых случаях null оказывается необходимым злом, а также узнали, что есть правильные и ошибочные варианты его использования. В текущей же статье я расскажу, как наиболее безопасно работать с null -значениями и приведу некоторые варианты решений, используемых нами в проекте Columna для того, чтобы избежать ошибок и применить изящную обработку null .

Возвращайте Optional вместо null

Хоть в предыдущей статье я и убеждал вас в уместности null для некоторых сценариев, в этой я буду напротив говорить, что по возможности его все же лучше избегать. При каждом добавлении в базу кода нового метода, способного вернуть пустое значение, вам следует стараться использовать вместо null тип Optional. Этот тип был введен, чтобы разработчики могли указывать на возможность возвращения методом пустого значения, что ранее было недоступно (либо требовало аннотаций, о которых я расскажу ниже). Существует два вида объектов Optional : содержащие значение и без него. Первые изначально создаются со значением, вторые же просто являются статическими одиночками. Структурно типичный случай их использования выглядит так:

Чтобы увидеть, почему сигнатура с Optional предпочтительней, представьте следующий метод:

Из этой сигнатуры понятно лишь, что она возвращает объект типа Admission . При этом, как уже говорилось, null соответствует любому типу. У нас нет иной возможности узнать, возвращает ли данный метод null , кроме как проанализировать его реализацию. В противоположность приведенному примеру, метод, возвращающий Optional , будет выглядеть так:

Поскольку смысл использовать Optional есть только в случае возможного возврата пустого значения, значит метод такое значение вернуть может. Поэтому при каждом возвращении Optional методом вы знаете, что есть вероятность получения пустого значения и его нужно должным образом обработать.

Возвращаемое значение Optional обрабатывается аналогично null : вы проверяете, передано ли значение, и, если да — выполняете с ним определенные действия. Вызов метода, возвращающего null , как правило, выглядит так:

Со значением Optional соответствующий код будет выглядеть так:

В то время как можно легко забыть выполнить проверку на null в первом примере, тип Optional безопаснее и делает очевидным возможное отсутствие возвращаемого значения. Если вы вызовите get() для пустого значения Optional , то получите исключение, как и в случае вызова любого метода для значения null . В самых простых случаях перед вызовом get() всегда нужно вызывать isPresent() , однако API предлагает и другие альтернативы, где при отсутствии значения может возвращаться его предустановленный вариант.

Обратите внимание, что тип Optional резонно использовать только для возвращаемых значений. Например, в примере ниже присваивание Optional в цикле ничем не лучше null . На деле использование null для начального присваивания будет даже более изящным, и у вашей IDE не должно возникнуть сложностей в обнаружении упущенной проверки на null , когда переменная используется в том же методе, где была объявлена.

Однако если бы вместо этого у нас был метод, возвращающий активную госпитализацию, то предпочтительнее было бы использовать Optional :

Можно ли использовать Optional для параметров?

SonarLint отвечает “Нет”, поскольку лучше предоставить действительное значение, чем Optional , которое может его иметь или нет. Ведь иначе нам придется проверять в методе оба случая, а также убеждаться, что никто не передал вместо него null. Смешивать же null и Optional вообще не стоит, иначе смысл в использовании последнего полностью исчезнет.

Никогда не делайте следующее, равно как не предоставляйте null в качестве аргумента параметра с Optional значением:

Если вы сделаете нечто подобное, то также получите NPE (исключение нулевого указателя).

Если у вас есть некое значение, которое, вероятно, содержит null , и вы хотите преобразовать его в Optional , то вам нужно вызвать вместо этого Optional.ofNullable() , который вернет обернутое в Optional значение, если аргумент не является null . В противном случае он вернет пустой Optional . Если же вам нужно выполнить обратную операцию, т.е. преобразовать, вероятно, пустое значение Optional в null или значение, которое оно содержит, используйте orElse(null) , который при отсутствии значения возвращает предоставленный аргумент.

Содействуйте IDE с помощью аннотаций Nullable/Nonnull

Для большинства баз кода полное отсутствие null в передаче данных является утопией. Еще одним способом прояснить ваши намерения и одновременно помочь системе типов помочь вам — это использовать для параметров и возвращаемых значений аннотации, которые указывают, где стоит ожидать null , а где нет, а также где его использование предполагается. Такую возможность предлагают рзаные фреймворки, но основная идея одна: параметр можно снабдить либо тегом Nonnull , сообщая, что значение null для него ожидать не нужно, либо Nullable , указывая обратное. Добавление тегов допустимо также для полей и возвращаемых значений.

Вот один из примеров подобной сигнатуры метода:

Сейчас эта сигнатура показывает, что метод не предполагает вызов с параметром Patient , содержащим null , и если такой вызов произойдет, IDE выдаст предупреждение. При этом мы также проинформированы, что данный метод может вернуть null , и вы аналогичным образом получите предупреждение при использовании этого возвращаемого значения метода.

Если новым методам в возвращаемых значениях следует использовать Optional вместо null , то выполнение обширного рефакторинга существующих методов обычно в ходе разработки функционала не рассматривается. Тем не менее, когда вы пишите код, то простое добавление тега после каждой проверки, допускает ли значение null , окажет помощь всем, кто будет использовать этот код в будущем, и поможет избежать NPE.

Используйте Objects.requireNonNull()

Аннотации имеют значение только во время компиляции и не влияют на поведение программы при выполнении. Если вам нужно, чтобы при выполнении метод не вызывался с null -аргументом, то следует использовать метод Objects.requireNonNull (T obj, строка сообщения). Объекты — это статический служебный класс для выполнения основных операций вроде equals , hashCode и toString , защищенным от null способом.

Чтобы оградить метод от нулевых аргументов, передайте в метод requireNonNull() соответствующие параметры вместе с описательным текстом ошибки. Если параметр окажется null , это вызовет NPE с указанным вами сообщением об ошибке. В противном случае данный метод просто вернет параметр. Обычно его используют в конструкторах, как показано ниже:

Здесь у вас может возникнуть вопрос, заключается ли причина избегания нулевого параметра в том, что он может ненамеренно привести к NPE. Не будет ли лучше выбросить NPE намеренно до того, как это произойдет?

Скорее всего, это одно и то же, и с позиции пользователя выглядеть это будет тоже одинаково — в виде появления ошибки. А вот при диагностировании причины сбоя в дальнейшем, последний вариант окажется гораздо предпочтительнее. Проблема с NPE в том, что они не сообщают, какая переменная оказалась null, а просто предоставляют номер строки. В строке, где для объектов вызывается несколько методов, может быть сложно определить, в каком из них причина.

Предположим, вы получаете NPE в следующей строке кода:

Какая из переменных была null ? И bar , и baz , и qux могли оказаться нулевыми. Если qux не была null , то объект, который она возвращает, мог. Потребуется потратить какое-то время на выяснение, какой из этих сценариев оказался виновником.

Выбрасывание собственного исключения для пустого значения параметра будет тут же показывать в лог-файлах, где искать проблему, потому что на нее будет указывать ваше собственное сообщение об ошибке.

Работа со строками

Я часто вижу баги там, где логике при генерации строк не удается учесть нулевые значения, в результате чего среди строк пользовательского интерфейса появляется null . Причина такого коварного поведения в том, что в Java при конкатенации строк неожиданное нулевое значение не ведет к NPE до тех пор, пока для него явно не вызываются методы. Если мы заглянем внутрь JRE, то выясним, что null-значения часто явно обрабатываются и преобразуются в null — значения. Каждый раз, когда мы используем сокращение “ + ”, генерируется StringBuilder и используется для создания объединенного значения. Это означает, что правосторонние значения присваиваний s3 и s4 по факту эквивалентны:

В обоих случаях получится строка hello null . Конкатенацию также можно выполнить при помощи s1.concat(s2) , и это, что интересно, будет вызывать NPE всякий раз, когда s2 будет null . Тем не менее метод concat не является предпочтительным несмотря на то, что справляется лучше StringBuilder при конкатенации всего нескольких строк. В связи с этим мы, как правило, будем обнаруживать null -строки лишь в UI.

Используйте класс Objects и библиотеку StringUtils

Ошибки нередко прокрадываются в базу кода, когда у нас нет ясного понимания того, что код делает или почему он это делает. Здесь важно удобство в обслуживании. Я часто сталкиваюсь с запутанным кодом создания строки. По какой-то причине люди склонны увлекаться тернарными выражениями, помещая их в другие выражения или вкладывая друг в друга. Это очень эффективно усложняет чтение и понимание простой логики.

Тернарный оператор часто используется для возвращения предустановленного значения, когда переменная содержит null . Ниже приведен пример этого, взятый прямо из нашей базы кода Columna:

Примечательно, что это не самый сложный для анализа код, но я думаю, что его можно написать гораздо лучше. В C# существует так называемый оператор объединения с неопределенным значением, относящийся к тернарному оператору, явно проверяющему значение на null . Вот пример:

Здесь левое значение оператора присваивается, если оно не null , в противном случае используется правая сторона. Несмотря на то, что в Java недостает подобного оператора, с той же целью можно использовать встроенный класс Objects , описанный выше. Метод toString(Object o, String s) класса Objects возвращает значение toString() первого объекта, если этот объект не null , в противном случае он возвращает указанное строковое значение.

Не так складно, как в примере с C#, но, на мой взгляд, намного надежнее, чем исходный пример, а значит и шанс появления ошибок при работе с кодом уже существенно меньше.

Используйте StringUtils для защиты String-методов от null

Библиотека StringUtils упрощает обработку null и пустых строк. В API это описывается как: “Защищенные от null операции со String.” Эта библиотека содержит много стандартных строковых операций со встроенными проверками на null . При ее использовании база кода станет менее громоздкой, так как вам не потребуется реализовывать весь рутинный код проверок на null , и читать условные выражения станет легче. При этом база кода также станет более единообразной, и что более важно, код станет безопаснее, так как вы уже ненароком не забудете реализовать проверки нулевых значений. В частности, такая агрессивная защита выражений конкатенации с условными инструкциями на основе метода isBlank(s) гарантирует, что в строки уже не проскользнут нежелательные “null”-значения.

Избегайте проверки оператора Null в Java

Изучите несколько стратегий,позволяющих избежать слишком знакомых шаблонных условных операторов для проверки нулевых значений в Java.

  • Автор записи

1. Обзор

Как правило, null переменные, ссылки и коллекции сложно обрабатывать в коде Java. Их не только трудно идентифицировать, но и с ними сложно иметь дело.

На самом деле, любая ошибка в работе с null не может быть идентифицирована во время компиляции и приводит к исключению NullPointerException во время выполнения.

В этом уроке мы рассмотрим необходимость проверки null в Java и различные альтернативы, которые помогут нам избежать проверок null в нашем коде.

Дальнейшее чтение:

Использование Null Away, чтобы избежать NullPointerExceptions

Примечания по безопасности пружины

Введение в шаблон нулевого объекта

2. Что Такое Исключение NullPointerException?

Согласно Javadoc для NullPointerException , он возникает, когда приложение пытается использовать null в случае, когда требуется объект, например:

  • Вызов метода экземпляра объекта null
  • Доступ или изменение поля объекта null
  • Принимая длину null , как если бы это был массив
  • Доступ или изменение слотов null , как если бы это был массив
  • Бросание null , как если бы это было Выбрасываемое значение

Давайте быстро рассмотрим несколько примеров кода Java, которые вызывают это исключение:

Здесь мы попытались вызвать вызов метода для ссылки null . Это приведет к исключению NullPointerException.

Другой распространенный пример-если мы попытаемся получить доступ к массиву null :

Это вызывает исключение NullPointerException в строке 6.

Таким образом, доступ к любому полю, методу или индексу объекта null вызывает исключение NullPointerException , как видно из приведенных выше примеров.

Распространенный способ избежать исключения NullPointerException – проверить наличие null :

В реальном мире программистам трудно определить, какие объекты могут быть null. Агрессивно безопасной стратегией было бы проверить null для каждого объекта. Это, однако, вызывает множество избыточных проверок null и делает наш код менее читаемым.

В следующих нескольких разделах мы рассмотрим некоторые альтернативы в Java, которые позволяют избежать такой избыточности.

3. Обработка null Через контракт API

Как обсуждалось в предыдущем разделе, доступ к методам или переменным объектов null вызывает исключение NullPointerException. Мы также обсуждали, что установка null проверки объекта перед доступом к нему исключает возможность исключения NullPointerException.

Однако часто существуют API, которые могут обрабатывать значения null . Например:

Вызов метода print() просто выведет “null” , но не вызовет исключения. Аналогично, process() никогда не вернет null в своем ответе. Это скорее вызывает исключение .

Таким образом, для клиентского кода, обращающегося к вышеуказанным API, нет необходимости в проверке null .

Однако такие API должны четко указывать это в своем контракте. Общим местом для API для публикации такого контракта является JavaDoc .

Это, однако, не дает четкого указания на контракт API и, таким образом, полагается на разработчиков клиентского кода для обеспечения его соответствия.

В следующем разделе мы увидим, как несколько идей и других инструментов разработки помогают разработчикам в этом.

4. Автоматизация контрактов API

4.1. Использование Статического Анализа Кода

Инструменты статического анализа кода помогают значительно улучшить качество кода. И несколько таких инструментов также позволяют разработчикам поддерживать контракт null . Одним из примеров является FindBugs .

FindBugs помогает управлять контрактом null с помощью аннотаций @Nullable и @NonNull . Мы можем использовать эти аннотации над любым методом, полем, локальной переменной или параметром. Это делает явным для клиентского кода, может ли аннотированный тип быть null или нет. Давайте рассмотрим пример:

Здесь @NonNull дает понять, что аргумент не может быть null. Если клиентский код вызывает этот метод без проверки аргумента на null, FindBugs выдаст предупреждение во время компиляции.

4.2. Использование поддержки IDE

Разработчики обычно полагаются на идеи для написания кода Java. И такие функции, как интеллектуальное завершение кода и полезные предупреждения, например, когда переменная, возможно, не была назначена, безусловно, в значительной степени помогают.

Некоторые IDE также позволяют разработчикам управлять контрактами API и тем самым устраняют необходимость в инструменте статического анализа кода. IntelliJ IDEA предоставляет аннотации @NonNull и @Nullable . Чтобы добавить поддержку этих аннотаций в IntelliJ, мы должны добавить следующую зависимость Maven:

Теперь IntelliJ выдаст предупреждение, если проверка null отсутствует, как в нашем последнем примере.

IntelliJ также предоставляет аннотацию Contract для обработки сложных контрактов API.

5. Утверждения

До сих пор мы говорили только об устранении необходимости проверки null из клиентского кода. Но это редко применимо в реальных приложениях.

Теперь давайте предположим, что мы работаем с API, который не может принимать null параметры или может возвращать null ответ, который должен быть обработан клиентом . Это приводит к необходимости проверки параметров или ответа на значение null .

Здесь мы можем использовать утверждения Java вместо традиционного условного оператора null check:

В строке 2 мы проверяем наличие параметра null . Если утверждения включены, это приведет к ошибке AssertionError.

Хотя это хороший способ утверждения предварительных условий, таких как не- null параметры, у этого подхода есть две основные проблемы :

  1. Утверждения обычно отключаются в JVM
  2. Утверждение false приводит к непроверенной ошибке, которую невозможно исправить

Следовательно, программистам не рекомендуется использовать утверждения для проверки условий. В следующих разделах мы обсудим другие способы обработки null валидаций.

6. Избегание Нулевых Проверок С Помощью Методов Кодирования

6.1. Предварительные условия

Обычно рекомендуется писать код, который рано выходит из строя. Поэтому, если API принимает несколько параметров, которые не могут быть null , лучше проверить каждый параметр, не являющийся null , в качестве предварительного условия API.

Например, давайте рассмотрим два метода – один, который рано терпит неудачу, и другой, который этого не делает:

Очевидно, что мы должны предпочесть good Accept() над bad Accept().

В качестве альтернативы мы также можем использовать предварительные условия Guava для проверки параметров API.

6.2. Использование примитивов вместо классов-оболочек

Поскольку null не является приемлемым значением для примитивов, таких как int, мы должны предпочесть их аналогам-оболочкам, таким как Integer , где это возможно.

Рассмотрим две реализации метода, который суммирует два целых числа:

Теперь давайте назовем эти API в нашем клиентском коде:

Это приведет к ошибке во время компиляции, так как нулевой не является допустимым значением для инт.

И при использовании API с классами-оболочками мы получаем NullPointerException :

Существуют и другие факторы для использования примитивов поверх оберток, как мы рассмотрели в другом учебнике, Примитивы Java по сравнению с объектами .

6.3. Пустые коллекции

Иногда нам нужно вернуть коллекцию в качестве ответа от метода. Для таких методов мы всегда должны пытаться возвращать пустую коллекцию вместо null :

Таким образом, мы избежали необходимости для нашего клиента выполнять проверку null при вызове этого метода.

7. Использование объектов

Java 7 представила новый Objects API. Этот API имеет несколько статических служебных методов, которые удаляют много избыточного кода. Давайте рассмотрим один из таких методов, requireNonNull() :

Теперь давайте протестируем метод accept() :

Таким образом, если null передается в качестве аргумента, accept() вызывает исключение NullPointerException.

Этот класс также имеет методы is Null() и NonNull () , которые могут использоваться в качестве предикатов для проверки объекта на наличие null.

8. Использование Опционально

8.1. Использование orElseThrow

Java 8 представила новый Необязательный API в языке. Это обеспечивает лучший контракт для обработки необязательных значений по сравнению с null. Давайте посмотрим, как Необязательно устраняет необходимость в null проверках:

Возвращая Необязательный, как показано выше, метод process дает понять вызывающему, что ответ может быть пустым и должен быть обработан во время компиляции.

Это, в частности, устраняет необходимость в любых проверках null в клиентском коде. Пустой ответ может быть обработан по-разному, используя декларативный стиль Необязательного API:

Кроме того, он также предоставляет лучший контракт разработчикам API, чтобы показать клиентам, что API может возвращать пустой ответ.

Хотя мы устранили необходимость проверки null на вызывающем объекте этого API, мы использовали его для возврата пустого ответа. Чтобы избежать этого, Optional предоставляет метод ofNullable , который возвращает Optional с указанным значением или empty , если значение null :

8.2. Использование опционально с коллекциями

При работе с пустыми коллекциями/| Необязательно пригодится:

Эта функция должна возвращать первый элемент списка. Функция Stream API findFirst вернет пустой Необязательный при отсутствии данных. Здесь мы использовали или , чтобы вместо этого указать значение по умолчанию.

Это позволяет нам обрабатывать либо пустые списки, либо списки, которые после того, как мы использовали метод Stream библиотеки filter , не имеют элементов для предоставления.

В качестве альтернативы мы также можем позволить клиенту решить, как обрабатывать empty , вернув Необязательно из этого метода:

Поэтому, если в результате getList пусто, этот метод вернет пустое Необязательный к клиенту.

Использование Необязательно с коллекциями позволяет нам создавать API, которые обязательно возвращают ненулевые значения, избегая, таким образом, явных проверок null на клиенте.

Здесь важно отметить, что эта реализация опирается на get List , не возвращающий null. Однако, как мы обсуждали в предыдущем разделе, часто лучше возвращать пустой список, а не null .

8.3. Комбинирование Необязательно

Когда мы начинаем делать наши функции возвращаемыми Необязательными , нам нужен способ объединить их результаты в одно значение. Давайте возьмем наш пример getList из предыдущего. Что делать, если он должен был вернуть Необязательный список или должен был быть обернут методом, который обернул null с Необязательным использованием ofNullable ?

Наш метод findFirst хочет вернуть Необязательный первый элемент Необязательного списка:

С помощью функции flatMap на Optional , возвращаемой из get Optional , мы можем распаковать результат внутреннего выражения, которое возвращает Optional . Без flatMap результатом будет Optional<Необязательная<Строка>> . Операция flatMap выполняется только в том случае, если Необязательный не пуст.

9. Библиотеки

9.1. Использование Ломбока

Lombok – отличная библиотека, которая уменьшает количество шаблонного кода в наших проектах. Он поставляется с набором аннотаций, которые заменяют общие части кода, которые мы часто пишем сами в приложениях Java, таких как геттеры , сеттеры и toString () , чтобы назвать некоторые из них.

Еще одна из его аннотаций – @NonNull. Итак, если проект уже использует Ломбок для устранения шаблонного кода, @NonNull может заменить необходимость в null проверках .

Прежде чем мы перейдем к некоторым примерам, давайте добавим зависимость Maven для Ломбока:

Теперь мы можем использовать @NonNull везде, где требуется проверка null :

Итак, мы просто аннотировали объект, для которого требовалась бы проверка null , и Lombok генерирует скомпилированный класс:

Если param равен null, этот метод вызывает исключение NullPointerException. Метод должен явно указать это в своем контракте, и клиентский код должен обработать исключение.

9.2. Использование стрингутилов

Как правило, проверка String включает проверку пустого значения в дополнение к значению null . Таким образом, общее утверждение проверки будет:

Это быстро становится излишним, если нам приходится иметь дело с большим количеством типов String . Вот где StringUtils пригодится. Прежде чем мы увидим это в действии, давайте добавим зависимость Maven для commons-lang3 :

Теперь давайте рефакторингуем приведенный выше код с помощью StringUtils:

Итак, мы заменили нашу проверку null или empty методом static utility isNotEmpty(). Этот API предлагает другие мощные служебные методы для обработки общих строковых функций.

10. Заключение

В этой статье мы рассмотрели различные причины NullPointerException и почему это трудно определить. Затем мы увидели различные способы избежать избыточности в коде вокруг проверки null с параметрами, типами возвращаемых значений и другими переменными.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *