Implementing SortedLinkedList

SortedLinkedList<T> implements SortedList<T> with two sentinel nodes, head and tail, that hold no user value. An empty list is the two sentinels pointing at each other; the real nodes between them are always kept in ascending order.

public class SortedLinkedList<T extends Comparable<T>> implements SortedList<T> {

  private Node<T> head;  // front sentinel
  private Node<T> tail;  // back sentinel
  private int size;  // number of real nodes

  private static class Node<T> {
    T value;
    Node<T> next;
    Node<T> prev;

    Node(T value) {
      this.value = value;
    }
  }

  public SortedLinkedList() {
    head = new Node<>(null);
    tail = new Node<>(null);
    head.next = tail;
    tail.prev = head;
    size = 0;
  }

  public int size() {
    return size;
  }

  private Node<T> insertBetween(T value, Node<T> before, Node<T> after) {
    Node<T> node = new Node<>(value);
    node.prev = before;
    node.next = after;
    before.next = node;
    after.prev = node;
    size++;
    return node;
  }

  // add and remove go here
}
Solution
public void add(T value) {
  if (value == null) {
    throw new NullPointerException();
  }
  Node<T> current = head.next;
  while (current != tail && current.value.compareTo(value) <= 0) {
    current = current.next;
  }
  insertBetween(value, current.prev, current);
}

: the traversal steps through up to all real nodes to find the first value greater than value, and the splice itself is .

Solution
public boolean remove(T value) {
  Node<T> current = head.next;
  while (current != tail) {
    if (Objects.equals(current.value, value)) {
      current.prev.next = current.next;
      current.next.prev = current.prev;
      size--;
      return true;
    }
    current = current.next;
  }
  return false;
}

: the traversal steps through up to all real nodes looking for a match, and unlinking the matched node is .