Питання Уникати! = Нульові твердження


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

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

Наприклад:

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

Це уникає a NullPointerException, коли невідомо, чи є об'єкт null чи ні.

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


3553


походження


@Shervin Заохочення nulls робить код менш зрозумілим і менш надійним. - Tom Hawtin - tackline
Оператори Елвіса були запропонований але, схоже, не буде Java 7. Надто погано?. ?: і? [] є неймовірними вкладниками часу. - Scott
Не використовуючи нуль, це перевершує більшість інших пропозицій тут. Викидайте виключення, не повертайте і не дозволяйте нулів. BTW - ключове слово "стверджувати" є марним, оскільки його за замовчуванням вимкнено. Використовуйте механізм відмови завжди включений - ianpojman
Це одна з причин, чому я зараз використовую Scala. У Scala все не нульове. Якщо ви хочете дозволити передавати або повертати "нічого", то вам слід використовувати виключення [T], а не тільки аргумент T als або тип повернення. - Thekwasti
@thSoft Дійсно, Scala's awesome Option Тип не знає небажання мови контролювати або забороняти null. Я згадав про це на хакерських новинах і сказав, що ніхто в Scala нічим не використовує null. Вгадай що, я вже знаходжу нуль у написаній колегою Скалі. Так, вони "роблять це неправильно" і повинні бути освічені про це, але факт залишається мовою тип системи повинен захистити мене від цього, і це не: ( - Andres F.


Відповіді:


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

Щоб поставити це іншим способом, існують два випадки, коли з'являється нульова перевірка:

  1. Де нуль є дійсною відповіддю щодо умов договору; і

  2. Де це не є дійсною відповіддю.

(2) легко. Або використовуйте assert заяви (твердження) або дозволити збій (наприклад, NullPointerException) Затвердження - це дуже неефективна функція Java, яка була додана в 1.4. Синтаксис:

assert <condition>

або

assert <condition> : <object>

де <condition> є логічним виразом і <object> є об'єктом, чиє toString() Вихід методу буде включений в помилку.

Ан assert заява кидає Error (AssertionError), якщо умова не відповідає дійсності. За замовчуванням Java ігнорує твердження. Ви можете включити твердження, передаючи параметр -ea для JVM. Ви можете включити та вимкнути твердження для окремих класів та пакетів. Це означає, що ви можете перевіряти код з твердженнями під час розробки та тестування, а також відключати їх у виробничому середовищі, хоча тестування показало, що показники ефективності не відображаються з тверджень.

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

(1) трохи складніше. Якщо ви не маєте контролю над кодом, який ви телефонуєте, тоді ви застрягли. Якщо нуль є дійсною відповіддю, ви повинні перевірити це.

Однак, якщо це кодекс, який ви контролюєте, однак (і це часто буває), то це інша історія. Не використовуйте nulls як відповідь. За допомогою методів, що повертають колекції, просто: повертайте порожні колекції (або масиви) замість нулів майже весь час.

З не колекціями це може бути важче. Розглянемо це як приклад: якщо у вас є такі інтерфейси:

public interface Action {
  void doSomething();
}

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

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

Альтернативним рішенням є ніколи не повертати null, а замість цього використовувати Нульовий шаблон об'єкта:

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

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

Ось що, можливо, метод method 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);
        }
    }
}

2392



