Нульова перевірка ланцюга проти лову 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.
Том

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 .


Чи відсутність помилки чи помилка?

Але також подумайте, чи це дійсний результат для проміжних методів повернення нуля чи це ознака помилки. Якщо це завжди помилка, то, ймовірно, краще кинути виняток, ніж повертати спеціальне значення, або для самих проміжних методів кидати виняток.


Можливо, додаткові опції?

Якщо, з іншого боку, відсутні значення значень проміжних методів дійсні, можливо, ви можете перейти до Optional s для них?

Тоді ви можете використовувати їх так:

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

Чому б не обов’язково?

Єдина причина, яку я можу подумати за те, що я не використовую, Optionalце те, що це справді критична частина коду, і якщо накладні витрати на збирання сміття виявляються проблемою. Це відбувається тому, що при Optionalкожному виконанні коду виділяється декілька об'єктів, і VM, можливо, не зможе їх оптимізувати. У такому випадку ваш оригінальний тест if може бути кращим.


Дуже гарна відповідь. Слід зазначити, що якщо є інші можливі умови відмови, і вам потрібно розрізняти їх, ви можете використовувати Tryзамість них Optional. Хоча немає Tryв API Java, існує багато ЛІЕСА , що забезпечує один, наприклад , javaslang.io , github.com/bradleyscollins/try4j , functionaljava.org або github.com/jasongoodwin/better-java-monads
Landei

8
FClass::getBarтощо буде коротше.
Борис Павук

1
@BoristheSpider: Можливо, трохи. Але я зазвичай віддаю перевагу лямбдам методам refs, тому що часто назви класів набагато довші, і мені здається, що лямбди трохи легше читати.
Лій

6
@Lii досить справедливо, але зауважте, що посилання на метод може бути трохи більш швидким, оскільки лямбда може зажадати більш складних конструкцій часу компіляції. Лямбда потребує вироблення staticметоду, який понесе дуже незначне покарання.
Борис Павук

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 розробила механізм винятків, щоб уникнути цього старомодного способу, який походить з мови С.

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


1
Objects.requireNonNullврешті-решт кидає NullPointerException. Тож це не робить ситуацію return wsObject.getFoo().getBar().getBaz().getInt()
Арка Гош

1
@ArkaGhosh, також він уникає безлічі ifs, як показав OP
Андрій Тобілко,

4
Це єдине розумне рішення. Всі інші радять використовувати винятки для контролю потоку, який є кодовим запахом. Зі сторони: Я вважаю, що метод ланцюга, зроблений ОП, також запах. Якби він працював з трьома локальними змінними та відповідною, якби ситуація була б набагато зрозумілішою. Крім того, я думаю, що проблема є глибшою, ніж просто заощадження NPE: ОП повинен запитати себе, чому одержувачі можуть повернути нуль. Що означає null? Може, якийсь нульовий об’єкт буде кращим? Або врізатися в один геттер зі значущим винятком? В основному все краще, ніж винятки для контролю потоку.
Маріус К.

1
Безумовна порада використовувати винятки, щоб сигналізувати про відсутність дійсного значення повернення, не дуже добре. Винятки корисні, коли метод не вдається таким чином, що абоненту важко відновитись, і який краще обробляти в операторі try-catch у якійсь іншій частині програми. Щоб просто сигналізувати про відсутність зворотного значення, краще скористатися Optionalкласом, а може, і повернути нульовийInteger
Лій

6

Якщо припустити, що структура класу насправді виходить з-під нашого контролю, як це здається, я вважаю, що прийняття NPE, як було запропоновано у питанні, справді є розумним рішенням, якщо продуктивність не викликає особливих проблем. Одне невелике поліпшення може полягати в обробці логіки кидка / лову, щоб уникнути захаращення:

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

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


4
Це дискусійно, якщо він не дотримується Закону Деметера, оскільки WsObject - це, мабуть, лише структура даних. Дивіться тут: stackoverflow.com/a/26021695/1528880
Derm

2
@DerM Так, це можливо, але оскільки в ОП вже є щось, що аналізує його XML-файл, він також може подумати про створення відповідних модельних класів для необхідних тегів, тож бібліотека розбору може відобразити їх. Тоді ці класи моделей містять логіку для nullперевірки власних підтегів.
Том

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вказує значення недійсне введення у вашу програму? Якщо це так, a NullPointerExceptionне є відповідним винятком для кидання, оскільки, як правило, він зарезервований для вказівки на помилки. Ви , ймовірно , хочете , щоб кинути призначене для користувача виняток , отримане з IllegalArgumentException(якщо ви хочете неперевірене виняток ) або IOException(якщо ви хочете перевіряється виключення). Чи потрібна ваша програма для надання детальних повідомлень про помилки синтаксису, коли недійсне введення? Якщо так, перевірка кожного методу на nullповернене значення, то викидання відповідного діагностичного винятку - єдине, що ви можете зробити. Якщо вашій програмі не потрібно надавати детальну діагностику, чітке та стислое чітке NullPointerExceptionта стисне вилучення методу виклику методів, спіймання будь-яких, а потім викидання спеціального винятку.

