Цепочка нулевой проверки и перехват исключения NullPointerException

118

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

return wsObject.getFoo().getBar().getBaz().getInt()

Проблема заключается в том, что getFoo(), getBar(), getBaz()может все возвращается null.

Однако, если я проверю nullво всех случаях, код станет очень подробным и трудным для чтения. Более того, я могу пропустить проверки по некоторым полям.

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();

Допустимо ли писать

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

или это будет считаться антипаттерном?

Дэвид Франк
источник
29
Я бы не возражал против nullчеков, так как wsObject.getFoo().getBar().getBaz().getInt()это уже запах кода. Прочтите, что такое «Закон Деметры» , и предпочтите соответственно реорганизовать свой код. Тогда nullисчезнет и проблема с чеками. И подумайте об использовании Optional.
Tom
9
А как насчет использования XPath и оставить это на их усмотрение?
Joop Eggen
15
Этот код, вероятно, сгенерирован wsdl2java, что не соблюдает Закон Деметры.
Адриан Кокс,

Ответы:

143

Отлов NullPointerException- действительно проблематичное занятие, поскольку оно может произойти практически где угодно. Очень легко получить один из бага, поймать его случайно и продолжить, как будто все в порядке, тем самым скрывая реальную проблему. С этим так сложно справиться, поэтому лучше избегать его вообще. (Например, подумайте об автоматической распаковке нуля Integer.)

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

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

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return null instead of an empty optional if any is null
        // .orElse(null);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

Почему необязательно?

Использование Optionals вместо nullзначений, которые могут отсутствовать, делает этот факт очень заметным и понятным для читателей, а система типов гарантирует, что вы случайно не забудете об этом.

Вы также получаете доступ к методам для более удобной работы с такими значениями, например, mapи orElse.


Отсутствие действительно или ошибка?

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


Может еще варианты?

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

Тогда вы можете использовать их так:

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

Почему не по желанию?

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

Lii
источник
Очень хороший ответ. Следует отметить, что если есть другие возможные условия отказа, и вам нужно различать их, вы можете использовать Tryвместо Optional. Хотя Tryв Java API его нет, есть много библиотек , предоставляющих его, например javaslang.io , github.com/bradleyscollins/try4j , functionjava.org или github.com/jasongoodwin/better-java-monads
Landei
8
FClass::getBarи т.д. будет короче.
Boris the Spider
1
@BoristheSpider: Может немного. Но я обычно предпочитаю лямбды ссылкам на методы, потому что часто имена классов намного длиннее, и я считаю, что лямбда-выражения немного легче читать.
Lii
6
@Lii достаточно честно, но обратите внимание, что ссылка на метод может быть немного быстрее, поскольку лямбда может потребовать более сложных конструкций времени компиляции. Лямбда потребует генерации staticметода, что повлечет за собой очень незначительные штрафы.
Boris the Spider
1
@Lii Я на самом деле считаю, что ссылки на методы чище и нагляднее, даже если они немного длиннее.
shmosel
14

Предлагаю подумать Objects.requireNonNull(T obj, String message). Вы можете строить цепочки с подробным сообщением для каждого исключения, например

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

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

Метание NullPointerExceptionтоже не лучший вариант. Вы можете указать собственное исключение (сделав его отмеченным, чтобы гарантировать, что оно будет обработано пользователем, или снимите флажок, чтобы обработать его более простым способом) или использовать конкретное исключение из используемого вами синтаксического анализатора XML.

