Вопрос-ответ

Check chains of "get" calls for null

Проверка цепочек вызовов "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());
Переведено автоматически
Ответ 1

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

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

Например, когда объекты не инициализированы, функция 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's 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;
}
}

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

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

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

Ответ 2

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

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. Возвращает null, если какой-либо из объектов в цепочке является null.

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

Ответ 3

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

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

Ответ 4

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

java