Null check chain vs catching NullPointerException

Веб-служба возвращает огромный 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;
}

или это считается антипаттерном?

Ответ 1

Захват 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 an -1 int instead of an empty optional if any is null
        // .orElse(-1);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

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

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

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


Является ли отсутствие действительным или ошибкой?

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


Возможно, больше опций?

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

Затем вы можете использовать их следующим образом:

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

Почему не факультативно?

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

Ответ 2

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

import static java.util.Objects.requireNonNull;
...
return requireNonNull(requireNonNull(requireNonNull(
           wsObject, "wsObject is null")
             .getFoo(), "getFoo() is null")
                .getBar(), "getBar() is null");

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

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

Ответ 3

Как уже отмечалось в Tom,

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

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

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

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

Ответ 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, если есть вероятность того, что этот объект будет null.

Ответ 5

Предполагая, что структура класса действительно находится вне нашего контроля, как кажется, я считаю, что уловка 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);

Ответ 6

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

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

Ответ 7

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

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

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

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

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

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

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

Ответ 8

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

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

Edit:

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

Ответ 9

Не ловите 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;
}

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

Ответ 10

Если вы не хотите реорганизовывать код, и вы можете использовать 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, getters.

Ответ 11

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

Ответ 12

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

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

В коде:

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

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

Как и в случае 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"
    }
}

Ответ 13

Следили за этим сообщением со вчерашнего дня.

Я комментирую/голосую комментарии, которые говорят, что улавливать 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, я добавляю этот ответ.

Bottom Line Для любых критически важных приложений обработка NPE является дорогостоящей.

Ответ 14

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

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

EDIT:

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

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

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

Ответ 15

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

Ответ 16

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

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

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

}

Ответ 17

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

Ответ 18

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

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

Причина:

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

Для упрощения чтения кода попробуйте это для проверки условий:

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

EDIT:

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

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

Ответ 19

Я написал класс под названием 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.