Андрей Тобилко
источник
1
Objects.requireNonNullв итоге выкидывает NullPointerException. Так что это не делает ситуацию иначе, чемreturn wsObject.getFoo().getBar().getBaz().getInt()
Арка Гош
1
@ArkaGhosh, также он избегает большого количества ifs, как показал OP
Эндрю Тобилко,
4
Это единственное разумное решение. Все остальные советуют использовать исключения для управления потоком, что является запахом кода. На стороне примечания: я считаю, что цепочка методов, выполняемая OP, также является запахом. Если бы он работал с тремя локальными переменными и соответствующими if, ситуация была бы намного яснее. Также я думаю, что проблема глубже, чем просто работа с NPE: OP должен спросить себя, почему геттеры могут возвращать null. Что значит ноль? Может, лучше бы какой-нибудь null-объект? Или сбой в одном геттере со значимым исключением? В принципе, для управления потоком все лучше исключений.
Marius K.
1
Безусловный совет использовать исключения для сигнализации отсутствия допустимого возвращаемого значения не очень хорош. Исключения полезны, когда метод дает сбой из-за того, что вызывающей стороне трудно восстановить, и что лучше обрабатывать с помощью оператора try-catch в какой-либо другой части программы. Чтобы просто сигнализировать об отсутствии возвращаемого значения, лучше использовать Optionalкласс или, может быть, вернуть значение, допускающее значение NULLInteger
Lii
6

Предполагая, что структура классов действительно находится вне нашего контроля, что, по-видимому, так и есть, я думаю, что обнаружение NPE, как предлагается в вопросе, действительно является разумным решением, если только производительность не является серьезной проблемой. Одно небольшое улучшение могло бы заключаться в том, чтобы обернуть логику throw / catch, чтобы избежать беспорядка:

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

Теперь вы можете просто:

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
shmosel
источник
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");не выдает ошибок во время компиляции, что может быть проблематичным.
Филипп Джозеффи
5

Как уже отмечал Том в комментарии,

Следующее заявление не подчиняется закону Деметры ,

wsObject.getFoo().getBar().getBaz().getInt()

Вы хотите, intи можете получить это Foo. Закон Деметры гласит: никогда не разговаривай с незнакомцами . В вашем случае вы можете скрыть реальную реализацию под капотом Fooи Bar.

Теперь вы можете создать метод в Fooизвлечь intиз Baz. В конечном счете, Fooбудет иметь Barи Barмы можем получить доступ , Intне подвергая Bazнепосредственно Foo. Таким образом, нулевые проверки, вероятно, разделены на разные классы, и только необходимые атрибуты будут разделяться между классами.

CoderCroc
источник
4
Спорный вопрос, не подчиняется ли это Закону Деметры, поскольку WsObject, вероятно, всего лишь структура данных. Смотрите здесь: stackoverflow.com/a/26021695/1528880
DerM,
2
@DerM Да, это возможно, но поскольку OP уже имеет что-то, что анализирует его XML-файл, он также может подумать о создании подходящих классов моделей для требуемых тегов, чтобы библиотека синтаксического анализа могла их сопоставить. Затем эти классы модели содержат логику для nullпроверки своих собственных вложенных тегов.
Tom
4

Мой ответ находится почти в той же строке, что и @janki, но я хотел бы немного изменить фрагмент кода, как показано ниже:

if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
   return wsObject.getFoo().getBar().getBaz().getInt();
else
   return something or throw exception;

Вы также можете добавить нулевую проверку wsObject, если есть вероятность, что этот объект будет нулевым.

Арка Гош
источник
4

Вы говорите, что некоторые методы «могут возвращаться null», но не говорите, при каких обстоятельствах они возвращаются null. Вы говорите, что ловите это, NullPointerExceptionно не говорите, почему вы это ловите. Этот недостаток информации предполагает, что вы не имеете четкого представления о том, для чего нужны исключения и почему они лучше альтернативы.

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

Используя исключения, мы можем написать методы, которые будут успешными как условие публикации . Если метод возвращается, он был успешным. Если он вызывает исключение, значит, он потерпел неудачу. Это большая победа для ясности. Мы можем написать код, который четко обрабатывает нормальный, успешный случай, и переместить весь код обработки ошибок в catchразделы. Часто выясняется, что подробности того, как и почему метод оказался неудачным, не важны для вызывающего, поэтому одно и то же catchпредложение можно использовать для обработки нескольких типов сбоев. И часто бывает , что метод не требует исключения улова на всех , но может просто позволить им распространяться на его вызывающий. Исключения из-за программных ошибок относятся к этому последнему классу; немногие методы могут адекватно реагировать на ошибку.

