检查"get"调用链是否为null

36 java null nullpointerexception

假设我想执行以下命令:

house.getFloor(0).getWall(WEST).getDoor().getDoorknob();
Run Code Online (Sandbox Code Playgroud)

为了避免NullPointerException,我必须在以下情况下执行以下操作:

if (house != null && house.getFloor(0) && house.getFloor(0).getWall(WEST) != null
  && house.getFloor(0).getWall(WEST).getDoor() != null) ...
Run Code Online (Sandbox Code Playgroud)

是否有一种方法或已经存在的Utils类更优雅地执行此操作,让我们说类似下面的内容?

checkForNull(house.getFloor(0).getWall(WEST).getDoor().getDoorknob());
Run Code Online (Sandbox Code Playgroud)

Joh*_*nny 42

如果您无法避免违反所选答案中所述的Demeter法则(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'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;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,对于你的getters链,它将如下所示:

Optional.ofNullable(house)
        .map(house -> house.getFloor(0))
        .map(floorZero -> floorZero.getWall(WEST))
        .map(wallWest -> wallWest.getDoor())
        .map(door -> wallWest.getDoor())
Run Code Online (Sandbox Code Playgroud)

它的返回将是一个类似的东西Optional<Door>,将允许你更安全的工作,而不用担心空例外.

  • 巧妙的技巧!就像可选值的构建器一样,链中丢失的任何链接都将导致链停止并使可选值包含空。 (2认同)
  • 老实说,这应该是正确的答案,而不是当前的答案!感谢您的精彩解释。 (2认同)

Jer*_*ing 16

最好的方法是避免链条.如果您不熟悉得墨忒耳法(LoD),我认为您应该这样做.您已经给出了一个完整的消息链示例,该消息链与类没有业务知道的类过于亲密.

得墨忒耳法则:http://en.wikipedia.org/wiki/Law_of_Demeter

  • 这个答案没有给出如何避免链条的指导,并假设 OP 有时间/许可重新设计现有代码。 (25认同)

小智 15

为了检查gets的链是否为null,您可能需要从闭包中调用您的代码.闭包调用代码如下所示:

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

然后使用以下语法调用它:

Doorknob knob = opt(() -> house.getFloor(0).getWall(WEST).getDoor().getDoorknob());
Run Code Online (Sandbox Code Playgroud)

此代码也是类型安全的,通常按预期工作:

  1. 如果链中的所有对象都不为null,则返回指定类型的实际值.
  2. 如果链中的任何对象为null,则返回null.

您可以将opt方法放入共享的util类中,并在应用程序的任何位置使用它.

  • NullpointerException 不是比 null 检查昂贵吗? (7认同)
  • 毫无疑问,这是处理这种情况的一种非常棘手的方法,但处理空指针异常是一种非常糟糕的做法,因为您最终可能会处理一些意想不到的事情。你可以阅读Effective java的相关部分来获得更好的理解。 (4认同)

Car*_*ter 7

您当然可以简单地将整个表达式包装在一个 try-catch 块中,但这是一个坏主意。更干净的是Null 对象模式。有了这个,如果你的房子没有 0 层,它只会返回一个像普通楼层一样的楼层,但没有真正的内容;楼层,当被要求提供他们没有的墙时,会返回类似的“空”墙等。

  • 所以基本上,您将“快速失败”的 NPE 转换为“可能在未指定的稍后时间和地点失败”?在某些情况下,null 对象是有意义的(空集合,最突出),但 IMO 它们完全不适合作为 null 的一般替代品。 (3认同)

Boz*_*zho 5

确保逻辑上不可能的事情null不是。例如 - 房子总是有一面西墙。为了避免状态中的此类异常,您可以使用方法来检查您期望的状态是否存在:

if (wall.hasDoor()) {
   wall.getDoor().etc();
}
Run Code Online (Sandbox Code Playgroud)

这本质上是一个空检查,但可能并不总是如此。

关键是你应该做一些事情,以防你有一个null. 例如 -return或抛出IllegalStateException

而你不应该做的——不要抓住NullPointerException。运行时异常不是用于捕获的 - 不希望您可以从它们中恢复,依赖逻辑流的异常也不是一个好习惯。想象一下,您实际上并不期望某事是null,并且您捕获(并记录)了 a NullPointerException。这不会是非常有用的信息,因为null此时可能会有很多事情。