Избегать! = Нулевые утверждения

4017

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

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

Например, я часто использую:

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

Это проверка для а NullPointerExceptionдля someobjectобъекта в приведенном выше фрагменте кода.

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

Goran Martinic
источник
120
@Shervin Поощрение нулевых значений делает код менее понятным и менее надежным.
Том Хотин -
44
Были предложены операторы Элвиса, но, похоже, их не будет в Java 7 . Очень плохо, ?. ?: и? [] невероятно экономят время.
Скотт
72
Не использовать ноль лучше, чем большинство других предложений здесь. Бросайте исключения, не возвращайте и не разрешайте нули. Кстати - ключевое слово assert бесполезно, потому что по умолчанию оно отключено. Используйте всегда включенный механизм отказа
ianpojman
13
Это одна из причин, почему я сейчас использую Scala. В Scala все не обнуляемо. Если вы хотите разрешить передавать или возвращать «ничего», то вам нужно явно использовать Option [T] вместо просто аргумента T als или возвращаемого типа.
Thekwasti
11
@thSoft Действительно, удивительный Optionтип Scala омрачен нежеланием языка контролировать или запрещать null. Я упомянул об этом в hackernews, получил отрицательное голосование и сказал, что "в любом случае в Scala никто не использует null". Угадайте что, я уже нахожу нули в написанном сотрудником Scala. Да, они «делают это неправильно» и должны быть осведомлены об этом, но факт остается фактом: система типов языка должна защищать меня от этого, а это не так :(
Андрес Ф.

Ответы:

2644

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

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

  1. Где ноль является действительным ответом с точки зрения договора; а также

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

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

assert <condition>

или

assert <condition> : <object>

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

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

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

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

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

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

public interface Action {
  void doSomething();
}

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

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

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

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);
        }
    }
}
клем
источник
631
Я не согласен с вашими заявлениями о действии DO_NOTHING. Если метод find action не может найти действие, то возвращение null является правильным решением. Вы «нашли» в своем коде действие, которое на самом деле не найдено, что нарушает принцип метода, чтобы найти полезное действие.
MetroidFan2002
266
Я согласен с тем, что null используется в Java, особенно в списках. Так много apis было бы лучше, если бы они возвращали пустой список / массив / коллекцию вместо null. Много раз, ноль используется, где вместо этого должно быть выброшено исключение. Исключение должно быть выдано, если синтаксический анализатор не может разобрать.
Лапли Андерсон
92
Последний пример здесь, IIRC, шаблон проектирования Null Object.
Стивен Эверс
62
@Cshah (и MetroidFan2002). Просто вставьте это в контракт, и тогда станет ясно, что не найденное возвращенное действие ничего не сделает. Если это важная информация для вызывающей стороны, предоставьте способ обнаружить, что это было не найденное действие (т. Е. Предоставить метод для проверки, был ли результат объектом DO_NOTHING). В качестве альтернативы, если обычно должно быть найдено действие, вам все равно не следует возвращать ноль, а вместо этого выдавать исключение, конкретно указывающее на это условие - это все равно приводит к лучшему коду. При желании предоставьте отдельный метод, возвращающий логическое значение, чтобы проверить, существует ли действие.
Кевин Брок
35
Краткий не равен качеству кода. Мне жаль, что ты так думаешь. Ваш код скрывает ситуации, в которых ошибка будет выгодна.
gshauger
636

Если вы используете (или планируете использовать) 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 .

Luca Molteni
источник
120
@NotNull, @NullableИ другое nullness аннотации является частью JSR 305 . Вы также можете использовать их для обнаружения потенциальных проблем с такими инструментами, как FindBugs .
Яцек С
32
Я нахожу странным, что интерфейсы @NotNull& @Nullableживут в пакете com.sun.istack.internal. (Думаю, я связываю com.sun с предупреждениями об использовании проприетарного API.)
Jonik
20
Переносимость кода обнуляется с помощью jetbrains. Я бы подумал дважды (квадрат), прежде чем связывать его с идеальным уровнем. Как Jacek S сказал, что они являются частью JSR в любом случае, как мне показалось, кстати, JSR303.
Ява Ка Малышка
10
Я действительно не думаю, что использование собственного компилятора является жизнеспособным решением этой проблемы.
Shivan Dragon
64
Хорошая вещь о аннотациях, которые @NotNullи @Nullableявляются в том , что они прекрасно деградировать , когда исходный код строятся с помощью системы , которая не понимает их. Таким образом, фактически аргумент, что код не является переносимым, может быть недопустимым - если вы используете систему, которая поддерживает и понимает эти аннотации, вы получаете дополнительное преимущество более строгой проверки ошибок, в противном случае вы получаете меньше ее, но ваш код все равно должен сборка в порядке, и качество вашей работающей программы ТО ЖЕ, потому что эти аннотации не были применены во время выполнения в любом случае. Кроме того, все компиляторы пользовательские ;-)
атп
322

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

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

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 && ...".

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

