Implementing get, containsKey, put, and remove

get traverses the list and returns the value of the node whose key matches:

@Override
public V get(K key) {
  if (key == null) {
    throw new IllegalArgumentException();
  }
  Node<K, V> curr = head;
  while (curr != null) {
    if (key.equals(curr.key)) {
      return curr.value;
    }
    curr = curr.next;
  }
  return null;
}

containsKey is the same loop, returning a boolean:

@Override
public boolean containsKey(K key) {
  if (key == null) {
    throw new IllegalArgumentException();
  }
  Node<K, V> curr = head;
  while (curr != null) {
    if (key.equals(curr.key)) {
      return true;
    }
    curr = curr.next;
  }
  return false;
}

Unlike LinkedSet’s add, put cannot call containsKey for its search. When it finds the key, it needs the node itself, so it can replace that node’s value.

@Override
public boolean put(K key, V value) {
  if (key == null || value == null) {
    throw new IllegalArgumentException();
  }
  Node<K, V> curr = head;
  while (curr != null) {
    if (key.equals(curr.key)) {
      curr.value = value;  // key present; replace its value
      return false;
    }
    curr = curr.next;
  }
  Node<K, V> newNode = new Node<>(key, value);
  newNode.next = head;  // insert at the head
  head = newNode;
  size++;
  return true;
}

remove is LinkedSet’s remove with keys compared instead of items:

@Override
public boolean remove(K key) {
  if (key == null) {
    throw new IllegalArgumentException();
  }
  Node<K, V> prev = null;
  Node<K, V> curr = head;
  while (curr != null) {
    if (key.equals(curr.key)) {
      if (prev != null) {
        prev.next = curr.next;
      } else {
        head = head.next;
      }
      size--;
      return true;
    }
    prev = curr;
    curr = curr.next;
  }
  return false;
}