Избегать! = Нулевые заявления - PullRequest
3805 голосов
/ 07 ноября 2008

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

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

Например:

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

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

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

Ответы [ 62 ]

2533 голосов
/ 07 ноября 2008

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

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

  1. Где ноль - действительный ответ по условиям контракта; и

  2. Где это не правильный ответ.

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

assert <condition>

или

assert <condition> : <object>

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

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

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

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

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

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

public interface Action {
  void doSomething();
}

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

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

Альтернативное решение - никогда не возвращать нуль и вместо этого использовать шаблон Нулевой объект :

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 слишком уродлив, вместо «Ничего не делать», ваше действие по умолчанию должно обеспечивать обратную связь с пользователем.

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);
        }
    }
}
578 голосов
/ 05 марта 2010

Если вы используете (или планируете использовать) Java IDE, например JetBrains IntelliJ IDEA , Eclipse или Netbeans или инструмент, подобный findbugs, тогда вы можете использовать аннотации для решения этой проблемы.

По сути, у вас есть @Nullable и @NotNull.

Вы можете использовать в методе и параметрах, как это:

@NotNull public static String helloWorld() {
    return "Hello World";
}

или

@Nullable public static String helloWorld() {
    return "Hello World";
}

Второй пример не скомпилируется (в IntelliJ IDEA).

Когда вы используете первую функцию helloWorld() в другом фрагменте кода:

public static void main(String[] args)
{
    String result = helloWorld();
    if(result != null) {
        System.out.println(result);
    }
}

Теперь компилятор IntelliJ IDEA скажет вам, что проверка бесполезна, поскольку функция helloWorld() никогда не вернет null.

Использование параметра

void someMethod(@NotNull someParameter) { }

если вы напишите что-то вроде:

someMethod(null);

Это не скомпилируется.

Последний пример с использованием @Nullable

@Nullable iWantToDestroyEverything() { return null; }

Делаем это

iWantToDestroyEverything().something();

И вы можете быть уверены, что этого не произойдет. :)

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

В IntelliJ IDEA 10.5 и далее добавлена ​​поддержка любых других @Nullable @NotNull реализаций.

См. Сообщение в блоге Более гибкие и настраиваемые аннотации @ Nullable / @ NotNull .

303 голосов
/ 07 ноября 2008

Если нулевые значения не разрешены

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

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

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

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

Пример:

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

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

Если разрешен ноль

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

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

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


На самом деле я редко использую идиому "if (object != null && ...".

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

227 голосов
/ 17 января 2009

Ничего себе, я почти не хочу добавлять другой ответ, когда у нас есть 57 различных способов рекомендовать NullObject pattern, но я думаю, что некоторые люди, интересующиеся этим вопросом, могут захотеть узнать, что на столе есть предложение для Java 7 для добавления "безопасной обработки нулем" - упрощенный синтаксис для логики if-not-equal-null.

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

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

?. означает только отмену ссылки на левый идентификатор, если он не равен нулю, в противном случае остальная часть выражения будет оцениваться как null. Некоторым людям, таким как член Java Posse Дик Уолл и избиратели в Devoxx , действительно нравится это предложение, но есть и оппозиция на том основании, что оно будет стимулировать более широкое использование null в качестве дозорного значения.


Обновление: Официальное предложение для нуль-безопасного оператора в Java 7 было отправлено в Project Coin. Синтаксис немного отличается от пример выше, но это то же самое понятие.


Обновление: Предложение нулевого безопасного оператора не попало в Project Coin. Таким образом, вы не увидите этот синтаксис в Java 7.

187 голосов
/ 14 января 2010

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

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

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

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

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

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

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

174 голосов
/ 09 ноября 2008

Только для этой ситуации -

Не проверять, равна ли переменная нулю, прежде чем вызывать метод equals (пример сравнения строк ниже):

if ( foo.equals("bar") ) {
 // ...
}

приведет к NullPointerException, если foo не существует.

Вы можете избежать этого, если вы сравните свои String s следующим образом:

if ( "bar".equals(foo) ) {
 // ...
}
154 голосов
/ 25 апреля 2013

С 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() позволяет вам предоставить запасной вариант для пропущенных значений.

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

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

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

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

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

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

121 голосов
/ 07 ноября 2008

В зависимости от того, какие объекты вы проверяете, вы можете использовать некоторые классы в общих достояниях Apache, такие как: Apache Commons Lang и Apache Commons Collections

Пример:

String foo;
...
if( StringUtils.isBlank( foo ) ) {
   ///do something
}

или (в зависимости от того, что вам нужно проверить):

String foo;
...
if( StringUtils.isEmpty( foo ) ) {
   ///do something
}

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

Ниже приведен пример того, как вы можете использовать null vallidation в JAVA, когда вы включаете библиотеку apache (commons-lang-2.4.jar)

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);
}

И если вы используете Spring, Spring также имеет те же функции в своем пакете, см. Library (spring-2.4.6.jar)

Пример использования этого статического classf из пружины (org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");
93 голосов
/ 07 ноября 2008
  • Если вы считаете, что объект не должен быть нулевым (или это ошибка), используйте assert.
  • Если ваш метод не принимает нулевые параметры, скажите это в javadoc и используйте assert.

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

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

88 голосов
/ 23 мая 2011

Я фанат кода "fast fast". Спросите себя - делаете ли вы что-то полезное в случае, когда параметр имеет значение null? Если у вас нет четкого ответа на то, что ваш код должен делать в этом случае ... Т.е. Во-первых, оно никогда не должно быть нулевым, а затем игнорировать его и разрешать исключение NullPointerException. Вызывающий код будет иметь такой же смысл для NPE, как и IllegalArgumentException, но разработчику будет легче отлаживать и понимать, что пошло не так, если выбрасывается NPE, а не ваш код пытается выполнить какое-то другое непредвиденное непредвиденное обстоятельство логика - что в конечном итоге приводит к сбою приложения в любом случае.

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