myplacedk
источник
94
Какой смысл в создании IllegalArgumentException? Я думаю, что NullPointerException будет более понятным, и это также будет сгенерировано, если вы сами не выполняете проверку на ноль. Я бы либо использовал assert, либо вообще ничего.
Аксель
23
Маловероятно, что любое другое значение, кроме нуля, является приемлемым. Вы можете иметь IllegalArgumentException, OutOfRageException и т. Д. И т. Д. Иногда это имеет смысл. В других случаях вы в конечном итоге создаете много классов исключений, которые не добавляют никакого значения, тогда вы просто используете IllegalArgumentException. Не имеет смысла иметь одно исключение для нулевого ввода и другое для всего остального.
myplacedk
7
Да, я согласен с принципом fast-fail, но в приведенном выше примере значение не передается, а является объектом, для которого должен быть вызван метод. Таким образом, он терпит неудачу одинаково быстро, и добавление нулевой проверки только для того, чтобы выдать исключение, которое в любом случае было бы выдано в то же время и в другом месте, кажется, не облегчает отладку.
Аксель
8
Дыра в безопасности? JDK полон такого кода. Если вы не хотите, чтобы пользователь видел трассировки стека, просто отключите их. Никто не подразумевал, что поведение не задокументировано. MySQL написан на C, где разыменование нулевых указателей является неопределенным поведением, совсем не похоже на создание исключения.
августа
8
throw new IllegalArgumentException("object==null")
Турбьёрн Равн Андерсен
238

Ничего себе, я почти не хочу добавлять другой ответ, когда у нас есть 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 было представлено в рамках проекта Coin. Синтаксис немного отличается от приведенного выше примера, но это то же самое понятие.


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

erickson
источник
20
Я думаю, что это неправильно. Должен быть способ указать, что данная переменная ВСЕГДА не равна нулю.
Турбьерн Равн Андерсен
5
Обновление: предложение не внесет Java7. Смотрите blogs.sun.com/darcy/entry/project_coin_final_five .
Борис Терзич
9
Интересная идея, но выбор синтаксиса абсурден; Я не хочу кодовую базу, полную вопросительных знаков, прикрепленных к каждому суставу.
Роб
7
Этот оператор существует в Groovy , поэтому те, кто хочет его использовать, по-прежнему имеют его в качестве опции.
Чт
8
Это самая гениальная идея, которую я видел. Это должно быть добавлено к каждому разумному языку синтаксиса C. Я бы предпочел «прикрепить вопросительные знаки» везде, чем пролистывать скриншоты строк или уворачиваться от «охранных статей» весь день.
Виктор
196

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

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

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

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

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

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

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

thSoft
источник
22
В самом деле, шаблон Option является потрясающим. Существуют некоторые Java-эквиваленты. Guava содержит ограниченную версию этого варианта, которая называется Optional, которая оставляет большинство функциональных возможностей. В Хаскеле этот шаблон называется «Возможно».
Бен Харди
9
Дополнительный класс будет доступен на Java 8
Пьер Генри
1
... и у него (пока) нет ни карты, ни flatMap: download.java.net/jdk8/docs/api/java/util/Optional.html
thSoft
3
Необязательный шаблон ничего не решает; вместо одного потенциально нулевого объекта, теперь у вас есть два.
Boann
1
@Boann, если использовать его осторожно, вы решите все свои проблемы с NPE. Если нет, то я думаю, что есть проблема «использования».
Луи Ф.
189

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

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

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

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

Вы можете избежать этого, если вы сравните свои Stringвот так:

