Уникання! = Нульові заяви


4014

Я object != nullбагато використовую, щоб уникнути NullPointerException.

Чи є добра альтернатива цьому?

Наприклад, я часто використовую:

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

Це перевірка для а NullPointerExceptionдля someobjectоб'єкта в наведеному вище фрагменті коду.

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


120
@Shervin Заохочення нулів робить код менш зрозумілим і менш надійним.
Том Хотін - тайклін

44
Оператори Elvis були запропоновані, але, схоже, цього не буде в Java 7 . Шкода, ?. ?: і? [] - неймовірна економія часу.
Скотт

72
Якщо не використовувати Null, то це найкраще, ніж більшість інших пропозицій. Викидайте винятки, не повертайте та не дозволяйте нулів. BTW - ключове слово «стверджувати» є марним, оскільки воно за замовчуванням вимкнено. Використовуйте завжди включений механізм відмов
ianpojman

13
Це одна з причин, чому я зараз використовую Scala. У Scala все не зводиться до нуля. Якщо ви хочете дозволити передати або повернути "нічого", вам доведеться експліцитно використовувати Option [T] замість просто аргументу Ts або повернення типу.
Теквасті

11
@thSoft Дійсно, приголомшливий Optionтип Скали омріяний небажанням мови контролювати чи забороняти null. Я згадав про це на хакерських новинах, і став недоступним і сказав, що "у Scala ніхто не використовує" null ". Здогадайтесь що, я вже знаходжу нулі у написаній колегою Scala. Так, вони "роблять це неправильно", і про це треба навчити, але факт залишається фактом, що система типів мови повинна захищати мене від цього, і це не так :(
Andres F.

Відповіді:


2642

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

Інакше кажучи, є два випадки, коли виникає перевірка нуля:

  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);
}

де Парсер приймає необмежену інформацію користувача і знаходить щось робити, можливо, якщо ви реалізуєте інтерфейс командного рядка для чогось. Тепер ви можете укласти договір про те, що він повертає недійсним, якщо немає відповідних дій. Це призводить до перевірки нуля, про яку ви говорите.

Альтернативне рішення - ніколи не повертати 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());
}

Або якщо ви вважаєте, що механізм "пробувати / ловити" занадто некрасиво, а не робити нічого, ваші дії за замовчуванням не повинні надавати відгуку користувачу.

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
Я погоджуюся з тим, що в Java використовується нуль, особливо зі списками. Тому багато apis буде краще, якщо вони повернуть порожній список / масив / колекцію замість null. Багато разів null використовується там, де замість нього слід кинути виняток. Виняток слід кинути, якщо аналізатор не може розібратися.
Лаплі Андерсон

92
Останній приклад тут - IIRC, модель дизайну Null Object.
Стівен Еверс

62
@Cshah (і MetroidFan2002). Просто, покладіть це на договір, і тоді зрозуміло, що не знайдене повернене дійство нічого не зробить. Якщо це важлива інформація для абонента, то наведіть спосіб виявити, що це була не знайдена дія (тобто надайте метод перевірити, чи був результат об’єктом DO_NOTHING). Крім того, якщо зазвичай слід знайти дію, тоді ви все одно не повинні повертати null, а замість цього викинути виняток, що конкретно вказує на цю умову - це все одно призводить до кращого коду. За бажанням надайте окремий метод, що повертає булевий, щоб перевірити, чи існує дія.
Кевін Брок

35
Короткий не відповідає коду якості. Мені шкода, що ти так вважаєш. Ваш код приховує ситуації, коли помилка була б вигідною.
gshauger

634

Якщо ви використовуєте (або плануєте використовувати) 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 .


119
@NotNull, @NullableІ інше nullness анотації є частиною JSR 305 . Ви також можете використовувати їх для виявлення потенційних проблем з такими інструментами, як FindBugs .
Jacek S