Итак, те методы, которые возвращаются null.

  • nullУказывает ли значение на ошибку в вашем коде? Если это так, вы вообще не должны перехватывать исключение. И ваш код не должен пытаться переубедить самого себя. Просто напишите то, что ясно и кратко, исходя из предположения, что это сработает. Является ли цепочка вызовов методов ясной и краткой? Тогда просто используйте их.
  • nullУказывает ли значение на недопустимый ввод в вашу программу? Если это так, то NullPointerExceptionисключение не следует генерировать, потому что обычно оно зарезервировано для индикации ошибок. Вероятно, вы захотите сгенерировать настраиваемое исключение, производное от IllegalArgumentException(если вы хотите исключениеIOException без проверки ) или (если вам нужно проверенное исключение). Требуется ли ваша программа для предоставления подробных сообщений об ошибках синтаксиса при недопустимом вводе? Если это так, nullто единственное, что вы можете сделать , - это проверка каждого метода на возвращаемое значение и выдача соответствующего диагностического исключения. Если вашей программе не требуется предоставлять подробную диагностику, объединение вызовов методов в цепочку, перехват любого NullPointerExceptionи последующее генерирование пользовательского исключения является наиболее ясным и кратким.

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

  • Когда дело доходит до разработки программ, на самом деле нет никаких абсолютных правил о том, что хорошо, а что плохо. Есть только эвристика: правила, которые верны в большинстве (даже почти во всех) случаях. Часть навыков программирования - знать, когда можно нарушать такие правила. Таким образом, краткое утверждение, что «это противоречит правилу X », на самом деле не является ответом. Это одна из ситуаций, когда правила следует нарушать?
  • Закон Деметры действительно правило о API или дизайн интерфейса класса. При разработке классов полезно иметь иерархию абстракций., У вас есть классы низкого уровня, которые используют языковые примитивы для непосредственного выполнения операций и представляют объекты в абстракции, которая находится на более высоком уровне, чем языковые примитивы. У вас есть классы среднего уровня, которые делегируют классам низкого уровня и реализуют операции и представления на более высоком уровне, чем классы низкого уровня. У вас есть классы высокого уровня, которые делегируют классам среднего уровня и реализуют операции и абстракции еще более высокого уровня. (Я говорил здесь только о трех уровнях абстракции, но возможны и другие). Это позволяет вашему коду выражать себя в терминах соответствующих абстракций на каждом уровне, тем самым скрывая сложность. Обоснование Закона Деметрызаключается в том, что если у вас есть цепочка вызовов методов, это предполагает, что у вас есть класс высокого уровня, достигающий через класс среднего уровня, чтобы иметь дело непосредственно с деталями низкого уровня, и, следовательно, ваш класс среднего уровня не предоставил абстрактную операцию среднего уровня что нужно классу высокого уровня. Но похоже, что здесь ситуация не та: вы не проектировали классы в цепочке вызовов методов, они являются результатом некоторого автоматически сгенерированного кода сериализации XML (верно?), А цепочка вызовов не является нисходящей. через иерархию абстракции, потому что десериализованный XML находится на одном уровне иерархии абстракции (верно?)?
Raedwald
источник
3

Чтобы улучшить читаемость, вы можете использовать несколько переменных, например

Foo theFoo;
Bar theBar;
Baz theBaz;

theFoo = wsObject.getFoo();

if ( theFoo == null ) {
  // Exit.
}

theBar = theFoo.getBar();

if ( theBar == null ) {
  // Exit.
}

theBaz = theBar.getBaz();

