Подтвердить что ты не робот

Java: не проверяйте значение null во вложенных классах (проверка Deep Null)

Представьте, что у меня есть класс Family. Он содержит список лиц. Каждый (класс) человек содержит (класс) адрес. Каждый (класс) Адрес содержит (класс) PostalCode. Любой "промежуточный" класс может быть нулевым.

Итак, есть ли простой способ добраться до PostalCode без проверки на нуль на каждом шагу? то есть есть ли способ избежать следующего кода цепи ввода-вывода? Я знаю, что нет "родного" Java-решения, но надеялся, что кто-нибудь знает библиотеку или что-то в этом роде. (проверил Commons и Guava и ничего не видел)

if(family != null) {
    if(family.getPeople() != null) {
        if(family.people.get(0) != null) {
            if(people.get(0).getAddress() != null) {
                if(people.get(0).getAddress().getPostalCode() != null) {
                    //FINALLY MADE IT TO DO SOMETHING!!!
                }
            }
        }
    }
}

Нет, не может изменить структуру. Это от службы, которой я не контролирую.

Нет, я не могу использовать Groovy и это удобный оператор "Элвис".

Нет, я бы предпочел не ждать Java 8: D

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

Идеи?

Спасибо

- Страница llappall

4b9b3361

Ответ 1

Ваш код ведет себя так же, как

if(family != null &&
  family.getPeople() != null &&
  family.people.get(0) != null && 
  family.people.get(0).getAddress() != null &&
  family.people.get(0).getAddress().getPostalCode() != null) { 
       //My Code
}

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

Ответ 2

Самое близкое, что вы можете получить, это использовать правила коротких выводов в условных выражениях:

if(family != null && family.getPeople() != null && family.people.get(0) != null  && family.people.get(0).getAddress() != null && family.people.get(0).getAddress().getPostalCode() != null) {
                    //FINALLY MADE IT TO DO SOMETHING!!!

}

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

Ответ 3

Если это редко, вы можете игнорировать проверки null и полагаться на NullPointerException. "Редкий" из-за возможной проблемы с производительностью (зависит, как правило, будет заполнять трассировку стека, которая может быть дорогостоящей).

Кроме этого 1) конкретный вспомогательный метод, который проверяет нуль, чтобы очистить этот код или 2) Сделать общий подход, используя отражение, и строку, например:

checkNonNull(family, "people[0].address.postalcode")

Выполнение выполнено как упражнение.

Ответ 4

Не такая классная идея, но как насчет того, чтобы поймать исключение:

    try 
    {
        PostalCode pc = people.get(0).getAddress().getPostalCode();
    }
    catch(NullPointerException ex)
    {
        System.out.println("Gotcha");
    }

Ответ 5

Вместо использования null вы можете использовать некоторую версию шаблона проектирования "нулевой объект". Например:

public class Family {
    private final PersonList people;
    public Family(PersonList people) {
        this.people = people;
    }

    public PersonList getPeople() {
        if (people == null) {
            return PersonList.NULL;
        }
        return people;
    }

    public boolean isNull() {
        return false;
    }

    public static Family NULL = new Family(PersonList.NULL) {
        @Override
        public boolean isNull() {
            return true;
        }
    };
}


import java.util.ArrayList;

public class PersonList extends ArrayList<Person> {
    @Override
    public Person get(int index) {
        Person person = null;
        try {
            person = super.get(index);
        } catch (ArrayIndexOutOfBoundsException e) {
            return Person.NULL;
        }
        if (person == null) {
            return Person.NULL;
        } else {
            return person;
        }
    }
    //... more List methods go here ...

    public boolean isNull() {
        return false;
    }

    public static PersonList NULL = new PersonList() {
        @Override
        public boolean isNull() {
            return true;
        }
    };
}

public class Person {
    private Address address;

    public Person(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        if (address == null) {
            return Address.NULL;
        }
        return address;
    }
    public boolean isNull() {
        return false;
    }

    public static Person NULL = new Person(Address.NULL) {
        @Override
        public boolean isNull() {
            return true;
        }
    };
}

etc etc etc

Тогда ваш оператор if может стать:

if (!family.getPeople().get(0).getAddress().getPostalCode.isNull()) {...}

Он субоптимален, так как:

  • Вы застряли в создании объектов NULL для каждого класса,
  • Трудно сделать эти общие объекты, поэтому вы застряли в создании нулевой версии каждого списка, карты и т.д., которые вы хотите использовать, и
  • Есть потенциально некоторые смешные проблемы с подклассом и который NULL использовать.

Но если вы действительно ненавидите свой == null s, это выход.

Ответ 6