Один із відповідей стверджує, що ланцюгові виклики методу порушують Закон Деметера і, отже, погані. Це твердження помилкове.

  • Що стосується розробки програм, то насправді немає абсолютних правил щодо того, що добре, а що погано. Є лише евристика: правила, які є правильними (навіть майже весь) часу. Частина навичок програмування - це знати, коли нормально порушувати ці види правил. Таким чином, хитке твердження, що "це проти правила X " насправді взагалі не є відповіддю. Це одна з ситуацій, коли це правило повинно бути порушувати?
  • Закон Деметри дійсно правило про API або дизайн інтерфейсу класу. При проектуванні класів корисно мати ієрархію абстракцій. У вас є класи низького рівня, які використовують мовні примітиви, щоб безпосередньо виконувати операції та представляти об'єкти в абстракції, що є вищим рівнем, ніж мовні примітиви. У вас є класи середнього рівня, які делегуються класам низького рівня та реалізують операції та представлення на більш високому рівні, ніж класи низького рівня. У вас є класи високого рівня, які делегуються класам середнього рівня та реалізують ще операції та абстракції вищого рівня. (Я тут говорив про лише три рівні абстракції, але більше можливих). Це дозволяє вашому коду виразити себе у відповідних абстракціях на кожному рівні, тим самим приховуючи складність. Обґрунтування Закону Деметраце те, що якщо у вас є ланцюжок викликів методів, це говорить про те, що у вас є клас високого рівня, який досягає через клас середнього рівня, щоб безпосередньо працювати з деталями низького рівня, а отже, ваш клас середнього рівня не надав абстрактних операцій середнього рівня що потрібен клас високого рівня. Але здається, що це не така ситуація, яку ви тут маєте: ви не проектували класи у ланцюжку викликів методів, вони є результатом деякого автоматично сформованого коду серіалізації XML (правда?), А ланцюжок викликів не спадає через ієрархію абстракцій, оскільки десеріалізована XML знаходиться на одному рівні ієрархії абстракцій (правда?)?

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

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

2

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

Редагувати: Суперечливо, якщо він не дотримується Закону Деметера, оскільки 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

нульовий
2

Інтерфейс Getter- це лише функціональний інтерфейс, ви можете використовувати будь-який еквівалент.
GetterResultклас, аксесуари, позбавлені ясності, містять результат ланцюга геттера, якщо такий є, або індекс останнього викликаного геттера.

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


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

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


2

Як говорили інші, повага до Закону про Деметер, безумовно, є частиною рішення. Інша частина, де це можливо, полягає в зміні цих прикованих методів, щоб вони не могли повернутися null. Ви можете уникнути повернення null, замість того, щоб повернути порожній String, порожній Collectionчи якийсь інший фіктивний об'єкт, який означає або робить все, що зробив би абонент null.


2

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

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

У коді:

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

Навіть коли ви знаєте, яке поле недійсне, ви не маєте уявлення про те, що йде не так. Можливо, Бар недійсний, але чи очікується це? Або це помилка даних? Подумайте про людей, які читають ваш код

Як і у відповіді 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 відповідним повідомленням і дозволити йому проскакувати через стек, поки глобальний фільтр не застане його і не відобразить повідомлення користувачеві.
Хоанг Лонг

Абонентам не потрібно ловити і повторно кидати перевірені винятки, просто оголосити їх кинутими.
Кевін Крумвіде

@KevinKrumwiede: ви праві, нам потрібно лише оголосити виняток, який буде кинутий. Нам все ж потрібно заявити. Редагувати: Подивившись на це, існує досить багато дебатів щодо перевірених та неперевірених винятків звичаїв (наприклад: programmers.stackexchange.com/questions/121328/… ).
Хоанг Лонг

2

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

Виняток доведеться ловити там, де ви хочете викликати метод (або він поширюватиме стек). Тим не менш, якщо у вашому випадку ви можете продовжувати працювати з цим результатом зі значенням -1, і ви впевнені, що він не поширюватиметься, оскільки ви не використовуєте жодного з "шматочків", які можуть бути недійсними, то мені здається, що правильно зловити його

