考虑一个哈希表,如果get
和put
操作的密钥为空,则会抛出NullPointerException(NPE)。这让我想象异常应该是对称的,即NPE应该对涉及空节点输入参数的所有散列表操作是一致的。
我的理解是否正确?
如果是,那么考虑一个示例类Graph的情况,它使用下面的HashMap。它旨在将NPE抛出为空输入。无需了解其内部结构,它有两个功能:
graph.addNode (node); // throws NPE if node is null.
graph.getEdges (node); // Should empty collections be returned
// or NPE for null node ?
根据 Effective Java ,我们应该更喜欢返回空集合而不是null。现在,如果node参数为null并且我们想要获取其边缘,map.get(null node)
将返回一个空集合。是否仍然希望返回NPE以保持对称性?
答案 0 :(得分:3)
这不是对称问题。这是合同问题,也是对合同的尊重。一个方法应该定义一个关于它接受什么作为参数,它做什么,以及它返回和抛出什么的契约。如果调用者不尊重合同,那么这是一个开发错误,应由运行时异常发出信号。
当调用者不遵守合同时返回有效结果是一个坏主意:它隐藏了错误而不是发出信号,这通常会使问题变得更糟,更难找到。
在您的示例中,向图表添加空节点毫无意义。所以应该在adddNode()
javadoc中写入该方法不接受null,并且该方法应该抛出NullPointerException。
同样,要求空节点的边缘也没有意义。这样做的开发人员可能忘记初始化变量,或类似的东西。没有正当理由要求null的边缘。因此,应记录getEdges()
不接受null,如果将null作为参数传递,则该方法应抛出NullPointerException。
当我教这个时,我经常使用以下示例。假设您创建了一个名为boolean hasCancer(ExaminationResults results)
的方法。该方法必须分析结果并判断患者是否患有癌症。如果传递的结果为null,它应该返回true吗?这将使患者进入癌症治疗,即使你不知道他是否确实患有癌症。它应该返回假吗?那么,患者可能患有癌症,并且可能死亡,因为该方法告诉他没有癌症,而实际上并不知道。所以该方法不应返回任何内容。它应该通过抛出NullPointerException来发出错误信号。
从getEdges()
返回一个与我的癌症例子相当的空集合?你不知道。也许您的图表的最终用户使用它来诊断癌症,并且可能使用空边缘集合来确定患者患有癌症。因此快速失败,并抛出NullPointerException。
答案 1 :(得分:2)
当客户端调用方法应该快速失败时,因为检测错误比容易。它们出现在它们首次出现的地方
在您的情况下,您有一个名为addNode(node)
的方法。因此,当客户端调用它来添加节点时,行为取决于方法的合同。我猜你不希望客户端调用它并希望添加null
节点。因此,您应该fail-fast
并抛出NullPointerException
或IllegalArgumentException
。
在第二种情况node.getEdges(node)
中,它还取决于合同,合同取决于您的问题域中有意义的内容。我认为node.getEdges(null)
应抛出IllegalArgumentException
,因为没有边的节点与null
节点不同,我想区分它。
有关返回值的更多信息
如果客户从方法中获得结果,结果应该使客户端代码更容易编写容错。
在第二种情况getEdges(node)
中,如果节点没有边缘,则应返回空集合,因为这会使客户端代码更容易。客户端可以避免不必要的null
检查。返回值时,您应该考虑客户端代码以及它如何处理返回值。然后,您应该选择一个使客户端代码更容易的返回值。
例如:我希望getEdges(node)
的客户端代码看起来像这样
Collection<Node> nodes = grap.getEdges(node);
for(Node node: nodes){
...
}
因此,如果您返回null
而不是空集合,则客户端代码必须如下所示
Collection<Node> nodes = grap.getEdges(node);
if(nodes != null){
for(Node node: nodes){
...
}
}
客户端的最差情况将是getEdges(node)
方法在代码没有边缘时抛出NullPointerException
。我认为图中的正常情况是某些节点不存在边。因此,我不会抛出异常。
这就是为什么我会在那种情况下返回一个空集合。