32
Мені дивно дратує те, що @NotNull& @Nullableінтерфейси живуть у пакеті com.sun.istack.internal. (Напевно, я пов'язую com.sun з попередженнями про використання фірмового API.)
Jonik

20
Переносність коду стає недійсною з jetbrains.Я подумав би двічі (квадрат), перш ніж прив’язатись до рівня ide.Like Jacek S сказав, що вони є частиною JSR будь-яким способом, який я, до речі, вважав JSR303.
Java Ka Baby

10
Я дійсно не вважаю, що використання спеціального компілятора є життєздатним рішенням цієї проблеми.
Дракон Шиван

64
Хороша річ у анотаціях, які є @NotNullі @Nullableв тому, що вони добре деградують, коли вихідний код побудований системою, яка їх не розуміє. Отже, фактично аргумент про те, що код не є портативним, може бути недійсним - якщо ви використовуєте систему, яка підтримує та розуміє ці анотації, ви отримуєте додаткову перевагу більш жорсткої перевірки помилок, інакше ви отримуєте її менше, але ваш код все одно повинен складайте чудово, і якість вашої запущеної програми - ТЕБЕ, оскільки ці анотації не застосовувалися під час виконання. Крім того, всі компілятори під замовлення ;-)
amn

321

Якщо нульові значення заборонені

Якщо ваш метод викликається зовні, починайте щось із цього:

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

Можливо, буде простіше навести приклади, якщо ви покажете приклади, де ви зазвичай використовуєте ідіому.


94
Який сенс кинути IllegalArgumentException? Я думаю, що NullPointerException буде чіткішим, і це також буде кинуто, якщо ви самі не зробите нульову перевірку. Я б або використовував ствердження, або взагалі нічого.
Аксель

23
Навряд чи прийнято будь-яке інше значення, ніж нульове. Ви можете мати IllegalArgumentException, OutOfRageException тощо. Іноді це має сенс. В іншому випадку ви створюєте безліч класів винятків, які не додають ніякого значення, тоді ви просто використовуєте IllegalArgumentException. Немає сенсу мати один виняток для нульового введення, а інший - для всього іншого.
myplacedk

7
Так, я погоджуюся з принципом невдалої швидкості, але у наведеному вище прикладі значення не передається, а є об'єктом, на якому повинен бути викликаний метод. Таким чином, він не вдається однаково швидко, і додавання нульової перевірки просто для викидання виключення, яке все одно буде кинуто одночасно і місце, схоже, не спрощує налагодження.
Аксель

8
Захищений отвір? JDK повний такого коду. Якщо ви не хочете, щоб користувач бачив стеки, тоді просто вимкніть їх. Ніхто не припускав, що поведінка не документально підтверджена. MySQL написаний на мові C, де перенаправлення нульових покажчиків є невизначеною поведінкою, не що інше, як кидання винятку.
fgb

8
throw new IllegalArgumentException("object==null")
Thorbjørn Ravn Andersen

238

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

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

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

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


Оновлення: офіційну пропозицію по нульовому безпечного оператору в Java 7 було представлено в рамках проекту Coin. Синтаксис трохи інший, ніж приклад вище, але це те саме поняття.


Оновлення: пропозиція оператора, що не враховує нуль, не внесла його в проектну монету. Отже, ви не побачите цього синтаксису в Java 7.


20
Я думаю, що це неправильно. Повинен бути спосіб вказати, що дана змінна ЗАВЖДИ ненулева.
Thorbjørn Ravn Andersen

5
Оновлення: пропозиція не буде робити Java7. Дивіться blogs.sun.com/darcy/entry/project_coin_final_five .
Борис Терзич

9
Цікава ідея, але вибір синтаксису є абсурдним; Я не хочу, щоб кодова база, повна запитань, закріплена у кожному стику.
Роб

7
Цей оператор існує в Groovy , тому ті, хто хоче ним скористатися, все ще мають це як варіант.
Muhd

8
Це найгеніальніша ідея, яку я бачив. Його слід додавати до кожної розумної мови синтаксису С. Я б краще "скріплювати знаки запитань" скрізь, ніж прокручувати скрізь рядки або ухилятися від "охоронних пропозицій" цілий день.
Віктор

196

Якщо невизначені значення заборонені:

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

Якщо невизначені значення дозволені:

Якщо ви хочете визначити новий API, де невизначені значення мають сенс , скористайтеся Option Pattern (можливо, це знайоме з функціональних мов). Він має такі переваги:

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

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

