Reputation: 59
I have a graph related method which returns the neighboring nodes of a certain node. If the one node has no neighbors it returns null, the method is the following
public Iterable<Node> getNeighbors(Node v) {
if (!this.adjacencyList.get(v).isEmpty())
return this.adjacencyList.get(v);
return null;
}
I try to avoid the exception using the following :
if (graph.getNeighbors(nodeIterator.name) == null)
nodeIterator = all_graph_nodes.iterator().next();
Iterable<Node> adjNodes = graph.getNeighbors(nodeIterator.name);
The NullPointerException
is raised even using the previous code.
How to solve this ?
Upvotes: 0
Views: 1652
Reputation: 21
You should avoid returning null
at all cost. This is of high danger as it may cause Null Pointer Exceptions to be thrown during runtime. Such exceptions are horrific to debug as they usually hide implementation errors due to the place where the exception was thrown is most likely far away from the original implementation error.
Your case is actually a good example of such behavior as it is not directly understandable where the NPE is coming from.
In situations in which the appearance of a null
value is inevitable (e.g. as @rzwitserloot pointed out, Java's 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 which (as stated in the docs) is:
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.
This object will act as wrapper to objects which may be assigned as null
thus preventing it to be used directly and possibly preventing NPEs to be thrown.
In your case this would apply as follows (note that this is assuming that adjancencyList
is a non-null object and that its get
method is the one actually throwing the 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();
Note that by wrapping the original get
method in an Optional
object there is no longer the propagation of a raw null
value hence preventing it to be used by the client. You are moving the responsibility of dealing with null
to your side only and protecting clients to handle them instead.
Another great advantage of using Optional
as a method's return type is that it implicitly declares that the return object of the method may or may not be present. This forces clients to understand that its return value may be empty (null
) and thus force it to act accordingly.
Upvotes: 0
Reputation: 102872
If you're still getting an NPE, then the problem is in getNeighbours
and not the second snippet.
this.adjacencyList
is null, -OR-this.adjacencyList.get(v)
returns null.Given that you're passing a name to a method that will then do a lookup by node, and that you can't call .get(someNodeRef)
on a list, adjacencyList
is probably some sort of hashmap, so your names are off and you should rename some things. Map's .get(x)
method returns null
if an entry is not found, so most likely the culprit is that v
isn't in the map at all, and thus .get(v).isEmpty()
throws NPE.
The fixes are as follows:
You should NEVER return null when a valid sentinel value that carries the intended semantic meaning is available. A mouthful, but it means here: Why are you returning null when you intend to treat that the exact same way as 'zero nodes'? There is an instance of Iterable<Node>
that properly represents the concept of zero nodes, and it isn't null
. It's List.of()
or equivalent: An empty list has no nodes. Great. That's what you intended. So return that.
.get(v).isEmpty()
is bad code here, as it would mean an NPE occurs if you ask for a non-existent node. Unless, of course, you want it to work that way. An easy way out is the defaulting mechanism: Call .getOrDefault
instead:
if (!this.adjacencyList.getOrDefault(v, List.of()).isEmpty()) ....
except, of course, you should never be returning null when you can return an empty list instead, so your getNeighbours method becomes simply:
return adjacencyMap.getOrDefault(v, List.of());
that one-liner will fix all things.
In general, if you are writing code where null
is dealt with in some way, and some sentinel value (such as a blank string or an empty list) is dealt with in the same way, your code is badly styled; however you got that null
should have gotten you that empty value instead. e.g. if you ever write this:
if (x == null || x.isEmpty()) ...
you messed up. Figure out where you got x from. Update it there, make x the blank sentinel (""
for strings, List.of
for lists, etcetera).
That, and use .getOrDefault
and other such methods more: Methods that let you provide what should happen when e.g. a key is not found.
Upvotes: 1
Reputation: 7403
You should probably avoid returning null from your getNeighbors method. It's not good practice to return null for Iterables, Iterators and Collections, since an empty iterable would represent the same concept (there is nothing in that adjacency list) without all the dangers of null. And your code would be simpler. You can check if the iterable contains anything and if not then default to the full iterator.
Upvotes: 1