检查“get”调用链是否为 null

Check chains of "get" calls for null

提问人: 提问时间:8/11/2010 最后编辑:Hubert Kario 更新时间:2/15/2023 访问量:49066

问:

假设我想执行以下命令:

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());
java null nullpointerexception

评论

6赞 Oded 8/11/2010
要是你遵循得墨忒耳定律就好了。
35赞 8/11/2010
仅仅因为我开始从事一个现有的项目,我就不能根据我或任何希腊神的法则重新设计它。
0赞 Prathab K 7/26/2017
我们可以使用 Java8 函数式接口方法。检查此答案:stackoverflow.com/a/45319845/822314
1赞 James Daily 11/12/2021
对于其他任何想知道的人:“特别是,一个对象应该避免调用另一个方法返回的对象的方法。...法律可以简单地表述为'只使用一个点'“en.wikipedia.org/wiki/Law_of_Demeter
2赞 barneypitt 7/27/2022
得墨忒耳定律是一个可怕的主意(这就是为什么,谢天谢地,没有人使用它!导致容易出错的 gobbledegook 代码。这也是违反直觉的:如果每一种直觉都告诉你不要写这样的类,你可能不应该。但我真的看不出它如何帮助 null 安全链接。它只是将多个 null 检查推送到 House::getDoorknob(Floor floor, CompassPoint wall, DoorType doorType) 方法中。这种可怕的方法的无用性说明了一切。

答:

9赞 Carl Manaster 8/11/2010 #1

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

评论

1赞 Robin 8/11/2010
但是,如果一堵墙没有门,那么返回 null 是合乎逻辑的事情。否则,您将需要像 hasDoor() 这样的方法来知道没有实际的门,因为当您要求它时,您只会得到一个假门。
0赞 Carl Manaster 8/11/2010
@Robin,它并不完全是一堵“假”墙。是那堵墙不存在,不存在。而且(与 null 不同)它的行为就像一堵真正的墙,所以它以 null 无法做到的方式有用。
0赞 Bozho 8/11/2010
如果您不关心最终是否会发生某些事情,那么使用 Null Object 模式是一个不错的选择(+1)
6赞 Michael Borgwardt 8/11/2010
所以基本上,您将“快速失败”的 NPE 转换为“可能在以后未指定的时间和地点失败”?在某些情况下,null 对象是有意义的(最突出的是空集合),但 IMO 它们完全不适合作为 null 的一般替代品。
0赞 Robin 8/11/2010
不,我希望有一堵墙,但不一定是一扇门(为了挑剔)。我在特定示例中描述了这种方法的问题,因为并非所有墙都包含门,并且使用 Null Object 模式使确定是否存在门的能力变得更加复杂。@Michael Borgwardt在他的评论中描述了一般意义上的问题。我的经验是,这种模式在可以应用它的应用程序中相当有限。
14赞 Jerod Houghtelling 8/11/2010 #2

最好的方法是避免链条。如果你不熟悉得墨忒耳定律 (LoD),在我看来你应该。您举了一个完美的消息链示例,该消息链与它一无所知的类过于亲密。

得墨忒耳定律:http://en.wikipedia.org/wiki/Law_of_Demeter

评论

56赞 K-- 10/5/2020
这个答案没有给出如何避免链的指导,并假设 OP 有时间/权限来重新设计现有代码。
5赞 Bozho 8/11/2010 #3

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

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

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

关键是你应该做点什么,以防万一你有一个.例如 - 或抛出一个nullreturnIllegalStateException

你不应该做什么 - 不要抓住.运行时异常不是用来捕获的 - 不希望您能从中恢复,也不需要依赖逻辑流的异常。想象一下,你实际上并不期望某些东西是 ,并且你捕获(并记录)一个 .这不是非常有用的信息,因为很多事情都可以在这一点上。NullPointerExceptionnullNullPointerExceptionnull

1赞 polygenelubricants 8/11/2010 #4

您无法编写任何方法来促进这一点(这根本不是 Java 中方法调用和参数评估的工作方式)。checkForNull

您可以将链接的语句分解为多个语句,并在每一步进行检查。但是,也许更好的解决方案是首先不要让这些方法返回。您可能希望改用一种称为 Null 对象模式的东西。null

相关问题

144赞 Johnny 12/14/2016 #5

如果你无法避免违反得墨忒耳定律(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>

评论

3赞 nom-mon-ir 7/8/2019
巧妙的把戏!就像 Optional 的构建器一样,链中缺少的任何链接都会导致链停止并使 Optional 包含 null。
5赞 Ahmed Hamdy 2/19/2021
老实说,这应该是正确答案,而不是现在的答案!感谢您的精彩解释。
0赞 Michael Peterson 4/3/2023
如果您有可用的 Java 8+,这是正确的方法。在 Optional 链的末尾,您仍然可以执行 .orElse(null);如果需要传递或以其他方式处理非 Optional 类型。链接 ofNullable/map 确实没有缺点,这应该是公认的答案。
0赞 Peter S. 5/19/2023
外星魔法!!
24赞 Roman Seleznov 8/8/2017 #6

为了检查 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());

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

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

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

评论

6赞 MD. Sahib Bin Mahboob 7/9/2020
毫无疑问,这是处理这种情况的一种非常棘手的方法,但处理空指针异常是一种非常糟糕的做法,因为您最终可能会处理一些意想不到的事情。您可以阅读有效 java 的相关部分以更好地理解。
7赞 user3044440 12/12/2020
NullpointerException 不比 null 检查贵吗?
1赞 Michael Peterson 4/3/2023
除非您使用的是非常旧的 Java 版本(<8 中 Optional 不可用),否则不要求助于此。请参阅涉及 Optional.ofNullable()/.map() 的答案
0赞 sanya 8/25/2023
当您具有复杂的数据结构时,此方法非常有效,其中大多数数据是可选的。就像访问嵌套的 JAXB xml 元素一样。不过有两件事:使用 ,我也会以同样的方式处理。带着一个回来会更优雅。.getFloor(0)IndexOutOfBoundsExceptionOptional<T>
-3赞 Adriaan Koster 9/17/2018 #7

很老的问题,但仍然添加我的建议:

我建议不要在一个方法调用链中从 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());
    }        
}
-1赞 Omar Ruiz 7/2/2019 #8

使用 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()));

评论

0赞 Omar Ruiz 7/2/2019
我忘了。。。T defaultValue 表示在调用时发送默认值,或 remove 直接返回 null
4赞 ChuckB 3/13/2020
这与上面的谢列兹诺夫的回答有何不同?
-3赞 Yuebing Cao 1/22/2021 #9
// 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);
}

}

评论

1赞 Swarathesh Addanki 12/11/2021
请添加解释,这将很有帮助。
0赞 Swarathesh Addanki 12/11/2021
它将抛出一个 NullPointer,即:A::getB
0赞 Hulk 4/12/2022
为什么要进行所有这些选角?这看起来像是一次非常失败的尝试,以避免创建一些中间对象。
4赞 Laloi 5/5/2021 #10

对我来说,更好的解决方案是使用 java.util.Optional.map(..) 来链接这些检查: https://stackoverflow.com/a/67216752/1796826

评论

0赞 Michael Peterson 4/3/2023
是的,这在答案中得到了很好的介绍 stackoverflow.com/a/41145698/211614
0赞 Ronit Pradhan 2/15/2023 #11

您可能有一个通用方法,如下所示:

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());