java правильная проверка - Избегание! = Null




15 Answers

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

Иными словами, есть два случая, когда возникает нулевая проверка:

  1. Если null - действительный ответ в терминах контракта; а также

  2. Если это недействительный ответ.

(2) легко. Используйте либо assert (утверждения), либо разрешите сбой (например, NullPointerException ). Утверждения - это очень малоиспользуемая функция Java, добавленная в 1.4. Синтаксис:

assert <condition>

или же

assert <condition> : <object>

где <condition> является логическим выражением, а <object> - это объект, чей вывод метода toString() будет включен в ошибку.

Оператор assert выдает Error ( AssertionError ), если условие не соответствует действительности. По умолчанию Java игнорирует утверждения. Вы можете включить утверждения, передав опцию -ea в JVM. Вы можете включать и отключать утверждения для отдельных классов и пакетов. Это означает, что вы можете проверить код с помощью утверждений при разработке и тестировании и отключить их в рабочей среде, хотя мое тестирование показало рядом с отсутствием влияния производительности на утверждения.

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

(1) немного сложнее. Если у вас нет контроля над кодом, который вы вызываете, вы застряли. Если null является допустимым ответом, вы должны проверить его.

Однако если это код, который вы контролируете, (и это часто бывает), то это совсем другая история. Избегайте использования нулей в качестве ответа. С помощью методов, возвращающих коллекции, легко: возвращать пустые коллекции (или массивы) вместо нулей в значительной степени все время.

С не-коллекциями это может быть сложнее. Рассмотрим это как пример: если у вас есть эти интерфейсы:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

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

Альтернативное решение - никогда не возвращать null и вместо этого использовать шаблон Null Object :

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

Для сравнения:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

в

ParserFactory.getParser().findAction(someInput).doSomething();

что намного лучше, потому что это приводит к более сжатому коду.

Тем не менее, возможно, вполне применимо, чтобы метод findAction () выдавал исключение со значимым сообщением об ошибке - особенно в этом случае, когда вы полагаетесь на ввод пользователя. Было бы намного лучше, если метод findAction выбрал исключение, чем для вызывающего метода, чтобы взорвать простое исключение NullPointerException без объяснения причин.

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

Или, если вы считаете, что механизм try / catch слишком уродлив, а не Do Nothing, ваше действие по умолчанию должно обеспечивать обратную связь с пользователем.

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}
object integer

Я использую object != null чтобы избежать NullPointerException .

Есть ли хорошая альтернатива этому?

Например:

if (someobject != null) {
    someobject.doCalc();
}

Это позволяет избежать NullPointerException , когда неизвестно, является ли объект null или нет.

Обратите внимание, что принятый ответ может быть устаревшим, см. https://.com/a/2386013/12943 для более позднего подхода.




Если значения null не допускаются

Если ваш метод вызывается извне, начните с чего-то вроде этого:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

Затем, в остальной части этого метода, вы узнаете, что object не является нулевым.

Если это внутренний метод (не часть API), просто документируйте, что он не может быть нулевым, и все.

Пример:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

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

Если null разрешен

Это действительно зависит. Если вы обнаружите, что я часто делаю что-то вроде этого:

if (object == null) {
  // something
} else {
  // something else
}

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

