如何处理方法返回的空值?

How to handle a null returned from a method?

我有一个图相关的方法returns某个节点的相邻节点。 如果一个节点没有邻居它returns null,方法如下

public Iterable<Node> getNeighbors(Node v) {
    if (!this.adjacencyList.get(v).isEmpty())
        return this.adjacencyList.get(v);
    return null;
}

我尝试使用以下方法避免异常:

if (graph.getNeighbors(nodeIterator.name) == null)
            nodeIterator = all_graph_nodes.iterator().next();
Iterable<Node> adjNodes = graph.getNeighbors(nodeIterator.name);

NullPointerException 即使使用前面的代码也会引发。 如何解决这个问题?

您应该避免从 getNeighbors 方法中 returning null。对于 Iterables、Iterators 和 Collections,return null 不是好的做法,因为一个空的 iterable 将表示相同的概念(该邻接列表中没有任何内容)而没有 null 的所有危险。而且您的代码会更简单。您可以检查可迭代对象是否包含任何内容,如果不包含则默认为完整迭代器。

如果您仍然收到 NPE,那么问题出在 getNeighbours 而不是第二个代码段。

  1. this.adjacencyList 为空,-OR-
  2. this.adjacencyList.get(v) returns null.

假设您将 name 传递给一个方法,然后该方法将通过 node 进行查找,并且您可以'不要在列表上调用 .get(someNodeRef)adjacencyList 可能是某种哈希图,所以你的名字不对,你应该重命名一些东西。 Map 的 .get(x) 方法 returns null 如果找不到条目,​​那么罪魁祸首很可能是 v 根本不在地图中,因此 .get(v).isEmpty() 抛出 NPE。

修正如下:

  1. 当携带预期语义的有效标记值可用时,您应该 NEVER return null。有点啰嗦,但它的意思是:当您打算以与 'zero nodes' 完全相同的方式对待它时,为什么要 returning null?有一个 Iterable<Node> 的实例正确地表示了零节点的概念,但它不是 null。它是 List.of() 或等价物:空列表没有节点。伟大的。这就是你的意图。所以 return 那。

  2. .get(v).isEmpty() 在这里是错误的代码,因为这意味着如果您请求一个不存在的节点,就会发生 NPE。当然,除非您希望 它以这种方式工作。一个简单的出路是默认机制:改为调用 .getOrDefault

if (!this.adjacencyList.getOrDefault(v, List.of()).isEmpty()) ....

除了,当然,当你可以 return 一个空列表时,你永远不应该 returning null,所以你的 getNeighbours 方法变得简单:

return adjacencyMap.getOrDefault(v, List.of());

一条线就能解决所有问题。

一般来说,如果您正在编写以某种方式处理 null 的代码,并且以相同的方式处理一些标记值(例如空白字符串或空列表),那么您的代码风格很糟糕;但是你得到 null 应该得到那个空值。例如如果你写过这个:

if (x == null || x.isEmpty()) ...

你搞砸了。弄清楚你从哪里得到 x 的。在那里更新它,使 x 成为空白标记("" 用于字符串,List.of 用于列表,等等)。

那个,并更多地使用 .getOrDefault 和其他类似的方法:让您提供在以下情况下应该发生什么的方法。找不到密钥。

您应该不惜一切代价避免 returning null。这是非常危险的,因为它可能会导致在运行时抛出空指针异常。此类异常很难调试,因为它们通常会隐藏实现错误,因为抛出异常的位置很可能远离原始实现错误。 您的案例实际上是此类行为的一个很好的例子,因为无法直接理解 NPE 的来源。

null 值的出现是不可避免的情况下(例如,正如@rzwitserloot 指出的那样,Java 的 Map get method) and there is the possibility of exposing it to client objects (e.g. your getNeighbors method may expose such null value) I like to use Java's Optional (如文档中所述)是:

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

此对象将充当可分配为 null 的对象的包装器,从而防止直接使用它并且 可能 防止抛出 NPE。

在你的情况下,这将按如下方式应用(请注意,这是假设 adjancencyList 是一个非空对象并且它的 get 方法是实际抛出 NPE 的方法):

public Optional<Iterable<Node>> getNeighbors(Node v) {
    return Optional.ofNullable(this.adjacencyList.get(v));
}
if (!graph.getNeighbors(nodeIterator.name).isPresent()) {
    nodeIterator = all_graph_nodes.iterator().next();
}

Iterable<Node> adjNodes = graph.getNeighbors(nodeIterator.name).get();

请注意,通过将原始 get 方法包装在 Optional 对象中,将不再传播原始 null 值,从而阻止客户端使用它。您只是将处理 null 的责任转移到您这边,并保护客户来处理它们。

使用 Optional 作为方法的 return 类型的另一个巨大优势是它隐式声明了方法的 return 对象可能存在也可能不存在。这迫使客户了解其 return 值可能为空 (null),从而迫使其采取相应行动。