Special Cases at the Ends
Look at unlink:
private void unlink(Node<T> target) {
if (target.prev == null) {
head = target.next;
} else {
target.prev.next = target.next;
}
if (target.next == null) {
tail = target.prev;
} else {
target.next.prev = target.prev;
}
size--;
}
The core of the method is just two lines:
target.prev.next = target.next;
target.next.prev = target.prev;
But we had to add a guard for each one because at the ends, a node’s neighbor is missing.
It is not only unlink. add needs a separate branch for the empty list, because there is no tail to attach to. Every operation that changes the shape of the list has to ask the same questions: is this the first node, is this the last node, is the list empty? Each question comes from a null where a node should be.
Notice that the interior of the list needs none of these guards. Every node in the middle has a real node on both sides. The guards are only needed at the ends.