Для меня действительно редко используется идиома « if (object != null && ... ».

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




Если неопределенные значения не разрешены:

Вы можете настроить свою среду IDE, чтобы предупредить вас о потенциальной нулевой разыменовании. Например, в Eclipse см. Настройки> Java> Компилятор> Ошибки / Предупреждения / Нулевой анализ .

Если разрешены неопределенные значения:

Если вы хотите определить новый API, где неопределенные значения имеют смысл , используйте шаблон Option (может быть знаком с функциональными языками). Он имеет следующие преимущества:

  • В API явно указано, существует или нет вход или выход.
  • Компилятор заставляет вас обрабатывать «неопределенный» случай.
  • Вариант - монада , поэтому нет необходимости в проверке верных нулей, просто используйте map / foreach / getOrElse или аналогичный комбинатор, чтобы безопасно использовать значение (example) .

Java 8 имеет встроенный Optional класс (рекомендуется); для более ранних версий существуют альтернативы библиотеки, например Optional Guava или FunctionalJava . Но, как и многие функциональные шаблоны, использование Option в Java (даже 8) приводит к довольно некоторому шаблону, который вы можете уменьшить, используя менее подробный язык JVM, например Scala или Xtend.

Если вам нужно иметь дело с API, который может возвращать значения NULL , вы не можете многое сделать на Java. У Xtend и Groovy есть оператор Элвиса ?: И нульбезопасный оператор разыменования ?. , но обратите внимание, что это возвращает null в случае нулевой ссылки, поэтому он просто «отменяет» правильную обработку нулевого значения.




С Java 8 появился новый класс java.util.Optional который, возможно, решает некоторые проблемы. Можно хотя бы сказать, что он улучшает читабельность кода, а в случае публичных API-интерфейсов упрощает работу с API-интерфейсом для клиентского разработчика.

Они работают так:

Необязательный объект для данного типа ( Fruit ) создается как возвращаемый тип метода. Он может быть пустым или содержать объект Fruit :

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

Теперь посмотрите на этот код, где мы ищем список Fruit ( fruits ) для данного экземпляра Fruit:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

Вы можете использовать оператор map() для выполнения вычисления на - или извлечения значения из - необязательного объекта. orElse() позволяет предоставить orElse() для отсутствующих значений.

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

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

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

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

Optional предлагает другие удобные методы, такие как orElse которые позволяют использовать значение по умолчанию, и ifPresent который работает с лямбда-выражениями .

Я предлагаю вам прочитать эту статью (мой основной источник для написания этого ответа), в которой проблема NullPointerException (и вообще нулевого указателя) проблематична, а также (частичное) решение, предоставленное Optional , хорошо объяснено: Java Необязательные объекты .




  • Если вы считаете, что объект не должен быть нулевым (или это ошибка), используйте assert.
  • Если ваш метод не принимает нулевые параметры, скажите это в javadoc и используйте assert.

Вы должны проверить объект! = Null, только если вы хотите обработать случай, когда объект может быть пустым ...

Есть предложение добавить новые аннотации в Java7, чтобы помочь с параметрами null / notnull: http://tech.puredanger.com/java7/#jsr308




Рамка коллекций Google предлагает хороший и элегантный способ достижения нулевой проверки.

В библиотечном классе есть метод:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

И использование (с import static ):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

Или в вашем примере:

checkNotNull(someobject).doCalc();



Вместо Null Object Pattern - который имеет свои применения - вы можете рассмотреть ситуации, когда нулевой объект является ошибкой.

Когда выбрано исключение, проверьте трассировку стека и выполните ошибку.




Нуль - это не проблема. Он является неотъемлемой частью complete набора инструментов моделирования. Программное обеспечение нацелено на моделирование сложности мира, и нуль несет его бремя. Null указывает «Нет данных» или «Неизвестно» на Java и тому подобное. Поэтому для этих целей целесообразно использовать nulls. Я не предпочитаю шаблон «Нулевой объект»; Я думаю, что он поднимает « кто будет охранять проблему опекунов ».
Если вы спросите меня, как зовут мою подругу, я скажу вам, что у меня нет подруги. На языке Java я верну null. Альтернативой было бы вывести осмысленное исключение, чтобы указать на какую-то проблему, которая не может быть (или дон-t хотят быть), и там делегировать его куда-то выше в стеке, чтобы повторить попытку или сообщить об ошибке доступа к данным пользователю.

  1. Для «неизвестного вопроса» дайте «неизвестный ответ». (Небезопасно, если это правильно с бизнес-точки зрения). Проверка аргументов для null один раз внутри метода до использования освобождает нескольких вызывающих лиц от проверки их перед вызовом.

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }
    

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

    getPhotoOfThePerson(me.getGirlfriend())
    

    И это соответствует новому API Java (с нетерпением ждущего)

    getPhotoByName(me.getGirlfriend()?.getName())
    

    Хотя довольно «нормальный бизнес-поток» не позволяет найти фотографию, хранящуюся в БД для какого-то человека, я использовал пару, как показано ниже, для некоторых других случаев

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);
    

    И не мешайте типу <alt> + <shift> + <j>(генерировать javadoc в Eclipse) и пишите три дополнительных слова для публичного API. Этого будет более чем достаточно для всех, кроме тех, кто не читает документацию.

    /**
     * @return photo or null
     */
    

    или же

    /**
     * @return photo, never null
     */
    
  2. Это довольно теоретический случай, и в большинстве случаев вам следует предпочесть Java API с нулевым безопасностью (в случае, если он будет выпущен еще через 10 лет), но NullPointerExceptionявляется подклассом a Exception. Таким образом, это форма, Throwableкоторая указывает условия, которые разумное приложение может захотеть поймать ( javadoc )! Чтобы использовать первое преимущество использования исключений и отдельный код обработки ошибок из «обычного» кода ( согласно создателям Java ), уместно, как и для меня, поймать NullPointerException.

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }
    

    Могут возникнуть вопросы:

    Q. Что, если getPhotoDataSource()возвращает null?
    О. Это деловая логика. Если мне не удастся найти фотоальбом, я покажу вам никаких фотографий. Что делать, если appContext не инициализирован? Бизнес-логика этого метода справляется с этим. Если одна и та же логика должна быть более строгой, то бросание исключения является частью бизнес-логики, а явная проверка на нуль должна использоваться (случай 3). Новый Java Null-безопасный API лучше подходит здесь , чтобы указать выборочно , что подразумевает и то , что не означает быть инициализирован , чтобы быть не в состоянии , быстро в случае ошибок программиста.

    Q. Резервный код может быть выполнен и ненужные ресурсы могут быть захвачены.
    О. Это может произойти, если getPhotoByName()попытаться открыть соединение с базой данных, создать PreparedStatementи использовать имя человека в качестве параметра SQL. Подход к неизвестному вопросу дает неизвестный ответ (случай 1). Перед захватом ресурсов метод должен проверять параметры и при необходимости возвращать «неизвестный» результат.

    Q. Этот подход имеет штраф за производительность из-за открытия закрытия попытки.
    A. Программное обеспечение должно быть легко понятным и модифицированным во-первых. Только после этого можно было подумать о производительности, и только в случае необходимости! и там, где это необходимо! ( source ) и многие другие).

    PS.Такой подход будет разумным использовать, поскольку отдельный код обработки ошибок из «обычного» принципа кода разумно использовать в некотором месте. Рассмотрим следующий пример:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }
    

    PPS. Для тех, кто быстро сжимается (и не так быстро читает документацию), я хотел бы сказать, что в своей жизни я никогда не видел исключения с нулевым указателем (NPE). Но эта возможность была специально разработана разработчиками Java, потому что NPE является подклассом Exception. У нас есть прецедент в истории Java, когда ThreadDeathэто Errorне так, потому что это на самом деле ошибка приложения, но только потому, что он не предназначен для того, чтобы быть пойманным! Сколько NPE подходит, чтобы быть Errorчем ThreadDeath! Но это не так.

  3. Проверьте «Нет данных» только в том случае, если это подразумевает бизнес-логика.

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }
    

    а также

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }
    

    Если appContext или dataSource не инициализируется необработанной рабочей средой, NullPointerException убьет текущий поток и будет обработан Thread.defaultUncaughtExceptionHandler (для определения и использования вашего любимого регистратора или другого Thread.defaultUncaughtExceptionHandler уведомления). Если не задано, ThreadGroup#uncaughtException будет печатать stacktrace для системного err. Необходимо отслеживать журнал ошибок приложений и открывать проблему Jira для каждого необработанного исключения, которое на самом деле является ошибкой приложения. Программист должен исправить ошибку где-то в файле инициализации.