Якщо вам доведеться мати справу з API, який може повернути нулі , ви не можете зробити багато чого в Java. У Xtend і Groovy є оператор Elvis ?: і оператор з обмеженням нуля ?. , але зауважте, що це повертає null у разі нульової посилання, тому воно просто "відкладає" правильне поводження з null.


22
Дійсно, модель Варіантів є приголомшливою. Існують деякі еквіваленти Java. Guava містить обмежену версію цього під назвою "Необов’язковий", яка залишає безліч функціональних речей. У Хаскеллі цей зразок називається Можливо.
Бен Харді

9
Факультативний клас буде доступний у Java 8
П'єр Генрі

1
... і у нього (ще) немає ні карти, ні flatMap: download.java.net/jdk8/docs/api/java/util/Otional.html
thSoft

3
Факультативний шаблон нічого не вирішує; замість одного потенційно нульового об’єкта, тепер у вас є два.
Боан

1
@Boann, якщо ви використовуєте обережно, ви вирішите всі ваші проблеми NPE. Якщо ні, то, мабуть, є проблема "використання".
Луї Ф.

189

Тільки для цієї ситуації -

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

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

призведе до, NullPointerExceptionякщо fooйого немає.

Ви можете цього уникнути, якщо порівнювати свій Strings таким чином:

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

42
Я згоден - лише в тій ситуації. Я не витримую програмістів, які підняли це на наступний непотрібний рівень, і пишуть, якщо (null! = MyVar) ... просто виглядає некрасиво для мене і не служить ніякій меті!
Алекс Worden

20
Це конкретний приклад, мабуть, найбільш використовуваний із загальної доброї практики: якщо ви це знаєте, завжди робіть <object that you know that is not null>.equals(<object that might be null>);. Він працює для інших методів, крім того, equalsякщо ви знаєте договір, і ці методи можуть обробляти nullпараметри.
Стеф

9
Це перший приклад, який я бачив із умов Yoda, який насправді має сенс
Ерін Драммонд

6
NullPointerExceptions кидаються не просто так. Їх кидають, тому що об’єкт є нульовим там, де він не повинен бути. Виправити це потрібно програмістам, а не приховувати проблему.
Олівер Уоткінс

1
Try-Catch-DoNothing приховує проблему, це дійсна практика, щоб обійти відсутність цукру в мові.
відлуння

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 Необов’язкові об’єкти .


11
Гуава Google має додаткові можливості для Java 6+.
Бредлі Готфрід

13
Це дуже важливо підкреслити , що використання необов'язкового тільки з ifPresent () НЕ НЕ додає багато значення вище нормальної перевірки нульової. Основна цінність полягає в тому, що це монада, яку можна використовувати у функціональних ланцюгах map / flapMap, яка досягає результатів, аналогічних оператору Елвіса в Groovy, згаданому в інших місцях. Навіть без цього використання, я вважаю синтаксис orElse / orElseThrow також дуже корисним.
Корнел Массон

Цей блог має хороший запис у додаткові winterbe.com/posts/2015/03/15/avoid-null-checks-in-java
JohnC

4
Чому люди мають тенденцію робити це if(optional.isPresent()){ optional.get(); }замістьoptional.ifPresent(o -> { ...})
Satyendra Kumar

1
Отже, крім контрактних підказок API, мова йде про задоволення функціональних програмістів, які нескінченно користуються методами ланцюга.
розчавити

125

Залежно від того, які саме об'єкти ви перевіряєте, ви зможете використовувати деякі класи класів у апаш-вічі, такі як: apache commons lang та apache commons collection

Приклад:

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

або (залежно від того, що потрібно перевірити):

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

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

Наведемо приклад того, як ви можете використовувати нульову валідацію в 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)

