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

Проверить цепочки вызовов "get" на null

Скажем, я бы хотел выполнить следующую команду:

house.getFloor(0).getWall(WEST).getDoor().getDoorknob();

Чтобы избежать исключения NullPointerException, я должен был бы сделать следующее, если:

if (house != null && house.getFloor(0) && house.getFloor(0).getWall(WEST) != null
  && house.getFloor(0).getWall(WEST).getDoor() != null) ...

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

checkForNull(house.getFloor(0).getWall(WEST).getDoor().getDoorknob());
4b9b3361

Ответ 1

Лучший способ - избежать цепочки. Если вы не знакомы с Законом Деметры (LoD), на мой взгляд, вы должны. Вы дали прекрасный пример цепочки сообщений, которая слишком близка с классами, в которых у нее нет бизнеса, зная о чем-либо.

Закон Деметры: http://en.wikipedia.org/wiki/Law_of_Demeter

Ответ 2

В случае, если вы не можете избежать нарушения Закона Деметры (LoD), как указано в выбранном ответе, и с Java 8, вводящим Optional, вероятно, будет наилучшей практикой обрабатывать нули в цепочках получения, таких как ваша.

Optional тип позволит вам передавать несколько операций с картой (которые содержат вызовы get) подряд. Нулевые чеки автоматически обрабатываются под капотом.

Например, когда объекты не инициализированы, print() не будет выполняться, и никакие исключения не будут выброшены. Все это мы бережно берем под капот. Когда объекты инициализированы, печать будет сделана.

System.out.println("----- Not Initialized! -----");

Optional.ofNullable(new Outer())
        .map(out -> out.getNested())
        .map(nest -> nest.getInner())
        .map(in -> in.getFoo())
        .ifPresent(foo -> System.out.println("foo: " + foo)); //no print

System.out.println("----- Let Initialize! -----");

Optional.ofNullable(new OuterInit())
        .map(out -> out.getNestedInit())
        .map(nest -> nest.getInnerInit())
        .map(in -> in.getFoo())
        .ifPresent(foo -> System.out.println("foo: " + foo)); //will print!

class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
class Inner {
    String foo = "yeah!";
    String getFoo() {
        return foo;
    }
}

class OuterInit {
    NestedInit nested = new NestedInit();
    NestedInit getNestedInit() {
        return nested;
    }
}
class NestedInit {
    InnerInit inner = new InnerInit();
    InnerInit getInnerInit() {
        return inner;
    }
}
class InnerInit {
    String foo = "yeah!";
    String getFoo() {
        return foo;
    }
}

Итак, с вашей цепочкой геттеров это будет выглядеть так:

Optional.ofNullable(house)
        .map(house -> house.getFloor(0))
        .map(floorZero -> floorZero.getWall(WEST))
        .map(wallWest -> wallWest.getDoor())
        .map(door -> wallWest.getDoor())

Возврат этого будет что-то вроде Optional<Door> что позволит вам гораздо безопаснее работать, не беспокоясь о пустых исключениях.

Ответ 3

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

public static <T> T opt(Supplier<T> statement) {       
    try {
        return statement.get();
    } catch (NullPointerException exc) {
        return null;
    }   
}

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

Doorknob knob = opt(() -> house.getFloor(0).getWall(WEST).getDoor().getDoorknob());

Этот код также является типобезопасным и в целом работает как задумано:

  1. Возвращает фактическое значение указанного типа, если все объекты в цепочке не равны NULL.
  2. Возвращает ноль, если какой-либо из объектов в цепочке равен нулю.

Вы можете поместить метод opt в разделяемый класс util и использовать его везде в своем приложении.

Ответ 4

Конечно, вы можете просто обернуть все выражение в блок try-catch, но это плохая идея. Что-то более чистое - это объектный объект Null. При этом, если в вашем доме нет этажа 0, он просто возвращает этаж, который действует как обычный этаж, но не имеет реального содержания; Этажи, когда их спрашивают о Стенах, которых у них нет, возвращают аналогичные "Нулевые" Стены и т.д. По линии.

Ответ 5

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

if (wall.hasDoor()) {
   wall.getDoor().etc();
}

Это, по сути, нуль-проверка, но может и не всегда быть.

Дело в том, что вы должны что-то сделать, если у вас есть null. Например - return или запустите IllegalStateException

И что вам не следует делать - не поймайте NullPointerException. Исключения для выполнения не предназначены для ловли - не ожидается, что вы сможете оправиться от них, и не следует полагаться на исключения для логического потока. Представьте, что вы действительно не ожидаете, что что-то будет null, и вы поймаете (и запишите) a NullPointerException. Это не будет очень полезной информацией, так как в этом случае многое может быть null.

Ответ 6

Нет метода checkForNull, который вы можете написать, что облегчит это (это просто не то, как метод invokation и оценка аргументов работает в Java).

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

Связанные вопросы

Ответ 7

Очень старый вопрос, но все еще добавляю мое предложение:

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

Ответ 8

реализуя nullPointer try/catch с поставщиком, вы можете отправить его по всей цепочке получения

public static <T> T getValue(Supplier<T> getFunction, T defaultValue) {
    try {
        return getFunction.get();
    } catch (NullPointerException ex) {
        return defaultValue;
    }
}

и затем назовите это таким образом.

ObjectHelper.getValue(() -> object1.getObject2().getObject3().getObject4()));

Ответ 9

Вы можете поддержать это в java (только для добавления синтаксического сахара), используя тейкейк simillar к тому, который используется Mockito:

org.mockito.Mockito.spy(house).getFloor(0).getWall(WEST).getDoor().getDoorknob()

или CatchException

com.googlecode.catchexception.CatchException
    .catchException(house).getFloor(0).getWall(WEST).getDoor().getDoorknob()

Mockito не может использоваться напрямую, поскольку выполняется проверка на отсутствие нулевого объекта и генерируется исключение, а CatchException - для другой цели.

Тем не менее, возможно, кто-то может предложить, где можно найти такую ​​функциональность.