Редагувати:

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


3
Що ви маєте на увазі під словом "незалежно від того, якщо ви його зловили, він може поширюватися на інші частини коду"?
Халк

Якщо null є в цьому реченні wsObject.getFoo (), а в наступних частинах коду ви знову запустите цей запит або використовуєте wsObject.getFoo (). GetBar () (наприклад), він знову підніме NullPointerException.
SCouto

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

Я виправлю це, вибачте, англійська мова не є моєю першою мовою, тому це може траплятися іноді :) Дякую
SCouto

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

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

Підсумкові дані Для будь-яких найважливіших застосувань, необхідних для роботи з NPE, це дорого.


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

@JeroenMostert: 3 секунди за чек / мільйон. Отже, кількість перевірок збільшить вартість
NewUser

Правда. Навіть незважаючи на це, я все-таки вважаю це випадком "профілю першим", хоча. Вам потрібно буде понад 300 перевірок в одному запиті, перш ніж запит займе цілих мільйсекунд. Дизайнерські міркування зважилися б на мою душу набагато раніше.
Джероен Мостерт

@JeroenMostert: :) Згода! Я хотів би залишити це програмісту з результатом і дозволити їм зателефонувати!
Новий користувач

1

Якщо ефективність є проблемою, тоді слід розглянути варіант "лову". Якщо «зловити» не може бути використаний , так як він буде поширюватися (як уже згадувалося «SCouto») , а потім використовувати локальні змінні , щоб уникнути декількох викликів методів getFoo(), getBar()і getBaz().


1

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

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

Редагувати:

Відповідно до обговорення в коментарях, дозвольте мені додати щось до своїх попередніх думок. У цьому коді є дві можливості. Одне полягає в тому, що ви приймаєте null, а інше - це помилка.

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

Якщо це прийнятно, то вам не байдуже, де з’явилася ця нуль. Якщо це зробити, ви точно не повинні зв'язувати ці запити.


2
Ви не вважаєте, що придушувати виняток погано? У реальному часі, якщо ми втрачаємо слід про виняток, його справжній біль внизу, щоб з’ясувати, що, до біса, відбувається! Я завжди пропоную не використовувати ланцюжок. Друга проблема, яку я бачу, полягає в тому, що цей код не може отримати певний момент часу, результат якого був недійсним.
NewUser

Ні, ваш виняток може мати повідомлення, яке, безумовно, вказувало б місце, куди воно було кинуто. Я згоден, що ланцюжок не найкраще рішення :)
xenteros

3
Ні, це просто сказало б про номер рядка. Отже, будь-який з дзвінків у ланцюжку може призвести до винятку.
Новий користувач

"Якщо це помилка і вона виникає, ви можете налагодити код" - не у виробництві. Я набагато скоріше знаю, ЩО не вдалося, коли все, що я маю, - це журнал, ніж намагатися божественно ставитись до того, що сталося. З урахуванням цього поради (і цього коду), все, що ви насправді знаєте, - це те, що одна з чотирьох речей була недійсною, але не та, яка та чому.
ВЛАЗ

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

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

Під час спілкування з віддаленою веб-службою дуже типово мати "віддалений домен" і "домен програми" та перемикатися між ними. Віддалений домен часто обмежений веб-протоколом (наприклад, ви не можете надсилати допоміжні методи туди-сюди за допомогою чистого сервісу 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;
    }
}

0

Давати відповідь, яка здається відмінною від усіх інших.

Рекомендую зареєструватися NULLв ifs.

Причина:

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

але колись краще затриматись на 1 мілісекунд, тоді програма виходить з ладу @xenteros .. !!
Janki Gadhiya

getFoo () може отримати значення від сервера, розташованого на іншому континенті. Це може тривати будь-який час: хвилини / години…
xenteros

wsObjectбуде містити значення, повернене з Webservice .. !! Служба вже буде викликана і wsObjectотримає довгі XMLдані у відповідь веб-сервісу .. !! Таким чином, немає нічого подібного до сервера, розташованого на іншому континенті, тому що getFoo()це лише метод отримання методу getter, а не виклик Webservice .. !! @xenteros
Janki Gadhiya

1
Добре, що з назви дійових осіб я б припустив, що вони повертають об'єкти Foo, Bar та Baz: P Також слід розглянути питання про видалення згаданої подвійної безпеки з вашої відповіді. Я не думаю, що це забезпечує реальну цінність, окрім забруднення коду. За допомогою нормальних локальних змінних та перевірки нуля ми зробили більш ніж достатньо, щоб забезпечити правильність коду. Якщо може статися виняток, його слід розглядати як один.
Маріус К.

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.