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.