Я просто искал одно и то же (мой контекст: куча автоматически созданных классов JAXB, и как-то у меня эти длинные цепочки-макеты .getFoo().getBar().... Неизменно, время от времени один из вызовов в середине return null, вызывающий NPE.

Что-то, с чего я начинал возиться с тобой, основано на размышлении. Я уверен, что мы можем сделать это красивее и эффективнее (кэширование отражения, с одной стороны, а также определение "магических" методов, таких как ._all, для автоматической итерации по всем элементам коллекции, если какой-то метод в середине возвращает коллекцию). Не красиво, но, возможно, кто-то может сказать нам, есть ли там что-то лучше:

/**
 * Using {@link java.lang.reflect.Method}, apply the given methods (in daisy-chain fashion)
 * to the array of Objects x.
 * 
 * <p>For example, imagine that you'd like to express:
 * 
 * <pre><code>
 * Fubar[] out = new Fubar[x.length];
 * for (int i=0; {@code i<x.length}; i++) {
 *   out[i] = x[i].getFoo().getBar().getFubar();
 * }
 * </code></pre>
 * 
 * Unfortunately, the correct code that checks for nulls at every level of the
 * daisy-chain becomes a bit convoluted.
 * 
 * <p>So instead, this method does it all (checks included) in one call:
 * <pre><code>
 * Fubar[] out = apply(new Fubar[0], x, "getFoo", "getBar", "getFubar");
 * </code></pre>
 * 
 * <p>The cost, of course, is that it uses Reflection, which is slower than
 * direct calls to the methods.
 * @param type the type of the expected result
 * @param x the array of Objects
 * @param methods the methods to apply
 * @return
 */
@SuppressWarnings("unchecked")
public static <T> T[] apply(T[] type, Object[] x, String...methods) {
    int n = x.length;
    try {
        for (String methodName : methods) {
            Object[] out = new Object[n];
            for (int i=0; i<n; i++) {
                Object o = x[i];
                if (o != null) {
                    Method method = o.getClass().getMethod(methodName);
                    Object sub = method.invoke(o);
                    out[i] = sub;
                }
            }
            x = out;
        }
    T[] result = (T[])Array.newInstance(type.getClass().getComponentType(), n);
    for (int i=0; i<n; i++) {
            result[i] = (T)x[i];
    }
            return result;
    } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new RuntimeException(e);
    }
}

Ответ 7

Если в случае использования java8 вы можете использовать;

resolve(() -> people.get(0).getAddress().getPostalCode());
    .ifPresent(System.out::println);

:
public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

REF: избегать нулевых проверок

Ответ 8

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

В двух словах:

end: {
   List<People> people = family.getPeople();            if(people == null || people.isEmpty()) break end;
   People person = people.get(0);                       if(person == null) break end;
   Address address = person.getAddress();               if(address == null) break end;
   PostalCode postalCode = address.getPostalCode();     if(postalCode == null) break end;

   System.out.println("Do stuff");
}

Поскольку существует много устаревшего кода, который все еще используется, использование Java 8 и Optional не всегда является опцией.

Всякий раз, когда есть глубоко вложенные классы (JAXB, SOAP, JSON, вы называете это...) и Закон Деметры isn ' t, вы в основном должны проверить все и посмотреть, есть ли потенциальные NPE, скрывающиеся вокруг.

Мое предлагаемое решение стремится к удобочитаемости и не должно использоваться, если не задействовано не менее 3 или более вложенных классов (когда я говорю вложенное, я не имею в виду Вложенные классы в формальном контексте). Поскольку код читается больше, чем он написан, быстрый взгляд на левую часть кода сделает его смысл более понятным, чем использование глубоко вложенных операторов if-else.

Если вам нужна часть else, вы можете использовать этот шаблон:

boolean prematureEnd = true;

end: {
   List<People> people = family.getPeople();            if(people == null || people.isEmpty()) break end;
   People person = people.get(0);                       if(person == null) break end;
   Address address = person.getAddress();               if(address == null) break end;
   PostalCode postalCode = address.getPostalCode();     if(postalCode == null) break end;

   System.out.println("Do stuff");
   prematureEnd = false;
}

if(prematureEnd) {
    System.out.println("The else part");
}

Некоторые IDE нарушают это форматирование, если вы не поручите им не делать (см. этот вопрос).

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

Еще одно: ваш код по-прежнему подвержен поломке. Вы должны использовать if(family.getPeople() != null && !family.getPeople().isEmpty()) в качестве первой строки в вашем коде, иначе пустой список будет вызывать NPE.

Ответ 9

Вы можете использовать для:

product.getLatestVersion().getProductData().getTradeItem().getInformationProviderOfTradeItem().getGln();

необязательный эквивалент:

Optional.ofNullable(product).map(
            Product::getLatestVersion
        ).map(
            ProductVersion::getProductData
        ).map(
            ProductData::getTradeItem
        ).map(
            TradeItemType::getInformationProviderOfTradeItem
        ).map(
            PartyInRoleType::getGln
        ).orElse(null);