Я не згоден з вашими твердженнями щодо дії DO_NOTHING. Якщо метод find action не може знайти дію, то повернення нуля є правильним. Ви "знайшли" дію у вашому коді, який насправді не виявлено, що порушує принцип методу, щоб знайти корисну дію. - MetroidFan2002
Я згоден з тим, що в роботі Java особливо недоопрацьовано, особливо зі списками. Так багато APIS буде краще, якщо вони повертають порожній список / масив / колекцію, а не нульові. Багато разів використовується нуль, де замість цього слід викинути виняток. Виняток слід викинути, якщо синтаксичний аналізатор не може проаналізувати. - Laplie Anderson
Останним прикладом тут є IIRC, шаблон дизайну Null Object. - Steven Evers
@Cshah (і MetroidFan2002). Просто, покладіть це в контракт, і тоді зрозуміло, що не знайдене повернута дія нічого не зробить. Якщо це важлива інформація для абонента, надайте спосіб виявити, що це невиявлена ​​дія (тобто, надає метод, щоб перевірити, чи був результат об'єктом DO_NOTHING). Як альтернатива, якщо дія зазвичай повинна бути знайдена, ви все одно не повинні повертати нуль, але замість того, щоб виключити конкретно, що означає це умова, це все ще призведе до кращого коду. Якщо потрібно, надайте окремий метод, що повертає логічне значення, щоб перевірити, чи існує дія. - Kevin Brock
Короткий код не відповідає якістю. Мені шкода, що ти так думаєш. Ваш код приховує ситуації, коли помилка буде вигідною. - gshauger


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


518



@NotNull, @Nullable та інші анотації нульової частини є частиною JSR 305. Ви також можете використовувати їх для виявлення потенційних проблем за допомогою таких інструментів FindBugs. - Jacek S
Мені здається дивно, що це дратує @NotNull & @Nullable інтерфейси живуть в пакеті com.sun.istack.internal. (Я думаю, я пов'язую com.sun з попередженнями про використання власного API.) - Jonik
Переносимість коду з нульовим значенням переходить з jetbrains.I думаю, двічі (квадрат), перш ніж прив'язати до рівня ідей. Як Jacek S сказав, що вони є частиною JSR будь-яким способом, яким я думав, JSR303 до речі. - Java Ka Baby
Я дійсно не думаю, що використання користувацького компілятора є життєздатним рішенням цієї проблеми. - Shivan Dragon
Гарна річ про анотації, яка @NotNull і @Nullable полягає в тому, що вони гарно розкладаються, коли вихідний код побудований системою, яка їх не розуміє. Отже, фактично, аргумент про те, що код не портативний, може бути недійсним - якщо ви використовуєте систему, яка підтримує та розуміє ці анотації, ви отримуєте додаткову перевагу від перевірки помилок більш жорсткої помилки, інакше ви отримаєте менше цього, але ваш код все одно повинен правильно побудувати, а якість запущеної програми - ТАКЕ, оскільки ці анотації в будь-якому випадку не застосовувались під час виконання. Крім того, всі компілятори є звичайними ;-) - amn


Якщо нульові значення не дозволяються

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

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

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


282



Яка справа в кишені IllegalArgumentException? Я думаю, що NullPointerException буде більш чітким, і це також буде викинуте, якщо ви не зробите нульову перевірку самостійно. Я б або використати затвердження, або взагалі нічого. - Axel
Малоймовірно, що прийнятним є будь-яке інше значення, ніж нуль. Ви можете мати IllegalArgumentException, OutOfRageException тощо. Іноді це має сенс. Інші рази призводять до створення багатьох класів виключень, які не додають ніякого значення, тоді ви просто використовуєте IllegalArgumentException. Немає сенсу мати одне виключення для нульового вводу, а інше - для всього іншого. - myplacedk
Так, я погоджуюсь з принципом "неспроможність", але в прикладі, наведеному вище, значення не передано, але це об'єкт, на якому повинен бути викликаний метод. Таким чином, він помирає в рівній мірі швидко, і додаючи нульову перевірку, щоб просто викинути виняток, яке в будь-якому випадку буде викинуте одночасно, і місце не полегшить налагодження. - Axel
Отвори безпеки? JDK заповнений таким кодом. Якщо ви не бажаєте, щоб користувач бачив стаккротрази, просто відключіть їх. Ніхто не мав на увазі, що поведінка не документована. MySQL написано на C, де нулі вказівки для відхилення є невизначеною поведінкою, нічим не подобається кидати виняток. - fgb
throw new IllegalArgumentException("object==null") - Thorbjørn Ravn Andersen


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

Приклад Алекса Міллера виглядає так:

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

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


Оновлення: Ан офіційна пропозиція для null-safe оператора в Java 7 був представлений під Монета проекту. Синтаксис трохи відрізняється від наведеного вище прикладу, але це те ж саме поняття.


Оновлення: Недійсна пропозиція оператора не перетворила його в проектну монету. Таким чином, ви не побачите цей синтаксис в Java 7.


215



Я думаю, що це неправильно. Там повинен бути спосіб вказати, що дана змінна ВНУТРІШНЬО не є нульовою. - Thorbjørn Ravn Andersen
Оновлення: пропозиція не зробить Java7. Побачити blogs.sun.com/darcy/entry/project_coin_final_five . - Boris Terzic
Цікава ідея, але вибір синтаксису абсурдний; Я не хочу, щоб кодова база була повна знакових знаків, прикріплених до кожного суглоба. - Rob
Цей оператор існує в Гровай, тому ті, хто хоче використовувати його, як і раніше, мають такий варіант. - Muhd
Це сама геніальна ідея, яку я бачив. Його слід додати до кожної розумної мови синтаксису C. Я б краще "писати знаки питання" скрізь, ніж прокручувати скрінлайн ліній або ухилятися від "охоронних положень" цілий день. - Victor


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

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

Якщо невизначені значення допускаються:

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

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

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

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


177



Дійсно, варіант шаблону чудовий. Деякі еквіваленти Java існують. Гуава містить обмежену версію цього називається необов'язковою, яка не містить більшості функціональних речей. У Хаскеллі така схема називається Можливо. - Ben Hardy
Додатковий клас буде доступний у Java 8 - Pierre Henry
... і воно (поки що) не має карти, а не "flatMap": download.java.net/jdk8/docs/api/java/util/Optional.html - thSoft
Факультативний шаблон нічого не вирішує; замість одного потенційно нульового об'єкта, тепер у вас є два. - Boann
@Боанн, якщо його використовувати обережно, ви вирішите проблему з усіма вашими НПЕ. Якщо ні, то я думаю, що існує проблема "використання". - Louis F.


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

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

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

призведе до а NullPointerException якщо foo не існує

Ви можете уникнути цього, якщо порівнюєте свою Stringяк це:

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

160



Я згоден - тільки в такій ситуації. Я не можу стояти програмістам, які взяли це на наступний непотрібний рівень і пишуть якщо (нуль! = MyVar) ... просто виглядає потворним для мене і не служить ніякої мети! - Alex Worden
Це конкретний приклад, ймовірно, найбільш часто використовуваний загальної належної практики: якщо ви це знаєте, завжди робіть <object that you know that is not null>.equals(<object that might be null>);. Він працює для інших методів, крім equals якщо ви знаєте контракт і ці способи можуть впоратися null параметри - Stef
Це перший приклад, який я бачив Йода умови що насправді має сенс - Erin Drummond
NullPointerExceptions кидаються з причини. Вони кидаються, тому що об'єкт нульовий, де він не повинен бути. Робота програмістів полягає в тому, щоб виправити це, а не приховати проблему. - Oliver Watkins
"Try-Catch-DoNothing" приховує проблему, це дійсна практика, спрямована на подолання відсутності цукру на мові. - echox


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

Вони працюють так:

Додатковий об'єкт для даного типу (Fruit) створюється як тип повернення методу. Він може бути порожнім або містити a 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) для певного примірника фруктів:

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 Додаткові об'єкти.


135



Гуава Google має додаткове імплікацію для Java 6+. - Bradley Gottfried
Його дуже Важливо підкреслити, що використання необов'язково тільки з ifPresent () робить ні додайте велику величину вище звичайної нульової перевірки. Основна цінність полягає в тому, що це монада, яку можна використовувати в ланцюжках функцій map / flapMap, які досягають результатів, подібних до оператора Елвіса в Groovy, згаданому в іншому місці. Хоча, навіть без цього використання, я вважаю, що синтаксис orElse / orElseThrow також дуже корисний. - Cornel Masson
Цей блог має хороший запис на вибір winterbe.com/posts/2015/03/15/avoid-null-checks-in-java - JohnC
Я насправді не зрозумів, чому люди настільки задоволені цим кодеком dzone.com/articles/java-8-elvis-operator - Mykhaylo Adamovych
Чому люди мають тенденцію робити це if(optional.isPresent()){ optional.get(); } замість optional.ifPresent(o -> { ...}) - Satyendra Kumar


Залежно від того, які об'єкти ви перевіряєте, ви, можливо, зможете використовувати деякі класи в загальних апасах, наприклад: apache commons lang і apache commons колекції

Приклад:

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

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

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

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

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

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

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

113



Також ви можете скористатися більш загальною версією з Apache Commons, дуже корисною на початку методів перевірки параметрів я знаходжу. Validate.notNull (об'єкт, "об'єкт не повинен бути нульовим"); commons.apache.org/lang/apidocs/org/apache/commons/lang/... - monojohnny
@monojohnny does Validate використовувати твердження Assert в ?. Я прошу, оскільки Assert може бути активований / деактивований на JVM, і це пропонують не використовувати у виробництві. - Kurapika
Не думайте так - я вважаю, що він просто викидає RuntimeException, якщо перевірка не вдається - monojohnny