我有一个与图相关的方法,它返回某个节点的相邻节点。如果一个节点没有邻居,则返回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
。如何解决这个问题?
如果您仍然得到一个NPE,那么问题出在getNeighbours
中,而不是第二个代码段中。
this.adjacencyList
为空,-OR-this.adjacencyList.get(v)
返回null
假设您将一个名称传递给一个方法,然后该方法将通过节点进行查找,并且您不能在列表中调用.get(someNodeRef)
,那么adjacencyList
可能是某种哈希图,因此您的名称是关闭的,您应该重命名一些东西。如果没有找到条目,Map的.get(x)
方法将返回null
,因此最有可能的罪魁祸首是v
根本不在映射中,因此.get(v).isEmpty()
抛出NPE。
修复如下:
-
当具有预期语义的有效sentinel值可用时,您应该NEVER返回null。一口,但它的意思是:当你打算以与"零节点"完全相同的方式处理时,为什么要返回null?
Iterable<Node>
的一个实例正确地表示了零节点的概念,但它不是null
。它是List.of()
或等效的:空列表中没有节点。太棒了这就是你的意图。所以把它还给我。 -
.get(v).isEmpty()
在这里是错误的代码,因为这意味着如果您请求一个不存在的节点,就会发生NPE。当然,除非您希望以这种方式工作。一个简单的解决方法是默认机制:改为调用.getOrDefault
:
if (!this.adjacencyList.getOrDefault(v, List.of()).isEmpty()) ....
当然,除了当你可以返回一个空列表时,你永远不应该返回null,所以你的getNeighbours方法变得简单:
return adjacencyMap.getOrDefault(v, List.of());
那艘班轮可以解决所有问题。
一般来说,如果您编写的代码以某种方式处理null
,而某些sentinel值(如空白字符串或空列表)以同样的方式处理,那么您的代码风格就不好了;然而,你得到了null
应该得到的是空值。例如,如果你写过这样的话:
if (x == null || x.isEmpty()) ...
你搞砸了。弄清楚你从哪里得到x。在那里更新它,使x为空白sentinel(""
表示字符串,List.of
表示列表,等等)。
并且更多地使用.getOrDefault
和其他这样的方法:让您提供当例如找不到密钥时应该发生的事情的方法。
您可能应该避免从getNeighbors方法返回null。对于Iterables、Iterators和Collections,返回null不是一个好的做法,因为空的可迭代表示相同的概念(该邻接列表中没有任何内容),而没有null的所有危险。而且你的代码会更简单。您可以检查iterable是否包含任何内容,如果不包含,则默认为完整迭代器。
您应该不惜一切代价避免返回null
。这是非常危险的,因为它可能会导致在运行时抛出空指针异常。这样的异常很难调试,因为它们通常隐藏实现错误,因为抛出异常的地方很可能远离原始实现错误。你的案例实际上是这种行为的一个很好的例子,因为它不能直接理解NPE来自哪里。
在null
值的出现是不可避免的情况下(例如@rzwitserloot指出,Java的Mapget
方法),并且有可能将其暴露给客户端对象(例如,您的getNeighbors
方法可能会暴露这样的null
值),我喜欢使用Java的Optional,它(如文档中所述)是:
一个容器对象,它可能包含也可能不包含非null值。如果存在一个值,isPresent()将返回true,get()则返回该值。
此对象将充当可能被分配为null
的对象的包装器,从而防止直接使用它,并且可能防止抛出NPE。
在您的情况下,这将应用如下(注意,这是假设adjancencyList
是一个非null对象,并且它的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
作为方法的返回类型的另一个巨大优点是,它隐式地声明了该方法的返回对象可能存在,也可能不存在。这迫使客户端理解其返回值可能为空(null
),从而迫使其相应地采取行动。