if ( "bar".equals(foo) ) {
 // ...
}
echox
источник
42
Я согласен - только в такой ситуации. Я не могу вынести программистов, которые подняли это на следующий ненужный уровень и пишут if (null! = MyVar) ... просто выглядит для меня безобразно и не имеет смысла!
Алекс Уорден
20
Это частный пример, возможно, наиболее часто используемый, из общей хорошей практики: если вы это знаете, всегда делайте <object that you know that is not null>.equals(<object that might be null>);. Он работает для других методов, кроме тех equals, которые известны контракту, и эти методы могут обрабатывать nullпараметры.
Стеф
9
Это первый пример условий Йоды, который я видел,
Эрин Драммонд,
6
Исключения NullPointerException вызываются по причине. Они выбрасываются, потому что объект равен нулю там, где его не должно быть. Это работа программистов, чтобы исправить это, а не скрывать проблему.
Оливер Уоткинс
1
Try-Catch-DoNothing скрывает проблему, это правильная практика для обхода отсутствия сахара в языке.
echox
167

С 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 .

Pierre Henry
источник
11
У гуавы Google есть дополнительное значение для Java 6+.
Брэдли Готфрид
13
Это очень важно подчеркнуть , что использование необязательного только с ifPresent () не не добавляет много значения выше нормальной проверки нулевой. Основное значение заключается в том, что это монада, которую можно использовать в цепочках функций map / flapMap, которая дает результаты, аналогичные оператору Elvis в Groovy, упомянутом в другом месте. Однако даже без такого использования я нахожу синтаксис orElse / orElseThrow также очень полезным.
Корнел Массон,
Этот блог имеет хорошую запись на Факультативным winterbe.com/posts/2015/03/15/avoid-null-checks-in-java
JohnC
4
Почему люди склонны делать это if(optional.isPresent()){ optional.get(); }вместоoptional.ifPresent(o -> { ...})
Сатьендра Кумар
1
Таким образом, помимо договорных намеков на API, речь идет об обслуживании функциональных программистов, которым бесконечно нравятся методы создания цепочек.
раздавить
125

В зависимости от того, какие объекты вы проверяете, вы можете использовать некоторые классы в общих фондах apache, такие как: apache commons lang и apache commons collection

Пример:

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 также имеет те же функции в своем пакете, см. Библиотеку (spring-2.4.6.jar)

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

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");
javamonkey79
источник
5
Также вы можете использовать более общую версию от Apache Commons, весьма полезную в начале методов для проверки параметров, которые я нахожу. Validate.notNull (объект, "объект не должен быть нулевым"); commons.apache.org/lang/apidocs/org/apache/commons/lang/…
monojohnny
@monojohnny Validate использует операторы Assert в ?. Я спрашиваю об этом, потому что Assert может быть активирован / деактивирован на JVM, и он предлагает не использовать в производстве.
Курапика
Не думаю - я считаю, что он просто генерирует RuntimeException, если проверка не удалась
monojohnny
96
  • Если вы считаете, что объект не должен быть нулевым (или это ошибка), используйте assert.
  • Если ваш метод не принимает нулевые параметры, скажите это в javadoc и используйте assert.

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

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

pgras
источник
91

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

Алекс Уорден
источник
2
лучше использовать утверждения, то есть Contract.notNull (abc, «abc должен быть ненулевым, не удалось загрузить во время xyz?»); - это более компактный способ, чем выполнение if (abc! = null) {throw new RuntimeException ...}
ianpojman
76

Каркас коллекций 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();
user2427
источник
71
ммм, в чем разница? p.getAge () выдаст тот же NPE с меньшими издержками и более четкой трассировкой стека. Что мне не хватает?
MySomic
15
Лучше добавить исключение IllegalArgumentException ("e == null") в вашем примере, поскольку оно четко указывает на то, что это исключение, предназначенное для программиста (вместе с достаточным количеством информации, чтобы фактически позволить сопровождающему идентифицировать проблему). Исключения NullPointerException должны быть зарезервированы для JVM, поскольку тогда это четко указывает на то, что это было непреднамеренно (и обычно это происходит где-то, что трудно определить)
Торбьерн Равн Андерсен
6
Теперь это часть Google Guava.
Стивен Бенитес
32
Пахнет как чрезмерная инженерия для меня. Просто позвольте JVM бросить NPE и не загромождайте свой код этим мусором.
Алекс Уорден
5
Мне это нравится, и я открываю большинство методов и конструкторов с явными проверками аргументов; если есть ошибка, методы всегда терпят неудачу в первых нескольких строках, и я знаю оскорбительную ссылку, не находя что-то вродеgetThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");
Кори Кендалл
75

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