Обычная «проблема» в Java действительно.

Во-первых, мои мысли об этом:

Я считаю, что плохо «есть» что-то, когда был отправлен NULL, где NULL не является допустимым значением. Если вы не выходите из метода с какой-то ошибкой, значит, в вашем методе ничего не получилось, что неверно. Тогда вы, вероятно, вернете null в этом случае, а в методе получения вы снова проверите значение null, и оно никогда не закончится, и вы получите «if! = Null» и т. Д.

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

Способ решения этой проблемы заключается в следующем:

Во-первых, я следую этому соглашению:

  1. Все общедоступные методы / API всегда проверяют свои аргументы на нуль
  2. Все частные методы не проверяют значение null, поскольку они управляются методами (просто дайте умереть с исключением nullpointer в случае, если он не был обработан выше)
  3. Единственными другими методами, которые не проверяют значение null, являются утилиты. Они публичные, но если вы их почему-то называете, вы знаете, какие параметры вы передаете. Это похоже на попытку кипятить воду в чайнике без подачи воды ...

И, наконец, в коде первая строка общедоступного метода выглядит следующим образом:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

Обратите внимание, что addParam () возвращает self, так что вы можете добавить дополнительные параметры для проверки.

Метод validate()будет выдавать флажок, ValidationExceptionесли какой-либо из параметров имеет значение null (отмеченный или не отмеченный флажок - это проблема дизайна / вкуса, но моя ValidationExceptionпроверка).

