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

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

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

Например:

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

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

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

Ответы [ 62 ]

74 голосов
/ 29 декабря 2008

Каркас коллекций 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();
73 голосов
/ 07 ноября 2008

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

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

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

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

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

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

68 голосов
/ 21 ноября 2011

Ноль не является «проблемой». Он является неотъемлемой частью полного набора инструментов моделирования. Программное обеспечение стремится моделировать сложность мира и ноль несет свое бремя. Ноль указывает «Нет данных» или «Неизвестно» в Java и т.п. Поэтому для этих целей целесообразно использовать нули. Я не предпочитаю шаблон «Нулевой объект»; Я думаю, что это поднимет 1005, кто будет охранять проблема хранителей .
Если вы спросите меня, как зовут мою подругу, я скажу вам, что у меня нет подруги. На языке 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())
    

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

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

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

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

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

    /**
     * @return photo or null
     */
    

    или

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

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

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

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

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

68 голосов
/ 10 августа 2012

В Java 7 появился новый служебный класс java.util.Objects, в котором есть метод requireNonNull(). Все, что это делает, это выдает 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");
}
50 голосов
/ 30 июля 2009

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

  • В Objective-C вы можете сделать эквивалентный вызов метода на nil, и абсолютно ничего не произойдет. Это делает большинство нулевых проверок ненужными, но может значительно усложнить диагностику ошибок.
  • В Nice , языке, производном от Java, есть две версии всех типов: потенциально нулевая версия и ненулевая версия. Вы можете вызывать методы только для ненулевых типов. Потенциально нулевые типы могут быть преобразованы в ненулевые типы посредством явной проверки на нулевые. Это позволяет гораздо легче узнать, где нужны нулевые проверки, а где нет.
35 голосов
/ 15 марта 2011

Действительно распространенная «проблема» в 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, если какой-либо из параметров имеет значение null (проверено или не отмечено, это больше проблема дизайна / вкуса, но мой ValidationException проверен).

void validate() throws ValidationException;

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

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

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

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

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

33 голосов
/ 17 августа 2010

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

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

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

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



    .....
}
33 голосов
/ 07 ноября 2008

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

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

    • проверить наличие ошибок и правильно их обработать

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

31 голосов
/ 18 октября 2012

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

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

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