a.f(b); <-> b.f(a);

Если вы знаете, что b никогда не может быть нулевым, вы можете просто поменять его местами. Это наиболее полезно для равных: foo.equals("bar");лучше не делать "bar".equals(foo);.

Johannes Schaub - litb
источник
2
Но тогда вы должны предположить equals(может быть любой метод) будет обрабатывать ноль правильно. На самом деле все, что это делает - передает ответственность кому-то другому (или другому методу).
Supericy
4
@Supericy В основном, да, но equals(или любой другой метод) должен проверить в nullлюбом случае. Или прямо заявите, что это не так.
Анджело Фукс
74

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

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

Джим Нельсон
источник
17
Проблема в том, что обычно вы теряете контекст, так как исключение NullPointerException не указывает, какая переменная была нулевой, и у вас может быть несколько «.» - операций в строке. Использование "if (foo == null) throw new RuntimeException (" foo == null ")" позволяет вам явно указать ЧТО было не так, давая вашей трассировке стека гораздо большую ценность для тех, кто должен это исправить.
Торбьерн Равн Андерсен
1
С Андерсеном - я бы хотел, чтобы система исключений Java включала в себя имя переменной, над которой работаем, чтобы NullPointerExceptions указывал не только на строку, в которой произошло исключение, но также и имя переменной. Это должно работать просто отлично в необоснованном программном обеспечении.
fwielstra
Мне пока не удалось заставить его работать, но это призвано решить именно эту проблему.
MatrixFrog
В чем проблема? Просто поймайте NPE на соответствующем уровне, где достаточно контекста, сбросьте информацию контекста и сбросьте исключение ... это так просто с Java.
user1050755
3
У меня был профессор, который проповедовал против цепочки вызовов методов. Его теория состояла в том, что вы должны опасаться цепочек вызовов, которые были длиннее двух методов. Я не знаю, является ли это жестким правилом, но оно определенно устраняет большинство проблем со следами стека NPE.
RustyTheBoyRobot
74

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

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

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

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

    getPhotoOfThePerson(me.getGirlfriend())

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

    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это подкласс класса Exception. Таким образом, это форма, Throwableкоторая указывает условия, которые разумное приложение может захотеть поймать ( javadoc )! Использовать первое наибольшее преимущество исключений и отделить код обработки ошибок от «обычного» кода ( по словам создателей Java ), уместно, на мой взгляд, поймать NullPointerException.

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

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

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

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

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

    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 (для вас, чтобы определить и использовать ваш любимый регистратор или другой механизм уведомлений). Если не установлено, ThreadGroup # uncaughtException выведет трассировку стека в системную ошибку. Необходимо следить за журналом ошибок приложения и открывать проблему Jira для каждого необработанного исключения, которое фактически является ошибкой приложения. Программист должен исправить ошибку где-то в инициализации.

