【问题标题】:Check if last getter in method chain is not null检查方法链中的最后一个 getter 是否不为空
【发布时间】:2013-11-06 22:45:48
【问题描述】:

在代码中我们有很多链式方法,例如obj.getA().getB().getC().getD()。我想创建帮助类来检查方法getD() 是否不为空,但在此之前我需要检查所有以前的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()); - 这将是最好的(我认为是这样)但是如何实现呢?

【问题讨论】:

  • 解决方案。重构。不要链接#LawOfDemeter
  • @user802421 我不想重构整个应用程序,它太大了。我想做的就是创建一些帮助类。我同意你的观点,重构将是最好的,但我们没有时间。
  • 我看不出你的两个“解决方案”如何帮助中间结果成为null
  • 我想以这种方式使用它if (NullObjectHelper.isNotNull(obj.getA().getB().getC().getD())) {} else {}。如果这是个坏主意,我应该怎么做/使用?
  • 我基本上和其他评论者一样,但您可能会考虑使用 AOP,具体取决于上下文。见this answer

标签: java design-patterns


【解决方案1】:

您可以使用Option 模式获得所需的结果。这会强制您更改方法签名,但基本上如果您的方法返回某个类型 T,它保证它有一些非空值,如果它返回Option<T>,则意味着它具有值 T 或 null。

Java 7 有一些称为空安全的特性,但它已从最终版本中删除。你可以这样做:

obj?.getA()?.getB()?.getC()?.getD()

此外,Java 8 将添加一个名为 Optional 的功能,因此您可以安全地进行操作。

事实上,如果你现在真的想使用它,试试Null Object 模式。这意味着您可以返回某种不会触发 NullPointerException 的 default 值,而不是返回普通的 null。不过,您需要对 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();
    }
});

它不需要您更改现有功能

【讨论】:

  • +1 感谢 mishadoff 的建议。我知道我可以使用Null object 模式,但如果我要使用它,我需要对应用程序进行一些更改。我不想这样做,因为申请快完成​​了,而且我是这个项目的新手。
【解决方案2】:

如前所述,真正的解决方案是重构。

与此同时,您可以将您的第一个解决方法封装在一个函数中:

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 调用。
  • 我根本不推荐您使用的实际代码。听起来 A 或 B 或 C 可能为空是很常见的,因此在这种情况下,以抛出异常为代价通常是一个坏主意。当然,用实用方法包装东西是一种好方法。但是,我更多宁愿实用方法包括执行空值检查而不是懒惰和捕获异常的“愚蠢”代码。
  • @AndrzejDoyle 我建议首先进行重构。 潜在的空对象和链式 getter 是缺陷的迹象。我对“愚蠢”代码的问题是它容易出错:很容易在那里打错字,或者错过空值检查等。我们可以争论 try-catch 与“愚蠢”的优缺点sn-ps 但解决方案是重构代码,以便我们不需要它们中的任何一个......包装函数(无论使用 try-catch 还是“愚蠢”的 sn-p 都无关紧要)在最少有助于保持新代码相对干净;这就是我的观点。
  • @Ali 当然,我同意这一切。我忽略了您回答中第一行的重要性(您会认为某些事情会引起我的注意,但显然不是!)。
【解决方案3】:

从 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 类型 here

没有任何方法可能比嵌套的 if-not-null 检查具有更高的运行时效率。

【讨论】:

  • 一行完成 Optional.ofNullable(obj) .map(Obj::getA) .map(A::getB) .map(B::getC) .map(C::getD ).orElse(objDefault);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-14
  • 1970-01-01
相关资源
最近更新 更多