Приклад використання цього статичного classf з весни (org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");

5
Також ви можете використовувати більш загальну версію від Apache Commons, досить корисну на початку методів перевірки параметів, які я знаходжу. Validate.notNull (об'єкт, "об'єкт не повинен бути нульовим"); commons.apache.org/lang/apidocs/org/apache/commons/lang/…
monojohnny

@monojohnny чи Validate використовує заяви твердження у ?. Я прошу це, оскільки Assert може бути активовано / деактивовано на JVM, і пропоную не використовувати у виробництві.
Курапіка

Не думайте так - я вважаю, що це просто кидає RuntimeException, якщо перевірка не вдасться
monojohnny

96
  • Якщо ви вважаєте, що об'єкт не повинен бути нульовим (або це помилка), використовуйте пришвидшення.
  • Якщо ваш метод не приймає нульові параметри, скажіть це в javadoc і використовуйте аргумент.

Ви повинні перевірити об'єкт! = Null, лише якщо ви хочете обробити випадок, коли об'єкт може бути нульовим ...

Існує пропозиція додати в Java7 нові анотації, щоб допомогти з паралелями null / notnull: http://tech.puredanger.com/java7/#jsr308



91

Я прихильник коду "не швидко". Запитайте себе - чи робите ви щось корисне у випадку, коли параметр є нульовим? Якщо у вас немає чіткої відповіді на те, що повинен робити ваш код у такому випадку ... Тобто він ніколи не повинен бути нульовим, а потім ігноруйте його і дозвольте NullPointerException кинути. Код виклику матиме стільки ж сенсу, що і NPE, як і IllegalArgumentException, але розробнику буде простіше налагодити і зрозуміти, що пішло не так, якщо NPE буде кинуто, а не ваш код, який намагається виконати якусь іншу несподівану ситуацію логіка - що в кінцевому рахунку призводить до того, що програма все-таки виходить з ладу.


2
краще використовувати твердження, тобто Contract.notNull (abc, "abc має бути ненульовим, не вдалося завантажити під час xyz?"); - це більш компактний спосіб, ніж робити if (abc! = null) {кинути новий 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();

71
ммм, яка різниця? p.getAge () буде кидати ту саму NPE з меншими накладними та чіткішими стежками стека. Що я пропускаю?
таємничий

15
Краще кинути IllegalArgumentException ("e == null") у вашому прикладі, оскільки це чітко вказує на те, що це виняток, призначений програмістом (разом із достатньою кількістю інформації, щоб фактично дозволяти технічному обслуговувачу визначити проблему). NullPointerExceptions слід зарезервувати для JVM, оскільки це чітко вказує на те, що це було ненавмисно (і зазвичай це відбувається десь важко ідентифікувати)
Thorbjørn Ravn Andersen

6
Зараз це частина Google Guava.
Стівен Бенітес

32
Пахне як надмірна інженерія для мене. Просто дозвольте JVM кинути NPE і не захаращуйте свій код цим сміттям.
Alex Worden

5
Мені це подобається і відкриваю більшість методів і конструкторів з явними перевірками аргументів; якщо є помилка, методи завжди виходять з ладу в перших кількох рядках, і я знаю, що ображає посилання, не знаходячи щось на кшталтgetThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");
Cory Kendall

75

Іноді у вас є методи, які працюють на його параметрах, які визначають симетричну операцію:

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

Якщо ви знаєте, що b ніколи не може бути нульовим, ви можете просто поміняти його. Це найкорисніше для рівних: Замість foo.equals("bar");кращого робити "bar".equals(foo);.


2
Але тоді ви повинні припустити equals(міг би бути будь-який метод) буде правильно обробляти нуль. Дійсно, все це робиться - це передати відповідальність комусь іншому (або іншим методом).
Суперситний

4
@Supericy В основному так, але equals(або будь-який метод) потрібно перевірити на nullбудь-який вигляд . Або прямо заявляйте, що це не так.
Анджело Фукс

74

Замість Null Object Pattern - яка має його використання - ви можете розглянути ситуації, коли нульовий об’єкт - помилка.

Коли викид буде викинуто, вивчіть слід стека та пропрацюйте помилку.


17
Проблема полягає в тому, що зазвичай ви втрачаєте контекст, оскільки NullPointerException не вказує, Яка змінна була нульовою, і у вас може бути кілька операцій на лінії. Використання "if (foo == null) кинути новий RuntimeException (" foo == null ")" дозволяє чітко заявити, ЩО було не так, надаючи трійку стека набагато більше значення тим, хто має це виправити.
Thorbjørn Ravn Andersen

1
Що стосується Андерсена - я б хотів, щоб система винятків Java могла включати ім'я змінної, над якою працює, так що NullPointerExceptions не вказуватиме лише на рядок, на якому стався виняток, але і ім'я змінної. Це може спрацювати непогано у непродуманому програмному забезпеченні.
fwielstra

Мені ще не вдалося змусити його працювати, але це покликане вирішити саме цю проблему.
MatrixFrog

В чому проблема? Просто вловлюйте NPE на відповідному рівні, де доступно достатньо контексту, скидайте інформацію про контекст та повторно скидайте виняток ... З Java це так просто.
користувач1050755

3
У мене був професор, який проповідував проти ланцюгових викликів методів. Його теорія полягала в тому, що вам слід насторожено ставитись до ланцюгів викликів, які були довші двох методів. Я не знаю, чи це важке правило, але воно однозначно усуває більшість проблем із слідами стека NPE.
RustyTheBoyRobot

74

Null не є «проблемою». Він є невід'ємною частиною повного набору інструментів для моделювання. Програмне забезпечення має на меті моделювати складність світу і несе його тягар. Null вказує на " Java даних" або "Невідомо" в Java тощо. Тож доречно використовувати нулі для цих цілей. Я не віддаю перевагу шаблону 'Null object'; Я думаю, це піднімає « хто охоронятиме проблему опікунів ».
Якщо ви запитаєте мене, як звуть мою дівчину, я скажу вам, що у мене немає подруги. На мові Java я поверну нульове значення. Альтернативою було б кинути змістовний виняток, щоб вказати на якусь проблему, яка не може бути (або не "

  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 null safe 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()повертається null?
    A. Це залежить від бізнес-логіки. Якщо мені не вдасться знайти фотоальбом, я покажу вам жодних фотографій. Що робити, якщо appContext не ініціалізується? Ділова логіка цього методу мириться з цим. Якщо ж логіка повинна бути більш суворою, то викидання виключення - це частина ділової логіки і слід використовувати чітку перевірку на нуль (випадок 3). У нових Java Null-безпечний API припадки краще тут , щоб вказати вибірково , що має на увазі і те , що не означає бути инициализирован , щоб бути не в змозі , швидко в разі помилок програміста.

    Q. Може бути виконаний надмірний код і захопити зайві ресурси.
    A. Це може відбутися, якщо getPhotoByName()б спробувати відкрити підключення до бази даних, створити PreparedStatementта використати ім’я людини як параметр SQL нарешті. Підхід до невідомого питання дає невідому відповідь (випадок 1) працює тут. Перед захопленням ресурсів метод повинен перевірити параметри і, якщо потрібно, повернути "невідомий" результат.

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

    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

23
Я б сказав, якщо б мав репутацію. Мало того, що null не потрібен, це отвір у системі типу. Призначення дерева до списку - це помилка типу, оскільки дерева не є значеннями типу List; за цією ж логікою присвоєння null має бути помилкою типу, оскільки null не є значенням типу Object або будь-яким корисним типом для цього питання. Навіть людина, яка вигадала нуль, вважає це "помилкою в мільярд доларів". Поняття "значення, яке може бути значенням типу T АБО нічого", не є власним типом, і воно повинно бути представлене як таке (наприклад, <T> або Необов'язково <T>).
Doval

2
Що стосується "Можливо, <T> або Необов'язково <T>", вам все одно потрібно написати код, як- if (maybeNull.hasValue()) {...}от, в чому різниця if (maybeNull != null)) {...}?
Михайло Адамович

1
Що стосується "лову NullPointerException і повернення нуля - жахливо налагоджувати. Ви все одно закінчитесь з NPE пізніше, і справді важко зрозуміти, що спочатку було нульовим". Я повністю згоден! У цих випадках слід написати десяток висловлювань «якщо» або кинути 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");
}

9
Насправді ваш приклад являє собою розширення коду: перший рядок є зайвим, оскільки NPE буде кинутий у другий рядок. ;-)
user1050755

1
Правда. Кращим прикладом було б, якби другий рядок був doCalc(someObject).
Стюарт відзначає

Залежить. Якщо ви автор doCalc (), я б запропонував поставити чек у тіло цього методу (якщо можливо). І тоді ви, швидше за все, викличете someObject.someMethod (), де знову не потрібно перевіряти на null. :-)
user1050755

Ну, якщо ви не є автором doCalc(), і він не одразу кидає NPE, коли йому задано null, вам доведеться перевірити наявність null і кинути NPE самостійно. Ось для чого Objects.requireNonNull().
Стюарт відзначає

7
Це не просто розшифровка коду. Краще перевірити спереду, ніж на півдорозі методом, який викликає побічні ефекти або використовує час / простір.
Роб Грант

51

Зрештою, єдиний спосіб повністю вирішити цю проблему - це використовувати іншу мову програмування:

  • У Objective-C ви можете виконати еквівалент виклику методу nil, і абсолютно нічого не станеться. Це робить більшість недійсних перевірок непотрібними, але це може зробити помилки набагато складніше діагностувати.
  • У Ніцці , що походить від мови Java, є дві версії всіх типів: потенційно нульова версія та ненулева версія. Способи можна викликати лише у ненульових типах. Типи потенційно-null можуть бути перетворені в ненульові типи шляхом явної перевірки на null. Завдяки цьому набагато простіше дізнатись, де необхідні перевірки нуля та де їх немає.

Вуа ... Найправильніша відповідь і її оскаржується, де справедливість? У Java null - це завжди дійсне значення. Це наслідок Все є об'єктом - Null is a Everything (звичайно, ми тут ігноруємо примітивів, але ви розумієте). Особисто я прихильний підхід Ніцци, хоча ми могли б зробити так, щоб методи можна було використовувати на змінних типах і сприяти НПЕ до перевірених винятків. Це потрібно зробити через перемикач компілятора, хоча він би порушив усі існуючі коди :(
CurtainDog

4
Я не знайомий з Ніцці, але Котлін реалізує ту саму ідею, має нульові та ненульові типи, вбудовані в систему типів мови. Набагато більш стислий, ніж необов'язковий або нульовий шаблон об'єкта.
mtsahakis

38

Поширена "проблема" в Java справді.

По-перше, мої думки з цього приводу:

Я вважаю, що погано "їсти" щось, коли було передано NULL там, де NULL не є дійсним значенням. Якщо ви не виходите з методу з якоюсь помилкою, це означає, що у вашому методі нічого не вийшло, що не відповідає дійсності. Тоді ви, ймовірно, повертаєте null в цьому випадку, і в методі отримання ви знову перевіряєте null, і він ніколи не закінчується, і ви закінчуєте "if! = Null" тощо.

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

Я вирішую цю проблему так:

По-перше, я дотримуюся цієї конвенції:

  1. Усі загальнодоступні методи / API завжди перевіряють його аргументи на нуль
  2. Усі приватні методи не перевіряють на null, оскільки вони є контрольованими методами (просто нехай гине за винятком nullpointer, якщо це не оброблялося вище)
  3. Єдині інші методи, які не перевіряють на нуль, - це корисні методи. Вони є загальнодоступними, але якщо ви зателефонуєте їм з якоїсь причини, ви знаєте, які параметри ви проходите. Це як спробувати кип'ятити воду в чайнику, не подаючи води ...

І нарешті, у коді перший рядок публічного методу йде так:

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

Зауважте, що addParam () повертається самоврядування, так що ви можете додати більше параметрів для перевірки.

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

void validate() throws ValidationException;

Повідомлення буде містити наступний текст, якщо, наприклад, "плани" є недійсними:

" Неправильне значення аргументу null зустрічається для параметра [плани] "

Як бачимо, друге значення методу addParam () (рядок) потрібне для повідомлення користувача, оскільки ви не можете легко виявити ім'я змінної, що передається, навіть за допомогою відображення (не є предметом цієї публікації ...).

І так, ми знаємо, що поза цим рядком ми більше не зустрінемо нульове значення, тому просто безпечно викликаємо методи на цих об'єктах.

Таким чином, код є чистим, легкодоступним і читабельним.


3
Абсолютно. Програми, які просто викликають помилку та збій, мають більш високу якість, оскільки немає сумнівів, коли вони не працюють. Програми, які поглинають помилки, у кращому випадку виграшно виграють, але зазвичай не працюють способами, які важко помітити та не виправити. І коли проблема помічена, їх налагодити набагато важче.
Пол Джексон

35

Поставляючи це питання, вказує, що вас можуть зацікавити стратегії поводження з помилками. Архітектор вашої команди повинен вирішити, як працювати з помилками. Існує кілька способів зробити це:

  1. дозвольте Виняткам прошиватись - зафіксуйте їх у "головному циклі" або в іншому розпорядницькому режимі.

    • перевірити умови помилок і правильно їх обробляти

Впевнені, що ви також подивитеся на програмування, орієнтоване на аспекти - у них є чіткі способи вставити if( o == null ) handleNull()у ваш байт-код.


35

На додаток до використання assertви можете використовувати наступне:

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

Це трохи краще, ніж:

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



    .....
}