Михаил Адамович
источник
6
Ловить NullPointerExceptionи возвращать nullужасно для отладки. В конце концов, вы все равно получите NPE, и очень трудно понять, что изначально было нулевым.
artbristol 14.09.13
23
Я бы понизил голос, если бы у меня была репутация. Не только ноль не является необходимым, это дыра в системе типов. Присвоение дерева списку является ошибкой типа, поскольку деревья не являются значениями типа List; по той же логике, присвоение нуля должно быть ошибкой типа, потому что нуль не является значением типа Object или какого-либо полезного типа в этом отношении. Даже человек, который изобрел ноль, считает это своей «ошибкой в ​​миллиард долларов». Понятие «значение, которое может быть значением типа T ИЛИ ничего» является его собственным типом и должно быть представлено как таковое (например, Maybe <T> или Optional <T>).
Довал
2
Что касается «Может быть <T> или Необязательного <T>», вам все еще нужно написать код, if (maybeNull.hasValue()) {...}так в чем же разница if (maybeNull != null)) {...}?
Михаил Адамович
1
Что касается «перехвата NullPointerException и возврата нуля, это ужасно для отладки. В любом случае, вы в конечном итоге получите NPE позже, и действительно сложно понять, что изначально было нулем». Я полностью согласен! В этих случаях вы должны написать дюжину операторов if или бросить NPE, если бизнес-логика подразумевает данные на месте, или использовать нуль-безопасный оператор из новой Java. Но бывают случаи, когда мне все равно, какой именно шаг дает мне ноль. Например, вычисление некоторых значений для пользователя непосредственно перед отображением на экране, когда вы ожидаете, что данные могут отсутствовать.
Михаил Адамович
3
@MykhayloAdamovych: Преимущество Maybe<T>или Optional<T>нет в случае, когда ваш Tможет быть нулевым, но в случае, когда он никогда не должен быть нулевым. Если у вас есть тип, который явно означает «это значение может быть нулевым, используйте его с осторожностью», и вы используете и возвращаете такой тип последовательно, тогда всякий раз, когда вы видите старый Tкод в своем коде, вы можете предположить, что он никогда не будет нулевым. (Конечно, это было бы намного полезнее, если бы оно могло быть реализовано компилятором.)
cHao
71

В Java 7 появился новый java.util.Objectsслужебный класс, в котором есть requireNonNull()метод. Все, что он делает - это бросает a, NullPointerExceptionесли его аргумент равен нулю, но он немного очищает код. Пример:

Objects.requireNonNull(someObject);
someObject.doCalc();

Этот метод наиболее полезен для проверки непосредственно перед присваиванием в конструкторе, где при каждом его использовании можно сохранить три строки кода:

Parent(Child child) {
   if (child == null) {
      throw new NullPointerException("child");
   }
   this.child = child;
}

становится

Parent(Child child) {
   this.child = Objects.requireNonNull(child, "child");
}
Raedwald
источник
9
На самом деле, ваш пример представляет собой раздувание кода: первая строка излишня, потому что NPE будет брошен во вторую строку. ;-)
user1050755
1
Правда. Лучшим примером было бы, если бы вторая строка doCalc(someObject).
Стюарт Маркс
Зависит. Если вы являетесь автором doCalc (), я бы предложил поместить проверку в тело этого метода (если это возможно). И тогда вы, скорее всего, будете вызывать someObject.someMethod (), где снова нет необходимости проверять нулевое значение. :-)
user1050755
Что ж, если вы не являетесь автором doCalc(), и он не бросает NPE сразу, когда ему дано значение NULL, вам нужно будет проверить NULL и бросить NPE самостоятельно. Вот для чего Objects.requireNonNull().
Стюарт Маркс
7
Это не просто раздувание кода. Лучше проверить заранее, чем на полпути через метод, который вызывает побочные эффекты или использует время / пространство.
Роб Грант
51

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

  • В Objective-C вы можете сделать эквивалентный вызов метода nil, и абсолютно ничего не произойдет. Это делает ненужными большинство нулевых проверок, но может значительно усложнить диагностику ошибок.
  • В Nice , Java-производном языке, есть две версии всех типов: потенциально нулевая версия и ненулевая версия. Вы можете вызывать методы только для ненулевых типов. Потенциально нулевые типы могут быть преобразованы в ненулевые типы с помощью явной проверки на нулевые. Это позволяет намного легче узнать, где необходимы нулевые проверки, а где нет.
