检查方法链中最后一个 getter 是否不为 null。

9

在代码中,我们有很多链式方法,例如obj.getA().getB().getC().getD()。我想创建一个帮助类来检查方法getD()是否为null,但在此之前,我需要检查所有先前的getter方法。我可以这样做:

try {
    obj.getA().getB().getC().getD();
}
catch (NullPointerException e) {
    // some getter is null
}

或者(这很“傻”)
if (obj!null && obj.getA()!=null && obj.getA().getB()!=null && ...) {
    obj.getA().getB().getC().getD();
}
else {
    // some getter is null
}

我不想每次在我的代码中使用try{} catch()检查它。这个目的最好的解决方案是什么?
我认为最好的方法是:
1. obj.getA().getB().getC().getD().isNull() - 为此,我需要更改所有的getter方法,例如实现一些包含isNull()方法的接口。
2. NullObjectHelper.isNull(obj.getA().getB().getC().getD()); - 这将是最好的(我认为),但该如何实现呢?

5
解决方案:重构,不要进行链式调用 #迪米特法则 - user802421
1
@user802421,我不想重构整个应用程序,它太大了。我只想创建一些辅助类。我同意你的看法,重构是最好的选择,但我们没有时间。 - pepuch
我不明白你的两个“解决方案”如何能够解决中间结果为null的问题。 - Henry
我想以这种方式使用它:if (NullObjectHelper.isNotNull(obj.getA().getB().getC().getD())) {} else {}。如果这是不好的想法,我应该怎么做/使用? - pepuch
我基本上和其他评论者一样,但根据情况,您可能需要考虑使用AOP。请参见此答案:http://stackoverflow.com/a/9211946/687514 - Anders R. Bystrup
显示剩余2条评论
3个回答

10

从Java 8开始,您可以使用像Optional.isPresentOptional.orElse这样的方法来处理getter链中的null:

boolean dNotNull = Optional.ofNullable(obj)
              .map(Obj::getA)
              .map(A::getB)
              .map(B::getC)
              .map(C::getD)
              .isPresent();

尽管这比捕获NullPointerException更可取,但这种方法的缺点是为Optional实例分配对象。

你可以编写自己的静态方法来执行类似的操作,而不会产生这种开销:

boolean dNotNull = Nulls.isNotNull(obj, Obj::getA, A::getB, B::getC, C::getD);

示例实现请参见Nullifier类型,此处

没有一种方法比嵌套的if-not-null检查更具运行时效率。


1
在一行代码中实现的任务:Optional.ofNullable(obj) .map(Obj::getA) .map(A::getB) .map(B::getC) .map(C::getD).orElse(objDefault); - Abs

7
您可以使用“Option”模式实现所需的结果。这会强制您更改方法签名,但基本上,如果您的方法返回某种类型“T”,则它保证具有某些非空值,如果它返回“Option<T>”,则表示它既具有值T,也可能为null。
Java 7有一些称为null安全性的功能,但它已从最终版本中删除。您可以执行以下操作:
obj?.getA()?.getB()?.getC()?.getD()

此外,Java 8将添加一个名为Optional的功能,因此您可以安全地执行它。
实际上,如果您现在真的想使用它,请尝试Null Object模式。这意味着您可以返回某种默认值,而不是返回普通的null,这样就不会触发NullPointerException。但是,您需要对getter方法进行一些更改。
class Object {
   A getA() {
     // ...
     return a == null ? A.NULL : a;
   }
}

class A {
   static A NULL = new A(); // some default behaviour
   B getB() {
     if (this == NULL) return B.NULL;
     // ...
     return b == null ? B.NULL : b;
   }
}

编辑:如果您想要一个工具来完成此操作,您可以将其封装在一些功能接口中,然后调用它。

static boolean isNullResult(Callable call) throws Exception {
    try {
        return call.call() == null;
    } catch (NullPointerException npe) {
        return true;
    }
}

使用方法如下:

isNullResult(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        return new A().getB().getC().getInt();
    }
});

它不需要你改变现有的功能


谢谢mishadoff的建议。我知道我可以使用“Null对象”模式,但如果我使用它,我将需要对应用程序进行一些更改。我不想这样做,因为应用程序几乎完成了,而且我是这个项目的新手。 - pepuch

1
如前所述,真正的解决方案是重构。 与此同时,您可以将第一个解决方法封装在一个函数中:
static D getD(MyClass obj) {

    try {
        return obj.getA().getB().getC().getD();
    }
    catch (NullPointerException e) {
        return null; // Or even better, some default D
    }
}

在调用方的站点上:
D d = getD(obj);

至少你不必使用try-catch块来删除调用者。当一些中间的getX()调用返回null时,仍然需要以某种方式处理错误,因此d变为null。最好在包装函数中返回一些默认的D。

我不明白你在问题结尾列出的两个选项如何有助于解决中间任何一个getX()返回null的情况;这将导致NullPointerException


为了避免最后一个示例中的NPE,我们可以将代码放在Runnable块中,并稍后使用try-catch调用。 - mishadoff
我不建议您使用实际代码。听起来A、B或C可能为空,因此在这种情况下抛出异常通常是一个坏主意。将事物包装在实用程序方法中是一个好方法。但是,我更喜欢实用程序方法包括“愚蠢”的空值检查代码,而不是懒惰地捕获异常。 - Andrzej Doyle
@AndrzejDoyle 我建议首先进行重构。 可能为空的对象和链接的getter是缺陷的迹象。 我对“愚蠢”代码的问题在于它容易出错:很容易在那里打错字,或者忽略空检查等。 我们可以就try-catch与“愚蠢”片段的利弊争论不休,但解决方案是重构代码,以便我们不需要它们中的任何一个...包装器函数(无论是使用try-catch还是“愚蠢”片段)至少有助于保持新代码相对干净;这就是我的观点。 - Ali
@Ali 当然,我同意你的所有观点。 我忽略了你回答中第一行的重要性(你认为有什么东西会引起我的注意,但显然没有!)。 - Andrzej Doyle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接