2
Mh, чому це? Будь ласка, не відчувайте жодної оборони, я просто хотів би дізнатися більше про Java :)
Matthias Meid

9
@Mudu Як загальне правило, я вважаю за краще, щоб вираз у операторі if був більш "позитивним", а не "негативним". Тож якби я бачив, if (!something) { x(); } else { y(); }я схильний би рефакторировать його як if (something) { y(); } else { x(); }(хоча можна стверджувати, що != nullце і позитивніший варіант ...). Але ще важливіше, що важлива частина коду не загорнута всередині {}s, і у вас на один рівень менше відступів для більшості методів. Я не знаю, чи були це міркування Fastcodejava, але це було б моїм.
MatrixFrog

1
Це те, що я, як правило, роблю .. Дотримується чистоти коду, на мій погляд.
Корай Тугай

34

Просто ніколи не використовуйте null. Не дозволяйте.

У моїх класах більшість полів та локальних змінних мають ненульові значення за замовчуванням, і я додаю виписки про контракти (завжди за твердженнями) скрізь у коді, щоб переконатися, що це виконується (оскільки воно є більш коротким та виразнішим, ніж його дозволяти придумати як 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, ви повинні якось розібратися.


чому б це було знято? на мій досвід, це набагато перевершує інші підходи, я хотів би знати, чому ні
ianpojman

Я погоджуюся, такий підхід запобігає проблемам, пов’язаним з нулями, а не їх виправленню, перевіряючи нульову перевірку коду скрізь.
ChrisBlom

7
Проблема такого підходу полягає в тому, що якщо ім'я ніколи не встановлюється, воно має значення "<невідомо>", яке поводиться як задане значення. Тепер скажімо, що мені потрібно перевірити, чи ім’я ніколи не було встановлено (невідомо), я повинен зробити порівняння рядків зі спеціальним значенням "<невідомо>".
Стів Куо

1
Справжня гарна точка Стіва. Те, що я часто роблю, - це значення як постійне, наприклад, загальнодоступне статичне кінцеве String UNSET = "__ unset" ... приватне поле String = UNSET ... потім приватне буле isSet () {return UNSET.equals (поле); }
ianpojman

IMHO, Це реалізація Null Object Pattern з власною реалізацією Факультативного (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 надала більш безпечний спосіб поводження з об'єктом, значення якого може бути нульовим у деяких випадках. Він натхненний ідеями Хаскелла та Скали .

У двох словах, клас Факультативний включає методи явного вирішення випадків, коли значення присутнє або відсутнє. Однак перевага порівняно з нульовими посиланнями полягає в тому, що клас необов’язковий <T> змушує задуматися про випадок, коли значення відсутнє. Як наслідок, ви можете запобігти випадковим виняткам нульових покажчиків.

У наведеному вище прикладі ми маємо фабрику побутового обслуговування, яка повертає ручку на кілька приладів, доступних в будинку. Але ці послуги можуть бути або не бути доступними / функціональними; це означає, що це може призвести до NullPointerException. Замість того, щоб додати нульову ifумову перед тим, як використовувати будь-яку послугу, давайте перетворимо її на Факультативний <Сервіс>.

ПОВЕРНЕННЯ ДО ОПЦІЇ <T>

Розглянемо метод отримання довідки про послугу з заводу. Замість того, щоб повертати посилання на службу, оберніть її необов’язково. Це дозволяє користувачеві API знати, що повернута послуга може, а може бути недоступною / функціональною, використовувати оборонно

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

Як ви бачите, Optional.ofNullable()надається простий спосіб отримати посилання. Є ще способи отримати посилання факультативних, або Optional.empty()&Optional.of() . Один для повернення порожнього об'єкта замість повторного перезапуску нуля, а інший для обертання ненульового об'єкта відповідно.

ТАК ЯК ВІДПОВІДНО ДОПОМОГАЄТЬСЯ НІЛЬНОГО ПРОВЕРКУ?

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

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

Необов’язково.ifPresent викликає даного Споживача з посиланням, якщо воно є ненульовим значенням. Інакше нічого не робить.

@FunctionalInterface
public interface Consumer<T>

Представляє операцію, яка приймає єдиний вхідний аргумент і не повертає результату. На відміну від більшості інших функціональних інтерфейсів, очікується, що споживач працює через побічні ефекти. Це так чисто і легко зрозуміти. У наведеному вище прикладі коду HomeService.switchOn(Service)викликається, якщо необов'язкове посилання на проведення не має значення.

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

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, а також Null check-free… Дійсно? .


Введений вище код нульової перевірки - if(homeServices != null) {який можна змінити homeServices.ifPresent(h -> //action);
KrishPrabakar

23

Мені подобаються статті від Nat Pryce. Ось посилання:

У статтях також є посилання на репозиторій Git для Java Maybe Type, який мені здається цікавим, але я не думаю, що це одне може зменшити кількість перевірки коду. Провівши деякі дослідження в Інтернеті, я думаю, що ! = Розширення нульового коду можна зменшити в основному при ретельному дизайні.


Michael Feathers написав короткий та цікавий текст про такі підходи, як той, про який ви згадали: manuelp.newsblur.com/site/424
ivan.aguirre

21

Я спробував, NullObjectPatternале для мене це не завжди найкращий шлях. Бувають випадки, коли "жодна дія" не відповідає.

NullPointerException- це виняток під час виконання програми, що означає, що це вина розробників, і маючи достатній досвід, він точно говорить вам, де помилка.

Тепер до відповіді:

Постарайтеся зробити всі свої атрибути та їх доступність максимально приватними або уникайте їх взагалі піддавати клієнтам. Ви можете мати значення аргументів у конструкторі, звичайно, але, зменшуючи область застосування, ви не дозволяєте класу клієнтів передавати недійсне значення. Якщо вам потрібно змінити значення, ви завжди можете створити нове object. Ви перевіряєте значення в конструкторі лише один раз, а в решті методів ви можете бути майже впевнені, що значення не є нульовими.

Звичайно, досвід є кращим способом зрозуміти та застосувати цю пропозицію.

Байт!


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());

Приклад того, як викинути виняток на null:

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();

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)

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

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

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 (Звичайні старі дані), які призначені для утримання стану у вигляді вбудованих типів Java. В останньому випадку ви в кінцевому підсумку викличете методи getter з довільними результатами. Наприклад, що повинен повертати метод NullPerson.getName ()?

Варто розглянути такі випадки, щоб уникнути абсурдних результатів.


Рішення з "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
    }
}