Michael Borgwardt
источник
Вау ... Самый правильный ответ и его опровергают, где справедливость? В Java null всегда является допустимым значением. Это следствие «Все есть объект» - «Ноль есть все» (конечно, мы игнорируем примитивы здесь, но вы понимаете). Лично я одобряю подход, принятый Nice, хотя мы могли бы сделать так, чтобы методы могли вызываться для типов, допускающих обнуляемость, и повышать NPE до проверенных исключений. Это должно быть сделано с помощью переключателя компилятора, поскольку он сломает весь существующий код :(
CurtainDog
4
Я не знаком с Nice, но Kotlin реализует ту же идею, имеет встроенные типы Nullable и NULL, встроенные в систему типов языка. Намного более сжатый, чем шаблон Optionals или NULL.
mtsahakis
38

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

Сначала мои мысли по этому поводу:

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

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

Я решаю эту проблему так:

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

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

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

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

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

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

void validate() throws ValidationException;

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

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

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

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

Таким образом, код является чистым, легко обслуживаемым и читаемым.

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

Задавая этот вопрос, вы обнаружите, что вас могут заинтересовать стратегии обработки ошибок. Архитектор вашей команды должен решить, как работать с ошибками. Есть несколько способов сделать это:

  1. Позвольте Исключениям проходить сквозь - поймайте их в «главном цикле» или в какой-либо другой управляющей подпрограмме.

    • проверять наличие ошибок и обрабатывать их соответствующим образом

Конечно, взгляните также на Аспектно-ориентированное программирование - у них есть аккуратные способы вставки if( o == null ) handleNull()в ваш байт-код.

xtofl
источник
35

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

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

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

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



    .....
}
fastcodejava
источник
2
Мм, почему это? Пожалуйста, не чувствуйте никакой защиты, я просто хотел бы узнать больше о Java :)
Матиас Мейд
9
@Mudu Как правило, я предпочитаю, чтобы выражение в выражении if было более «положительным», чем «отрицательным». Так что, если бы я увидел, if (!something) { x(); } else { y(); }я был бы склонен рефакторинг как if (something) { y(); } else { x(); }(хотя можно утверждать, что != nullэто более позитивный вариант ...). Но что еще более важно, важная часть кода не обернута внутри {}s, и у вас на один уровень меньше отступов для большей части метода. Я не знаю, было ли это рассуждением fastcodejava, но это было бы моим.
MatrixFrog
1
Это то, что я склонен делать также. Содержит код в чистоте на мой взгляд.
Корай Тугай
34

Просто никогда не используйте нуль. Не позволяй это.

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

Как только я принял эту практику, я заметил, что проблемы, похоже, решаются сами собой. Вы поймете вещи намного раньше в процессе разработки просто случайно и поймете, что у вас есть слабое место ... и что еще более важно ... это помогает инкапсулировать проблемы разных модулей, разные модули могут "доверять" друг другу и больше не засорять код сif = null else конструкциями!

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

class C {
    private final MyType mustBeSet;
    public C(MyType mything) {
       mustBeSet=Contract.notNull(mything);
    }
   private String name = "<unknown>";
   public void setName(String s) {
      name = Contract.notNull(s);
   }
}


class Contract {
    public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}

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

iangreen
источник
почему это будет опущено? по моему опыту, это намного превосходит другие подходы, хотелось бы знать, почему нет
ianpojman
Я согласен, что этот подход предотвращает проблемы, связанные с нулями, вместо того, чтобы их исправлять путем повсюду проверок нуля кода.
ChrisBlom
7
Проблема этого подхода заключается в том, что если имя никогда не устанавливается, оно имеет значение «<unknown>», которое ведет себя как установленное значение. Теперь допустим, что мне нужно проверить, не задано ли имя (неизвестно), мне нужно сравнить строки со специальным значением «<unknown>».
Стив Куо
1
Правильно, Стив. Я часто использую это значение как константу, например, public static final String UNSET = "__ unset" ... private String field = UNSET ... then private boolean isSet () {return UNSET.equals (field); }
ianpojman
IMHO, это реализация Null Object Pattern с собственной реализацией Optional (Contract). Как он ведет себя на классе персистентности? Я не вижу применимости в этом случае.
Курапика
31

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

Как объяснено в вики:

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

Применение:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
Мурат
источник
@CodyGuldner Правильно, Коди. Я предоставил соответствующую цитату из ссылки, чтобы дать больше контекста.
Мурат Дерья Озен
25

Это очень распространенная проблема для каждого разработчика Java. Так что в Java 8 есть официальная поддержка для решения этих проблем без загроможденного кода.

Java 8 представила java.util.Optional<T>. Это контейнер, который может содержать или не содержать ненулевое значение. Java 8 предоставила более безопасный способ обработки объекта, значение которого может быть нулевым в некоторых случаях. Он вдохновлен идеями Хаскелла и Скалы .

В двух словах, в классе Optional есть методы для явного обращения со случаями, когда значение присутствует или отсутствует. Однако преимущество по сравнению с нулевыми ссылками заключается в том, что дополнительный класс <T> заставляет задуматься о случае, когда значение отсутствует. Как следствие, вы можете предотвратить непреднамеренные исключения нулевого указателя.