if ( theBaz == null ) {
  // Exit.
}

return theBaz.getInt();
JimmyB
источник
На мой взгляд, это гораздо менее читабельно. Он засоряет метод целым набором логики проверки нуля, которая совершенно не имеет отношения к реальной логике метода.
Developer102938
2

Не лови NullPointerException. Вы не знаете, откуда он исходит (я знаю, что это маловероятно в вашем случае, но, возможно, что-то еще выбросило его), и это медленно. Вы хотите получить доступ к указанному полю, и для этого все остальные поля не должны быть пустыми. Это прекрасная веская причина проверить каждое поле. Я бы, наверное, проверил это в одном if, а затем создал бы метод для удобочитаемости. Как отмечали другие, уже возвращение -1 - это очень старая школа, но я не знаю, есть ли у вас для этого причина или нет (например, разговаривать с другой системой).

public int callService() {
    ...
    if(isValid(wsObject)){
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    return -1;
}


public boolean isValid(WsObject wsObject) {
    if(wsObject.getFoo() != null &&
        wsObject.getFoo().getBar() != null &&
        wsObject.getFoo().getBar().getBaz() != null) {
        return true;
    }
    return false;
}

Edit: Это спорно , если это disobeyes Закона Деметры , поскольку WsObject вероятно только структура данных (проверка https://stackoverflow.com/a/26021695/1528880 ).

дерма
источник
2

Если вы не хотите реорганизовывать код и можете использовать Java 8, можно использовать ссылки на методы.

Сначала простая демонстрация (извините за статические внутренние классы)

public class JavaApplication14 
{
    static class Baz
    {
        private final int _int;
        public Baz(int value){ _int = value; }
        public int getInt(){ return _int; }
    }
    static class Bar
    {
        private final Baz _baz;
        public Bar(Baz baz){ _baz = baz; }
        public Baz getBar(){ return _baz; }   
    }
    static class Foo
    {
        private final Bar _bar;
        public Foo(Bar bar){ _bar = bar; }
        public Bar getBar(){ return _bar; }   
    }
    static class WSObject
    {
        private final Foo _foo;
        public WSObject(Foo foo){ _foo = foo; }
        public Foo getFoo(){ return _foo; }
    }
    interface Getter<T, R>
    {
        R get(T value);
    }

    static class GetterResult<R>
    {
        public R result;
        public int lastIndex;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
        WSObject wsObjectNull = new WSObject(new Foo(null));

        GetterResult<Integer> intResult
                = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);

        GetterResult<Integer> intResult2
                = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);


        System.out.println(intResult.result);
        System.out.println(intResult.lastIndex);

        System.out.println();
        System.out.println(intResult2.result);
        System.out.println(intResult2.lastIndex);

        // TODO code application logic here
    }

    public static <R, V1, V2, V3, V4> GetterResult<R>
            getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
            {
                GetterResult result = new GetterResult<>();

                Object tmp = value;


                if (tmp == null)
                    return result;
                tmp = g1.get((V1)tmp);
                result.lastIndex++;


                if (tmp == null)
                    return result;
                tmp = g2.get((V2)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g3.get((V3)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g4.get((V4)tmp);
                result.lastIndex++;


                result.result = (R)tmp;

                return result;
            }
}

Вывод

241
4

null
2

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

Метод getterChainпредставляет собой простой шаблонный фрагмент кода, который может быть сгенерирован автоматически (или вручную при необходимости).
Я структурировал код так, чтобы повторяющийся блок был очевиден.


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

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

Маргарет Блум
источник
2

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

Кевин Крамвиде
источник
2

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

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

В коде:

wsObject.getFoo().getBar().getBaz().getInt();

Даже если вы знаете, какое поле имеет значение NULL, вы не знаете, что пошло не так. Может быть, Bar равен нулю, но ожидается ли это? Или это ошибка данных? Подумайте о людях, которые читают ваш код

Как и в ответе xenteros, я бы предложил использовать настраиваемое исключение без проверки . Например, в этой ситуации: Foo может быть нулевым (допустимые данные), но Bar и Baz никогда не должны быть нулевыми (недопустимые данные)

Код можно переписать:

void myFunction()
{
    try 
    {
        if (wsObject.getFoo() == null)
        {
          throw new FooNotExistException();
        }

        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    catch (Exception ex)
    {
        log.error(ex.Message, ex); // Write log to track whatever exception happening
        throw new OperationFailedException("The requested operation failed")
    }
}


void Main()
{
    try
    {
        myFunction();
    }
    catch(FooNotExistException)
    {
        // Show error: "Your foo does not exist, please check"
    }
    catch(OperationFailedException)
    {
        // Show error: "Operation failed, please contact our support"
    }
}
Хоанг Лонг
источник
Непроверенные исключения указывают на то, что программист неправильно использует API. Внешние проблемы, такие как «не удается подключиться к базе данных, исключение ввода-вывода, сетевая ошибка», должны обозначаться отмеченными исключениями.
Кевин Крамвиде,
Это действительно зависит от потребностей звонящего. Проверенная справка по исключению, потому что она заставляет вас обрабатывать ошибку. Однако в других случаях в этом нет необходимости, и это может привести к загрязнению кода. Например, у вас есть исключение IOException на уровне данных, вы бросите его на уровень презентации? Это означает, что вам нужно перехватывать исключение и повторно вызывать каждого вызывающего абонента. Я бы предпочел обернуть IOException настраиваемым BusinessException с соответствующим сообщением и позволить ему пройти через трассировку стека, пока глобальный фильтр не поймает его и не отобразит сообщение пользователю.
Hoàng Long
Вызывающим абонентам не нужно перехватывать и повторно генерировать проверенные исключения, просто объявляйте их выброшенными.
Кевин Крамвиде,
@KevinKrumwiede: вы правы, нам нужно только объявить исключение, которое будет сгенерировано. Однако нам все еще нужно объявить. Изменить: если взглянуть на это второй раз, существует довольно много споров о проверенных и непроверенных использованиях исключений (например: programmers.stackexchange.com/questions/121328/… ).
Hoàng Long
2

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

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

Редактировать:

Я согласен с более поздним ответом от @xenteros, лучше будет запустить собственное исключение, вместо того, чтобы возвращать -1, вы можете его назвать, InvalidXMLExceptionнапример.

SCouto
источник
3
Что вы имеете в виду, говоря «независимо от того, поймаете ли вы это, оно может распространиться на другие части кода»?
Hulk
Если в этом предложении находится нуль wsObject.getFoo () И в последующих частях кода вы снова запускаете этот запрос или используете wsObject.getFoo (). GetBar () (например), он снова вызовет исключение NullPointerException.
SCouto
Это необычная формулировка для выражения «Вам придется перехватывать исключение везде, где вы хотите вызвать метод (или оно будет распространяться вверх по стеку)». если я правильно понимаю. Я согласен с этим (и это может быть проблемой), просто формулировка меня сбивает.
Hulk
Я исправлю это, извините, английский не мой
родной
2

Подписываюсь на этот пост со вчерашнего дня.

Я комментировал / голосовал за комментарии, в которых говорится, что ловить NPE - это плохо. Вот почему я это делаю.

package com.todelete;

public class Test {
    public static void main(String[] args) {
        Address address = new Address();
        address.setSomeCrap(null);
        Person person = new Person();
        person.setAddress(address);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            try {
                System.out.println(person.getAddress().getSomeCrap().getCrap());
            } catch (NullPointerException npe) {

            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000F);
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            if (person != null) {
                Address address1 = person.getAddress();
                if (address1 != null) {
                    SomeCrap someCrap2 = address1.getSomeCrap();
                    if (someCrap2 != null) {
                        System.out.println(someCrap2.getCrap());
                    }
                }
            }
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println((endTime1 - startTime1) / 1000F);
    }
}

  public class Person {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

package com.todelete;

public class Address {
    private SomeCrap someCrap;

    public SomeCrap getSomeCrap() {
        return someCrap;
    }

    public void setSomeCrap(SomeCrap someCrap) {
        this.someCrap = someCrap;
    }
}

package com.todelete;

public class SomeCrap {
    private String crap;

    public String getCrap() {
        return crap;
    }

    public void setCrap(String crap) {
        this.crap = crap;
    }
}

Вывод

3,216

0,002

Я вижу здесь явного победителя. Наличие проверок if намного дешевле, чем перехват исключения. Я видел, как это делается в Java-8. Учитывая, что 70% текущих приложений все еще работают на Java-7, я добавляю этот ответ.

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

Новый пользователь
источник
Три дополнительных секунды на миллион запросов в худшем случае поддаются измерению, но это редко может помешать сделке, даже в «критически важных приложениях». Существуют системы, в которых добавление 3,2 микросекунды к запросу является большим делом, и если у вас есть такая система, обязательно подумайте об исключениях. Но вызов веб-службы и десериализация ее вывода, в соответствии с исходным вопросом, вероятно, займет гораздо больше времени, и беспокоиться о производительности обработки исключений не имеет значения.
Jeroen Mostert
@JeroenMostert: 3 секунды на чек / миллион. Значит, количество проверок увеличит стоимость
NewUser
Правда. Но даже с учетом этого я все равно считаю это случаем «сначала профиль». Вам потребуется более 300 проверок в одном запросе, прежде чем запрос займет дополнительную миллисекунду. Дизайнерские соображения давят на мою душу гораздо раньше.
Jeroen Mostert
@JeroenMostert: :) Согласен! Я хотел бы оставить это программисту с результатом и позволить им позвонить!
NewUser
1

Если эффективность является проблемой, следует рассмотреть вариант «улова». Если 'catch' нельзя использовать, потому что он будет распространяться (как указано в 'SCouto'), тогда используйте локальные переменные, чтобы избежать множественных вызовов методов getFoo(), getBar()и getBaz().

СЭП
источник
1

Стоит подумать о создании собственного исключения. Назовем это MyOperationFailedException. Вы можете выбросить его вместо того, чтобы вернуть значение. Результат будет таким же - вы выйдете из функции, но не вернете жестко запрограммированное значение -1, которое является антипаттерном Java. В Java мы используем исключения.

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    throw new MyOperationFailedException();
}

РЕДАКТИРОВАТЬ:

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

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

Если это приемлемо, вам все равно, где появился этот нуль. Если вы это сделаете, вам определенно не следует связывать эти запросы.

xenteros
источник
2
Вам не кажется, что подавление исключения - плохая идея? В реальном времени, если мы потеряем след исключения, настоящая боль внизу - выяснить, что, черт возьми, происходит! Я всегда предлагал не использовать цепочку. Вторая проблема, которую я вижу: этот код не может быть предоставлен в определенный момент времени, какой из результатов был нулевым.
NewUser
Нет, ваше исключение может иметь сообщение, которое обязательно укажет место, где оно было брошено. Я согласен, что создание цепочки - не лучшее решение :)
xenteros
3
Нет, это было бы просто о номере строки. Итак, любой из вызовов в цепочке может привести к исключению.
NewUser
«Если это ошибка, и она возникает, вы можете отладить свой код» - не в производстве. Я бы предпочел знать, ЧТО не удалось, когда все, что у меня есть, это журнал, чем пытаться угадать, что произошло, что привело к неудаче. С этим советом (и этим кодом) все, что вы действительно знаете, это то, что одна из четырех вещей была нулевой, но не какая из них и почему.
ВЛАЗ
1

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

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

private int getFooBarBazInt() {
    if (wsObject.getFoo() == null) return -1;
    if (wsObject.getFoo().getBar() == null) return -1;
    if (wsObject.getFoo().getBar().getBaz() == null) return -1;
    return wsObject.getFoo().getBar().getBaz().getInt();
}

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

Когда вы общаетесь с удаленной веб-службой, очень типично иметь «удаленный домен» и «домен приложения» и переключаться между ними. Удаленный домен часто ограничен веб-протоколом (например, вы не можете отправлять вспомогательные методы туда и обратно в чистой службе RESTful, а глубоко вложенные объектные модели являются обычным явлением, чтобы избежать множественных вызовов API) и поэтому не идеальны для прямого использования в ваш клиент.

Например:

public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}
аллюр
источник
1
return wsObject.getFooBarBazInt();

