nullable java что это
Учимся избегать null-значений в современном Java. Часть 2
Возвращайте Optional вместо null
Хоть в предыдущей статье я и убеждал вас в уместности null для некоторых сценариев, в этой я буду напротив говорить, что по возможности его все же лучше избегать. При каждом добавлении в базу кода нового метода, способного вернуть пустое значение, вам следует стараться использовать вместо null тип Optional. Этот тип был введен, чтобы разработчики могли указывать на возможность возвращения методом пустого значения, что ранее было недоступно (либо требовало аннотаций, о которых я расскажу ниже). Существует два вида объектов Optional : содержащие значение и без него. Первые изначально создаются со значением, вторые же просто являются статическими одиночками. Структурно типичный случай их использования выглядит так:
Чтобы увидеть, почему сигнатура с Optional предпочтительней, представьте следующий метод:
Поскольку смысл использовать Optional есть только в случае возможного возврата пустого значения, значит метод такое значение вернуть может. Поэтому при каждом возвращении Optional методом вы знаете, что есть вероятность получения пустого значения и его нужно должным образом обработать.
Со значением Optional соответствующий код будет выглядеть так:
Однако если бы вместо этого у нас был метод, возвращающий активную госпитализацию, то предпочтительнее было бы использовать Optional :
Можно ли использовать Optional для параметров?
Никогда не делайте следующее, равно как не предоставляйте null в качестве аргумента параметра с Optional значением:
Если вы сделаете нечто подобное, то также получите NPE (исключение нулевого указателя).
Содействуйте IDE с помощью аннотаций Nullable/Nonnull
Вот один из примеров подобной сигнатуры метода:
Используйте Objects.requireNonNull()
Здесь у вас может возникнуть вопрос, заключается ли причина избегания нулевого параметра в том, что он может ненамеренно привести к NPE. Не будет ли лучше выбросить NPE намеренно до того, как это произойдет?
Скорее всего, это одно и то же, и с позиции пользователя выглядеть это будет тоже одинаково — в виде появления ошибки. А вот при диагностировании причины сбоя в дальнейшем, последний вариант окажется гораздо предпочтительнее. Проблема с NPE в том, что они не сообщают, какая переменная оказалась null, а просто предоставляют номер строки. В строке, где для объектов вызывается несколько методов, может быть сложно определить, в каком из них причина.
Предположим, вы получаете NPE в следующей строке кода:
Выбрасывание собственного исключения для пустого значения параметра будет тут же показывать в лог-файлах, где искать проблему, потому что на нее будет указывать ваше собственное сообщение об ошибке.
Работа со строками
Используйте класс Objects и библиотеку StringUtils
Ошибки нередко прокрадываются в базу кода, когда у нас нет ясного понимания того, что код делает или почему он это делает. Здесь важно удобство в обслуживании. Я часто сталкиваюсь с запутанным кодом создания строки. По какой-то причине люди склонны увлекаться тернарными выражениями, помещая их в другие выражения или вкладывая друг в друга. Это очень эффективно усложняет чтение и понимание простой логики.
Не так складно, как в примере с C#, но, на мой взгляд, намного надежнее, чем исходный пример, а значит и шанс появления ошибок при работе с кодом уже существенно меньше.
Используйте StringUtils для защиты String-методов от null
Заключение
Вкратце содержание рассмотренной серии статей можно выразить так:
Учимся избегать null-значений в современном Java. Часть 2
Oct 13, 2020 · 8 min read
Возвращайте Optional вместо null
Хоть в предыдущей статье я и убеждал вас в уместности null для некоторых сценариев, в этой я буду напротив говорить, что по возможности его все же лучше избегать. При каждом добавлении в базу кода нового метода, способного вернуть пустое значение, вам следует стараться использовать вместо null тип Optional. Этот тип был введен, чтобы разработчики могли указывать на возможность возвращения методом пустого значения, что ранее было недоступно (либо требовало аннотаций, о которых я расскажу ниже). Существует два вида объектов Optional : содержащие значение и без него. Первые изначально создаются со значением, вторые же просто являются статическими одиночками. Структурно типичный случай их использования выглядит так:
Чтобы увидеть, почему сигнатура с Optional предпочтительней, представьте следующий метод:
Поскольку смысл использовать Optional есть только в случае возможного возврата пустого значения, значит метод такое значение вернуть может. Поэтому при каждом возвращении Optional методом вы знаете, что есть вероятность получения пустого значения и его нужно должным образом обработать.
Со значением Optional соответствующий код будет выглядеть так:
Однако если бы вместо этого у нас был метод, возвращающий активную госпитализацию, то предпочтительнее было бы использовать Optional :
Можно ли использовать Optional для параметров?
Никогда не делайте следующее, равно как не предоставляйте null в качестве аргумента параметра с Optional значением:
Если вы сделаете нечто подобное, то также получите NPE (исключение нулевого указателя).
Содействуйте IDE с помощью аннотаций Nullable/Nonnull
Вот один из примеров подобной сигнатуры метода:
Используйте Objects.requireNonNull()
Здесь у вас может возникнуть вопрос, заключается ли причина избегания нулевого параметра в том, что он может ненамеренно привести к NPE. Не будет ли лучше выбросить NPE намеренно до того, как это произойдет?
Скорее всего, это одно и то же, и с позиции пользователя выглядеть это будет тоже одинаково — в виде появления ошибки. А вот при диагностировании причины сбоя в дальнейшем, последний вариант окажется гораздо предпочтительнее. Проблема с NPE в том, что они не сообщают, какая переменная оказалась null, а просто предоставляют номер строки. В строке, где для объектов вызывается несколько методов, может быть сложно определить, в каком из них причина.
Предположим, вы получаете NPE в следующей строке кода:
Выбрасывание собственного исключения для пустого значения параметра будет тут же показывать в лог-файлах, где искать проблему, потому что на нее будет указывать ваше собственное сообщение об ошибке.
Работа со строками
Используйте класс Objects и библиотеку StringUtils
Ошибки нередко прокрадываются в базу кода, когда у нас нет ясного понимания того, что код делает или почему он это делает. Здесь важно удобство в обслуживании. Я часто сталкиваюсь с запутанным кодом создания строки. По какой-то причине люди склонны увлекаться тернарными выражениями, помещая их в другие выражения или вкладывая друг в друга. Это очень эффективно усложняет чтение и понимание простой логики.
Не так складно, как в примере с C#, но, на мой взгляд, намного надежнее, чем исходный пример, а значит и шанс появления ошибок при работе с кодом уже существенно меньше.
Используйте StringUtils для защиты String-методов от null
Заключение
Вкратце содержание рассмотренной серии статей можно выразить так:
Тестирование аннотаций @NonNull/@Nullable
Описанная ниже задача не была новаторской или чертовски полезной, компания в которой я работаю не получит за нее прибыль, а я премию.
Но эта задача была, а значит ее пришлось решить.
Intro
В статье вы часто будете встречать слово Lombok, прошу хейтеров не торопиться с выводами.
Я не собираюсь «топить» за Lombok или его отсутствие, я как Геральт Сапковского, стараюсь хранить нейтралитет, и могу спокойно и без дрожи в веке читать код как с Lombok, так и без оного.
Но на текущем проекте упомянутая библиотека присутствует, и что-то подсказывает мне, что наш проект такой не единственный.
Так вот.
Последнее время в java безусловно есть тренд к анноташкам. Во славу концепции fast fail часто параметры методов аннотируются аннотацией @NonNull (чтоб если что, как зашло — так и вышло пало).
Вариантов импорта для данной(или похожей по идеологии аннотации) довольно много, мы же, как наверняка уже стало понятно, остановимся на версии
Если вы используете эту(или подобную) аннотацию, то имеете некоторый контракт, который необходимо проверить тестом и любой статический анализатор кода любезно это подскажет(Sonar точно подсказывает).
Протестировать эту аннотацию unit-тестом достаточно просто, проблема в том что такие тесты будут размножаться в вашем проекте со скоростью кроликов по весне, а кролики, как известно, нарушают принцип DRY.
В статье мы напишем небольшой тестовый фреймворк, для тестирования контракта аннотаций @NonNull(и для того чтоб Sonar не светил вам в глаз противным красным светом).
P.S На написания названия меня вдохновила песня группы PowerWolf, которая заиграла(ей богу) когда я писал название(в оригинале название звучит более позитивно)
Основная часть
Изначально мы тестировали аннотацию как-то так:
вызывали метод и подавали null в качестве параметра, аннотированного аннотацией @NonNull.
Получали NPE и оставались довольны(Sonar тоже радовался).
Потом стали делать то же самое, но с более модным assertThrow который работает через Supplier(мы же любим лямбды):
Стильно. Модно. Молодежно.
Казалось бы можно и закончить, аннотации протестированы, чего же боле?
Проблема (не то чтобы проблема, но все же) данного способа тестирования «всплыла» когда в один прекрасный день я написал тест на метод, он благополучно отработал, а потом я заметил, что аннотации @NonNull на параметре нет.
Оно и понятно: вы вызываете тестовый метод, при этом не описываете поведение моковых классов, через when()/then(). Исполняющий поток благополучно заходит внутрь метода, где то внутри ловит NPE, на незамоканном (или замоканном, но без when()/then()) объекте, и падает, впрочем с NPE, как вы и предупреждали, а значит тест зеленый
Получается что тестируем мы в таком случае уже не аннотацию, а непонятно что. При правильной работе теста мы не должны были вообще зайти вглубь метода(свалившись на пороге).
У @NonNull аннотации Lombok есть одна особенность: если мы падаем с NPE на аннотации, в ошибку записывается имя параметра.
На это мы и завяжемся, после того как упадем с NPE, дополнительно будем проверять текст stacktrace, вот так:
Все бы вроде ничего, сейчас мы действительно проверяем то что надо, но проблема «кроликов» не решена.
Хотелось бы иметь некий инструмент, которому можно было бы сказать, например так:
а он бы сам пошел и просканировал все публичные методы указанного класса и проверил все их @NonNull параметры тестом.
Вы скажете, доставай рефлексию, и проверяй, есть ли на методе @NonNull и если есть пуляй в него null.
Все бы ничего, да RetentionPolicy не тот.
У всех аннотаций есть параметр RetentionPolicy, который может быть 3 типов: SOURCE, CLASS и RUNTIME, так вот у Lombok, по умолчанию RetentionPolicy.SOURCE, а это значит что в Runtime этой аннотации не видно и через reflection вы ее не найдете.
В нашем проекте аннотируются все параметры публичных методов(не считая примитивов), если подразумевается что параметр не может быть null, если подразумевается обратное — то параметр аннотируется спринговой @Nullable. На это можно завязаться, мы будем искать все публичные методы, и все параметр в них, не помеченные @Nullable и не являющиеся примитивами.
Подразумеваем, что для всех остальных случаев, на параметрах должна стоять аннотация @NonNull.
Для удобства по возможности будем расскидывать логику по приватным методам, для начала получим все публичные методы:
где METHOD_FILTER обычный предикат, в котором мы говорим что:
Если параметры есть, нам надо понять, аннотированы ли они @NonNull(точнее должны ли быть, согласно
Для этого сделаем мапку и положим в нее наши параметры по очередности следования в методе, а напротив них положим флаг, который будет говорить должна быть над параметром аннотация @NonNull или нет:
эта мапка пригодится нам чтобы потом вызывать метод и передавать ему null во все параметры с аннотацией @NonNull поочередно, а не только в первый попавшийся.
Параметр nonNullAnnotationCount считает сколько параметров в методе должно быть аннотировано @NonNull, по нему будет определено число интераций вызова каждого метода.
Кстати если аннотаций @NonNull нет(параметры есть, но все примитивные либо @Nullable), то и говорить не о чем:
Имеем на руках карту параметров. Знаем, сколько раз вызывать метод и в какие позиции пулять null, дело за малым (как я наивно полагал не разобравшись), нужно создавать instance класса и вызывать у них методы.
Проблемы начинаются когда понимаешь, насколько разные бывают instance: это может быть приватный класс, это может быть класс с одним дефолтным конструктором, с одним конструктором с параметрами, с таким и таким конструктором, абстрактный класс, интерфейс(со своими default методами, которые тоже public и которые тоже надо тестировать).
А когда мы соорудили-таки правдами и неправдами instance, нужно передать в метод invoke параметры и тут тоже раздолье: как создать instance финального класса? а Enum? а примитива? а массива примитивов(который тоже объект и тоже может быть аннотирован).
Ну давайте по порядку.
Первый случай это класс с одним приватным конструктором:
тут все просто вызываем у нашего метода invoke, передаем ему clazz который пришел из вне в тест и массив параметров, в котором уже заряжен null на первую позицию с флагом на аннотацию @NonNull(помните, выше мы создали карту @NonNull-ов)мы начинаем бежать в цикле и создавать массив параметров, поочередно меняя позицию null параметра, и обнуляя флаг перед вызовом метода, чтобы в следующей интерации другой параметр стал null.
В коде это выглядит так:
С первым вариантом инстанцирования разобрались.
Дальше интерфейсы, нельзя взять и создать instance интерфейса(у него даже конструктора нет).
Поэтому с интерфейсом это будет так:
createInstanceByDynamicProxy позволяет нам создать instance на класс, если он реализует хотя бы один интерфейс, либо сам является интерфейсом
а внутри он какой-то такой:
Следующий instance(мой любимый) это абстрактный класс. И тут Dynamic proxy нам уже не поможет, так как если абстрактный класс и реализует какой то интерфейс, то это явно не тот тип какой нам бы хотелось. И просто так взять и создать newInstance() у абстрактного класса мы не можем. Тут нам на помощь придет CGLIB, спринговая либа, которая создает прокси на основе наследования, но вот беда, целевой класс должен иметь default (без параметров) конструктор
Вариант для инстанцирования абстрактного класса будет такой:
makeErrorMessage() который встречался уже в примерах кода, роняет тест, если мы вызывали метод с аннотированным @NonNull параметром передав null и он не упал, значит тест не отработал, надо падать.
Для маппинга параметров у нас один общий метод, который умеет мэппировать и мокировать как параметры конструктора, так и метода, выглядит он так:
Обратите внимание на создание Enum(вишенка на торте), вообщем нельзя просто так взять и создать Enum.
Здесь для финальных параметров свой маппинг, для нефинальных свой, а далее просто по тексту (кода).
Ну и после того как мы создали параметры для конструктора и для метода формируем наш instance:
Мы уже точно знаем, что раз мы дошли до этого этапа кода, значит у нас есть минимум один конструктор, чтобы создать instance мы можем взять любой, поэтому берем первый попавшийся, смотрим, есть ли у него параметры в конструкторе и если нет то вызываем вот так:
ну а если есть то так:
Это та логика которая происходит в методе createAndInvoke() который вы видели чуть выше.
Полная версия тестового класса под спойлером, заливать на git я не стал, так как писал на рабочем проекте, но по сути это всего лишь один класс, который можно отнаследовать в ваших тестах и использовать.
Заключение
Данный код работает и тестирует аннотации в реальном проекте, на данный момент возможен только один вариант, когда все сказанное можно заколлапсить.
Объявить в классе ломбоковский setter (если найдется специалист, который ставит сеттер не в Pojo-классе, хотя чего только не бывает) и при этом поле на котором объявят сеттер будет не финальное.
Тогда фреймворк любезно скажет что мол есть публичный метод, а у него есть параметр на котором нет аннотации @NonNull, решение простое: объявить setter явно и аннотировать его параметр, исходя из контекста логики @NonNull/@Nullable.
Учтите, что если вы хотите как я, завязаться на имя параметра метода в своих тестах (или чем то еще), в Runtime по умолчанию недоступны имена переменных в методах, вы найдете там arg[0] и arg[1] и т.д.
Для включения отображения имен методов в Runtime используйте плагин Maven-а:
Использование @Nullable в Java
У меня есть вопрос относительно использования аннотации @Nullable в Java.
До сих пор так хорошо, но как насчет использования @Nullable для параметров метода? Является ли это хорошей практикой или код станет еще более подробным (если используется с final ), и преимущество может отсутствовать, поскольку вы не всегда знаете, какие аргументы будут переданы вызову функции? Также, каково ваше мнение об использовании @Nullable с помощью методов setter?
Использование связано с ситуацией при работе в компании, а не с небольшим проектом (например, домашним заданием).
Из-за присущей сложности анализ потока лучше всего выполняется в небольших кусках. Анализ одного метода за один раз может быть выполнен с хорошей производительностью инструмента, тогда как анализ всей системы выходит за рамки для компилятора Eclipse Java. Преимущество : анализ выполняется быстро и может выполняться постепенно, чтобы компилятор мог предупреждать вас непосредственно по мере ввода. вниз: анализ не может “видеть”, какие значения (нулевые или ненулевые) текут между методами (в качестве параметров и возвращаемых значений).
Применение:
Эта ссылка объясняет, какую аннотацию использовать там.
Использование 2
Getters/Setters : Да, это возможно. Проект Lombok (http://projectlombok.org/index.html) определяет аннотации для генерации геттеров/сеттеров и многое другое.
Будет генерировать getter для имени (не setter, поскольку он является окончательным) и getter/setter для возраста. Он также генерирует equals, hashCode, toString и construtor, инициализируя необходимые поля (имя). Добавление @AllArgsConstructor будет генерировать конструктор, инициализирующий оба поля.
Существуют и другие аннотации и параметры, позволяющие вам контролировать права доступа (если ваш геттер будет защищен или открыт), имена (getName или имя?) и т.д. И есть еще. Например, мне очень нравятся методы расширения.
Ломбок очень прост в использовании. Просто загрузите банку и используйте аннотации, тогда геттер/сеттеры могут использоваться в вашем коде без фактического указания. Более того, IDE, например Netbeans, поддерживает это, так что вы видите геттер/сеттер в завершении кода, навигации и т.д. Аннотации используются только во время компиляции не во время выполнения, поэтому вы не распространяете ломбок с вашей банкой.
NotNull: это поддерживается findbugs и IdeaJ IDE, возможно, другие
Для собственных небольших проектов никто не должен использовать это. Но при создании библиотек для других это может помочь пользователям API писать надежные приложения. ИМО
Как я писал в комментарии: см. использование аннотации с нулевым значением для хорошего ответа на этот конкретный вопрос.
Использование @Nullable позволяет пользователю и компилятору знать, что это нормально, чтобы разрешить нулевое значение в качестве этого параметра.
Я считаю, что это более прямой способ дать пользователю возможность взглянуть на ваш API, чтобы знать, что передача null не приведет к созданию поведения NPE или undefined.
Для setter, если это поле будет использоваться во многих местах, было бы неплохо посмотреть на него setter и обнаружить, что null обрабатывается.
Говоря, я думаю, что он должен использоваться только для API или библиотек, использование их в вашем собственном коде создало бы слишком много бесполезного кода.
@Nullable and @NotNull
@Nullable and @NotNull annotations let you check nullability of a variable, parameter, or return value. They help you control contracts throughout method hierarchies, and if IntelliJ IDEA spots that the contract is being violated, it will report the detected problem, and will point to the code where NullPointerException may occur.
For example, if you create a method where a parameter has the @NotNull annotation, and then call this method with a parameter that potentially can be null, IntelliJ IDEA will highlight the problem on the fly.
@Nullable
The @Nullable annotation helps you detect:
Method calls that can return null
Variables (fields, local variables, and parameters), that can be null
Methods with the @Nullable annotation in the parent method can have either @Nullable or @NotNull annotations in the child class method.
The @Nullable annotation of the parameter in the parent method requires the @Nullable annotation in the child class method parameter.
@NotNull
The @NotNull annotation is, actually, an explicit contract declaring that:
A method should not return null
Variables (fields, local variables, and parameters) cannot hold a null value
IntelliJ IDEA warns you if these contracts are violated.
The @NotNull annotation of the parent method requires the @NotNull annotation for the child class method.
Methods with the @NotNull annotation of the parameter in the parent method can have either @Nullable or @NotNull annotations (or none of them) in the child class method parameter.
If @NotNull has the _TYPE_USE_ target, it’s applied to the array element type, not to the array type itself. To annotate the array type with the TYPE_USE annotation, use the byte @NotNull [] bytes syntax.