В приведенном выше примере у нас есть фабрика обслуживания на дому, которая возвращает дескриптор для нескольких бытовых приборов, доступных в доме. Но эти услуги могут быть или не быть доступными / функциональными; это означает, что это может привести к NullPointerException. Вместо добавления нуляif условия перед использованием какого-либо сервиса, давайте поместим его в Optional <Service>.

ОБРАЩАЯСЬ К ВАРИАНТУ <T>

Давайте рассмотрим метод, чтобы получить ссылку на сервис с фабрики. Вместо того, чтобы возвращать сервисную ссылку, оберните ее необязательно. Это позволяет пользователю API знать, что возвращенный сервис может или не может быть доступен / функционировать, использовать для защиты

public Optional<Service> getRefrigertorControl() {
      Service s = new  RefrigeratorService();
       //...
      return Optional.ofNullable(s);
   }

Как видите, Optional.ofNullable()обеспечивает простой способ обернуть ссылку. Есть и другие способы получить ссылку на Optional, либо Optional.empty()&Optional.of() . Один для возврата пустого объекта вместо перенастройки null, а другой для переноса ненулевого объекта соответственно.

ТАК КАК ТОЧНО ЭТО ПОМОГАЕТ ИЗБЕЖАТЬ ПУСТОЙ ПРОВЕРКИ?

После того, как вы обернули ссылочный объект, Optional предоставляет много полезных методов для вызова методов с обернутой ссылкой без NPE.

Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);

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

@FunctionalInterface
public interface Consumer<T>

Представляет операцию, которая принимает один входной аргумент и не возвращает результата. В отличие от большинства других функциональных интерфейсов, ожидается, что потребитель будет работать через побочные эффекты. Это так чисто и легко понять. В приведенном выше примере кода HomeService.switchOn(Service)вызывается, если необязательная ссылка удержания не равна нулю.

Мы очень часто используем троичный оператор для проверки нулевого условия и возвращаем альтернативное значение или значение по умолчанию. Необязательно предоставляет другой способ обработки того же условия без проверки нулевого значения. Optional.orElse (defaultObj) возвращает defaultObj, если Optional имеет нулевое значение. Давайте использовать это в нашем примере кода:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

Теперь HomeServices.get () делает то же самое, но лучше. Проверяет, инициализирован ли сервис или нет. Если это так, верните то же самое или создайте новый новый сервис. Необязательный <T> .orElse (T) помогает вернуть значение по умолчанию.

Наконец, вот наш NPE, а также нулевой код без проверки:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}

public static void main(String[] args) {
    /* Get Home Services handle */
    Optional<HomeServices> homeServices = HomeServices.get();
    if(homeServices != null) {
        Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
        refrigertorControl.ifPresent(HomeServices::switchItOn);
    }
}

public static void switchItOn(Service s){
         //...
    }
}

Полный пост - NPE, а также пустой код без проверки ... Правда? ,

