提问人: 提问时间:8/11/2010 最后编辑:Hubert Kario 更新时间:2/15/2023 访问量:49066
检查“get”调用链是否为 null
Check chains of "get" calls for 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());
答:
当然,你可以简单地将整个表达式包装在一个 try-catch 块中,但这是一个坏主意。更简洁的是 Null 对象模式。这样一来,如果你的房子没有楼层 0,它只会返回一个 Floor,它的行为类似于普通 Floor,但没有实际内容;当楼层被要求提供他们没有的墙时,会返回类似的“空”墙,等等。
评论
最好的方法是避免链条。如果你不熟悉得墨忒耳定律 (LoD),在我看来你应该。您举了一个完美的消息链示例,该消息链与它一无所知的类过于亲密。
得墨忒耳定律:http://en.wikipedia.org/wiki/Law_of_Demeter
评论
确保逻辑上不可能的东西不是。例如,房子总是有西墙。为了避免状态中的此类异常,可以使用一些方法来检查是否存在预期的状态:null
if (wall.hasDoor()) {
wall.getDoor().etc();
}
这本质上是一个空检查,但可能并不总是如此。
关键是你应该做点什么,以防万一你有一个.例如 - 或抛出一个null
return
IllegalStateException
你不应该做什么 - 不要抓住.运行时异常不是用来捕获的 - 不希望您能从中恢复,也不需要依赖逻辑流的异常。想象一下,你实际上并不期望某些东西是 ,并且你捕获(并记录)一个 .这不是非常有用的信息,因为很多事情都可以在这一点上。NullPointerException
null
NullPointerException
null
您无法编写任何方法来促进这一点(这根本不是 Java 中方法调用和参数评估的工作方式)。checkForNull
您可以将链接的语句分解为多个语句,并在每一步进行检查。但是,也许更好的解决方案是首先不要让这些方法返回。您可能希望改用一种称为 Null 对象模式的东西。null
相关问题
如果你无法避免违反得墨忒耳定律(LoD),并且随着 Java 8 引入 Optional,那么在像你这样的 get 链中处理 null 可能是最好的做法。
该类型将使您能够在一行中通过管道传递多个映射操作(其中包含 get 调用)。空检查在后台自动处理。Optional
例如,当对象未初始化时,不会生成 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;
}
}
因此,对于您的 getter 链,它将如下所示:
Optional.ofNullable(house)
.map(house -> house.getFloor(0))
.map(floorZero -> floorZero.getWall(WEST))
.map(wallWest -> wallWest.getDoor())
.map(door -> wallWest.getDoor())
它的返回将是这样的,这将使您更安全地工作,而不必担心空异常。Optional<Door>
评论
为了检查 gets 链中是否存在 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());
此代码也是类型安全的,通常按预期工作:
- 如果链中的所有对象都不为 null,则返回指定类型的实际值。
- 如果链中的任何对象为 null,则返回 null。
您可以将 opt 方法放入共享的 util 类中,并在应用程序中的任何地方使用它。
评论
.getFloor(0)
IndexOutOfBoundsException
Optional<T>
很老的问题,但仍然添加我的建议:
我建议不要在一个方法调用链中从 House 深处获取 DoorKnob,而应该尝试从调用代码中向此类提供 DoorKnob,或者通过创建专门用于此目的的中央查找工具(例如 DoorKnob 服务)
松耦合设计简化示例:
class Architect {
FloorContractor floorContractor;
void build(House house) {
for(Floor floor: house.getFloors()) {
floorContractor.build(floor);
}
}
}
class FloorContractor {
DoorMaker doorMaker;
void build(Floor floor) {
for(Wall wall: floor.getWalls()) {
if (wall.hasDoor()) {
doorMaker.build(wall.getDoor());
}
}
}
}
class DoorMaker {
Tool tool;
void build(Door door) {
tool.build(door.getFrame());
tool.build(door.getHinges());
tool.build(door.getDoorKnob());
}
}
使用 Supplier 实现 nullPointer try/catch,您可以将其发送到所有 get 链
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()));
评论
// Example
LazyObject.from(curr).apply(A.class, A::getB).apply(B.class, B::getC).apply(C.class, C::getD).to(String.class);
// LazyObject.java
public class LazyObject {
private Object value;
private LazyObject(Object object) {
this.value = object;
}
public <F, T> LazyObject apply(Class<F> type, Function<F, T> func) {
Object v = value;
if (type.isInstance(v)) {
value = func.apply(type.cast(v));
} else {
value = null; // dead here
}
return this;
}
public <T> void accept(Class<T> type, Consumer<T> consumer) {
Object v = value;
if (type.isInstance(v)) {
consumer.accept(type.cast(v));
}
}
public <T> T to(Class<T> type) {
Object v = value;
if (type.isInstance(v)) {
return type.cast(v);
}
return null;
}
public static LazyObject from(Object object) {
return new LazyObject(object);
}
}
评论
对我来说,更好的解决方案是使用 java.util.Optional.map(..) 来链接这些检查: https://stackoverflow.com/a/67216752/1796826
评论
您可能有一个通用方法,如下所示:
public static <T> void ifPresentThen(final Supplier<T> supplier, final Consumer<T> consumer) {
T value;
try {
value = supplier.get();
} catch (NullPointerException e) {
// Don't consume "then"
return;
}
consumer.accept(value);
}
所以现在你可以做
ifPresentThen(
() -> house.getFloor(0).getWall(WEST).getDoor().getDoorknob(),
doorKnob -> doSomething());
评论