一种简洁的方法来检查一个Set是否不包含null。

11

我有一个方法,它接收一个对象的Set。它委托给的一个方法要求Set中不包含任何空元素。我想在委托之前在该方法中检查前置条件,即确保Set不包含任何空元素。明显的代码是:

public void scan(Set<PlugIn> plugIns) {
   if (plugIns == null) {
      throw new NullPointerException("plugIns");
   } else if (plugIns.contains(null)) {
      throw new NullPointerException("plugIns null element");
   }
   // Body
 }

但是这是不正确的,因为 Set.contains() 可能会抛出 NullPointerException,如果 Set 实现本身不允许空元素。在这种情况下捕获并忽略 NullPointerException 将起作用 但不优雅。有没有一种简洁的方法来检查这个前提条件?
< p > Set 接口中是否存在设计缺陷?如果一个 Set 实现可能永远不包含 null,为什么不要求 Set.contains(null) 总是返回 false 呢?或者使用 isNullElementPermitted() 谓词?


如果您有这样的特定要求,请子类化“Set”并禁止“null”放置。此外,我不会在这里使用“else”。 - Dave Newton
2
我同意这非常令人恼火。你允许使用通用的Set创建你的类,但是要根据你的类合同确保它不包含null。然后,如果有人实际上传递了一个不允许nullSet,你的安全contains检查会抛出NPE,因为它在规范中。在我看来,这是设计错误,因为调用者无法做任何事情(即,无法询问Set是否允许null),更不用说在这种情况下抛出NPE似乎很愚蠢,而不是返回false - john16384
3个回答

5
最简单的方法是枚举 Set 并检查是否存在 null。
public void scan(Set<PlugIn> plugIns) {
  if (plugIns == null) throw new NullPointerException("plugIns");
  for (PlugIn plugIn : plugIns) {
    if (plugIn == null) throw new NullPointerException("plugIns null element");
  }
}

1
简单,但复杂度为O(N),这对于前置条件检查来说是不好的。 - Raedwald
1
引用另一个人的话:“过早优化是万恶之源”。你确定这是性能瓶颈吗?考虑到你正在做的事情,我猜测问题可能出在其他地方的架构上。 - Dan Hardiker
我的关注点并不是过早的优化。一个整洁的解决方案应该是通用的和相当高效的。每当有人在没有先测量性能的情况下使用HashSet,并获得快速的Set.contains()的好处时,他们并没有参与过早的优化。http://programmers.stackexchange.com/questions/79946/what-is-the-best-retort-to-premature-optimization-is-the-root-of-all-evil. - Raedwald

3
从plugIns创建一个HashSet,并检查是否存在null。
public void scan(Set<PlugIn> plugIns) {
  if (plugIns == null) throw new NullPointerException("plugIns");
  Set<PlugIn> copy = new HashSet<PlugIn>(plugIns);
  if (copy.contains(null)) {
      throw new NullPointerException("null is not a valid plugin");
  }
}

1
简单,但复杂度为O(N),并且创建了一个新对象,这对于前置条件检查来说是不好的。 - Raedwald
1
你每秒钟扫描多少次新插件?;) 根据我的经验,复制或创建新集合很少会出现性能问题。也许你应该防止在 plugIns 来源处添加 null(对我来说,这听起来像是你正在修复别人的有缺陷的代码,除非存在一个有效的理由来存在一个 null-plugin)。 - Matt
我这样做并不是为了规避有缺陷的代码,而是因为及早检测故障更好。想象一下如果这是一个API方法:我们无法控制调用代码,但可能希望提供良好的诊断信息。 - Raedwald
每秒扫描多少次新插件?很好,你的解决方案对于我特定的情况来说性能足够了。但我也想知道在其他更注重性能的情况下如何进行检查。 - Raedwald
好的,我明白你的观点。但是假设这是实际API的一部分:在这种情况下,它取决于调用方和被调用方之间的接口/合约。如果允许将null作为集合的一部分处理,则可以在//body中使用简单的if进行处理(因为我们不知道如何处理空插件)。如果不允许将null包含在集合中,则根本没有必要检查null,除非您怀疑API有漏洞。 在我看来,这与个人品味有很大关系,所以没有对错之分。希望能帮到你 ;) - Matt
不是最理想的,但目前为止最好的方法是将copy变量嵌入到代码中,以获得简洁的结果。 - Raedwald

2

如果抛出NullPointerException,则只需捕获并忽略它:

public void scan(Set<PlugIn> plugIns) {
    if (plugIns == null) {
        throw new NullPointerException("plugIns");
    }

    NullPointerException e = null;
    try {
        if (plugIns.contains(null)) {
            // If thrown here, the catch would catch this NPE, so just create it
            e = new NullPointerException("plugIns null element");
        }
    } catch (NullPointerException ignore) { }

    if (e != null) {
        throw e;
    }
    // Body
}

如果抛出异常,这只会带来很小的开销,但是如果不使用异常(特别是不使用堆栈跟踪),它实际上非常轻量级。


你不觉得你的NPE候选人更像是非法参数异常的候选人吗? - unknown_boundaries

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