Йогеш Деватрадж
источник
В приведенном выше коде есть нулевая проверка - if(homeServices != null) {которую можно изменить на homeServices.ifPresent(h -> //action);
КришПрабакар
23

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

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

Mr Palo
источник
Майкл Фезерс написал короткий и интересный текст о подходах, подобных тому, который вы упомянули: manuelp.newsblur.com/site/424
ivan.aguirre
21

Я пробовал, NullObjectPatternно для меня это не всегда лучший путь. Бывают случаи, когда «никаких действий» не уместно.

NullPointerExceptionэто исключение времени выполнения, которое означает, что это ошибка разработчиков, и, имея достаточный опыт, она точно скажет, где находится ошибка.

Теперь к ответу:

Постарайтесь сделать все свои атрибуты и их методы доступа максимально конфиденциальными или вообще не показывать их клиентам. Конечно, вы можете иметь значения аргумента в конструкторе, но, уменьшая область действия, вы не позволяете клиентскому классу передавать недопустимое значение. Если вам нужно изменить значения, вы всегда можете создать новое object. Вы проверяете значения в конструкторе только один раз, а в остальных методах вы можете быть почти уверены, что значения не равны NULL.

Конечно, опыт - лучший способ понять и применить это предложение.

Байт!

OscarRyz
источник
19

Вероятно, лучшая альтернатива для 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();
Raghu K Nair
источник
17

Могу я ответить на это более широко!

Мы обычно сталкиваемся с этой проблемой, когда методы получают параметры так, как мы этого не ожидали (неправильный вызов метода - ошибка программиста). Например: вы ожидаете получить объект, вместо этого вы получите ноль. Вы ожидаете получить строку как минимум с одним символом, вместо этого вы получите пустую строку ...

Так что нет разницы между:

if(object == null){
   //you called my method badly!

}

или

if(str.length() == 0){
   //you called my method badly again!
}

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

Как уже упоминалось в некоторых других ответах, чтобы избежать вышеуказанных проблем, вы можете следовать схеме проектирования по контракту . Пожалуйста, смотрите http://en.wikipedia.org/wiki/Design_by_contract .

Чтобы реализовать этот шаблон в java, вы можете использовать основные java-аннотации, такие как javax.annotation.NotNull, или использовать более сложные библиотеки, такие как Hibernate Validator .

Просто образец:

getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

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

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

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}
Алиреза Фаттахи
источник
javaxэто, по определению, не «ядро Java».
Томас
17

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

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

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

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

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

оборота luke1985
источник
Решение с "hasBackground ()" имеет один недостаток - оно не поточно-ориентированное. Если вам нужно вызвать два метода вместо одного, вам нужно синхронизировать всю последовательность в многопоточной среде.
pkalinow
@pkalinow Вы сделали надуманный пример только для того, чтобы указать, что у этого решения есть недостаток. Если код не предназначен для запуска в многопоточном приложении, то недостатка нет. Я мог бы поместить в вас, вероятно, 90% вашего кода, который не является потокобезопасным. Мы не говорим здесь об этом аспекте кода, мы говорим о шаблоне дизайна. А многопоточность - это отдельная тема.
luke1985
Конечно, в однопоточном приложении это не проблема. Я дал этот комментарий, потому что иногда это проблема.
pkalinow
@pkalinow Если вы внимательно изучите эту тему, то обнаружите, что шаблон проектирования Null Object не решит проблемы многопоточности. Так что это не имеет значения. И если честно, я нашел места, где этот шаблон хорошо бы подходил, так что мой первоначальный ответ немного неправильный, на самом деле.
luke1985
16
  1. Никогда не инициализируйте переменные нулем.
  2. Если (1) невозможно, инициализируйте все коллекции и массивы пустыми коллекциями / массивами.

Делая это в вашем собственном коде, вы можете избежать! = Пустых проверок.

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

// Bad
ArrayList<String> lemmings;
String[] names;

void checkLemmings() {
    if (lemmings != null) for(lemming: lemmings) {
        // do something
    }
}



// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};

void checkLemmings() {
    for(lemming: lemmings) {
        // do something
    }
}

В этом есть небольшие накладные расходы, но оно того стоит для более чистого кода и меньшего количества исключений NullPointerException.

Стюарт Аксон
источник
3
+1 С этим я согласен. Вы никогда не должны возвращать половину инициализированных объектов. Код, связанный с Jaxb, и код bean-кода notiroius для этого. Это плохая практика. Все коллекции должны быть инициализированы, и все объекты должны существовать без (в идеале) нулевых ссылок. Рассмотрим объект, в котором есть коллекция. Проверка того, что объект не является нулевым, что коллекция не является нулевым и что коллекция не содержит нулевых объектов, является необоснованной и глупой.
ggb667
15

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

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

Подход 1:

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

notNull (Объектный объект, Строковое сообщение)

Подход 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;
}
Сириш Ярлагадда
источник
Объявление. 4. Это не очень полезно - когда вы проверяете, равен ли указатель нулю, вы, вероятно, захотите вызвать метод для него. Вызов метода с нулевым значением дает вам то же поведение - NullPointerException.
pkalinow
11
public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}
tltester
источник
2
Что не так с этим методом, я думаю, что @tltester просто хочет дать значение по умолчанию, если оно пустое, что имеет смысл.
Сойер
1
Существует такой метод а в Apache Commons-лана: ObjectUtils.defaultIfNull(). Есть еще один общий:, ObjectUtils.firstNonNull()который можно использовать для реализации стратегии firstNonNull(bestChoice, secondBest, thirdBest, fallBack);
унизления