применяя Закон Деметры,

class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}
Khaled.K
источник
0

Давать ответ, который кажется отличным от всех остальных.

Я рекомендую вам проверить NULLв ifс.

Причина:

Мы не должны оставлять ни единого шанса, что наша программа выйдет из строя. NullPointer генерируется системой. Поведение исключений, созданных Системой, невозможно предсказать . Не следует оставлять свою программу в руках Системы, если у вас уже есть способ справиться с ней самостоятельно. И поместите механизм обработки исключений для дополнительной безопасности. !!

Чтобы упростить чтение кода, попробуйте это для проверки условий:

if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
   return -1;
else 
   return wsObject.getFoo().getBar().getBaz().getInt();

РЕДАКТИРОВАТЬ :

Здесь вам нужно сохранить эти значения wsObject.getFoo(), wsObject.getFoo().getBar(), wsObject.getFoo().getBar().getBaz()в некоторых переменных. Я не делаю этого, потому что не знаю возвращаемые типы этих функций.

Любые предложения будут оценены .. !!

Джанки Гадхия
источник
Вы считали getFoo () очень трудоемкой операцией? Вы должны хранить возвращаемые значения в переменных, однако это пустая трата памяти. Ваш метод идеально подходит для программирования на C.
xenteros
но иногда лучше быть на 1 миллисекунду позже, чем программа вылетает @xenteros .. !!
Janki Gadhiya
getFoo () может получить значение с сервера, расположенного на другом континенте. Это может длиться в любое время: минут / часов ...
xenteros
wsObjectбудет содержать значение, возвращаемое веб-сервисом .. !! Сервис уже будет вызван и wsObjectполучит длинные XMLданные в качестве ответа веб- сервиса .. !! Таким образом, нет ничего лучше сервера, расположенного на другом континенте, потому что getFoo()это просто элемент, получающий метод получения, а не вызов веб-службы .. !! @xenteros
Janki Gadhiya
1
Судя по именам получателей, я бы предположил, что они возвращают объекты Foo, Bar и Baz: P Также подумайте об удалении упомянутой двойной безопасности из вашего ответа. Я не думаю, что это дает реальную ценность, кроме загрязнения кода. С разумными локальными переменными и нулевой проверкой мы сделали более чем достаточно для обеспечения правильности кода. Если может возникнуть исключение, его следует рассматривать как одно.
Marius K.
0

Я написал класс, Snagкоторый позволяет вам определять путь для навигации по дереву объектов. Вот пример его использования:

Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();

Это означает, что экземпляр ENGINE_NAMEбудет эффективно вызывать Car?.getEngine()?.getName()переданный ему экземпляр и возвращать его, nullесли возвращается какая-либо ссылка null:

final String name =  ENGINE_NAME.get(firstCar);

Он не опубликован на Maven, но если кому-то это пригодится, он здесь (конечно, без гарантии!)

Это немного простовато, но, похоже, работает. Очевидно, что он более устарел с более поздними версиями Java и других языков JVM, которые поддерживают безопасную навигацию или Optional.

Богатый
источник