У цьому є невеликий наклад, але це варто того, щоб отримати більш чистий код і менше NullPointerExceptions.



3
+1 З цим я згоден. Ніколи не слід повертати половину ініціалізованих об'єктів. Код, пов'язаний з Jaxb, і код квасолі - це повідомлення. Це погана практика. Усі колекції повинні бути ініціалізовані, а всі об'єкти повинні існувати з (в ідеалі) відсутніми нульовими посиланнями. Розглянемо предмет, у якому є колекція. Перевірка того, що об’єкт не є нульовим, що колекція не є нульовою і що колекція не містить нульових об'єктів, є необґрунтованою та нерозумною.
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. Це не дуже корисно - коли ви перевіряєте, чи покажчик недійсний, ви, ймовірно, хочете викликати метод на ньому. Виклик методу null дає вам таку саму поведінку - NullPointerException.
pkalinow

11
public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}

2
Що не так з цим методом, я думаю, що @tltester просто хоче дати значення за замовчуванням, якщо воно є нульовим, що має сенс.
Сойєр

1
Існує такий метод у Apache commons-lang : ObjectUtils.defaultIfNull(). Є ще одне загальне: ObjectUtils.firstNonNull()яке можна використовувати для впровадження принизливої ​​стратегії:firstNonNull(bestChoice, secondBest, thirdBest, fallBack);
ivant
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.