void validate() throws ValidationException;

Сообщение будет содержать следующий текст, если, например, «plans» имеет значение NULL:

« Недопустимое значение аргумента null встречается для параметра [plans] "

Как вы можете видеть, второе значение в методе addParam () (строка) необходимо для сообщения пользователя, потому что вы не можете легко обнаружить имя переменной pass-in, даже с отражением (в любом случае это не относится к этому сообщению ...).

И да, мы знаем, что за этой строкой мы больше не будем сталкиваться с нулевым значением, поэтому просто безопасно вызываем методы для этих объектов.

Таким образом, код чист, прост в обслуживании и читабельности.




В дополнение к использованию assertвы можете использовать следующее:

if (someobject == null) {
    // Handle null here then move on.
}

Это немного лучше, чем:

if (someobject != null) {
    .....
    .....



    .....
}



Гува, очень полезная базовая библиотека от Google, имеет хороший и полезный API, чтобы избежать нулей. Я нахожу Guava очень полезным.

Как поясняется в wiki:

Optional<T>является способом замены нулевой ссылки T с ненулевым значением. Необязательный может либо содержать ненулевую ссылку T (в этом случае мы говорим, что ссылка «присутствует»), либо она может содержать ничего (в этом случае мы говорим, что ссылка «отсутствует»). Он никогда не говорил «содержать null».

Использование:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5



Мне нравятся статьи из Nat Pryce. Вот ссылки:

В статьях есть также ссылка на репозиторий Git для Java Maybe Type, который я нахожу интересным, но я не думаю, что это само по себе может уменьшить разбухание кода проверки. После некоторых исследований в Интернете, я думаю ! = Нулевой код bloat может быть уменьшен главным образом путем тщательного проектирования.




Вероятно, лучшей альтернативой Java 8 или новее является использование этого Optionalкласса.

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

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

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

Пример того, как выбрасывать исключение:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7 представила Objects.requireNonNullметод, который может быть полезен, когда что-то нужно проверить для непустоты. Пример:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();



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

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

Также имейте в виду, что шаблон нулевого объекта будет голоден, если использовать его без осторожности. Для этого - экземпляр NullObject должен делиться между владельцами, а не быть экземпляром unigue для каждого из них.

Кроме того, я бы не рекомендовал использовать этот шаблон, где тип должен быть представлением примитивного типа, как математические объекты, которые не являются скалярами: векторами, матрицами, комплексными числами и объектами POD (простые старые данные), которые предназначены для хранения состояния в виде встроенных типов Java. В последнем случае вы в конечном итоге вызываете методы getter с произвольными результатами. Например, что должен вернуть метод NullPerson.getName ()?

Стоит рассмотреть такие случаи, чтобы избежать абсурдных результатов.




Это самая распространенная ошибка для большинства разработчиков.

У нас есть несколько способов справиться с этим.

Подход 1:

org.apache.commons.lang.Validate //using apache framework

notNull (объект объекта, сообщение String)

Подход 2:

if(someObject!=null){ // simply checking against null
}

Подход 3:

@isNull @Nullable  // using annotation based validation

Подход 4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}



Related