Implementing ArrayMap

Here is the Map interface:

public interface Map<K, V> {
  boolean put(K key, V value);  // stores value under key, replacing any old value;
                                // returns true if the key was not present before
  V get(K key);                 // returns the value stored under key, or null if key is absent
  boolean remove(K key);        // removes key and its value if key is present;
                                // returns true if the map changed
  boolean containsKey(K key);   // returns true if key is in the map
  int size();                   // returns the number of key-value pairs in the map
}

Every method that takes a key throws an IllegalArgumentException if the key is null. put also throws it if the value is null.

ArrayMap<K, V> implements the Map<K, V> interface with a backing array of entries. Each entry holds one key and its value. The entries are at indices 0 through size - 1, in no particular order:

public class ArrayMap<K, V> implements Map<K, V> {

  private Entry<K, V>[] arr;  // the backing array
  private int size;           // how many pairs the map holds

  private static class Entry<K, V> {
    K key;
    V value;

    Entry(K key, V value) {
      this.key = key;
      this.value = value;
    }
  }

  @SuppressWarnings("unchecked")
  public ArrayMap() {
    arr = (Entry<K, V>[]) new Entry[10];
    size = 0;
  }

  @Override
  public int size() {
    return size;
  }

  // Doubles the capacity of arr and keeps the entries where they are.
  private void grow() { /* already implemented */ }

  // the other operations go here
}
Solution
private int indexOf(K key) {
  for (int i = 0; i < size; i++) {
    if (arr[i].key.equals(key)) {
      return i;
    }
  }
  return -1;
}
Solution
@Override
public V get(K key) {
  if (key == null) {
    throw new IllegalArgumentException();
  }
  int i = indexOf(key);
  if (i == -1) {
    return null;
  }
  return arr[i].value;
}

@Override
public boolean put(K key, V value) {
  if (key == null || value == null) {
    throw new IllegalArgumentException();
  }
  int i = indexOf(key);
  if (i != -1) {
    arr[i].value = value;
    return false;
  }
  if (size == arr.length) {
    grow();
  }
  arr[size] = new Entry<>(key, value);
  size++;
  return true;
}
Solution
@Override
public boolean remove(K key) {
  if (key == null) {
    throw new IllegalArgumentException();
  }
  int i = indexOf(key);
  if (i == -1) {
    return false;
  }
  arr[i] = arr[size - 1];
  arr[size - 1] = null;
  size--;
  return true;
}
Solution

size is : it returns a field. get and containsKey are : each is a linear search for the key. put is : replacing a value is and appending an entry is amortized, but the search for the key comes first. remove is : moving the last entry into the gap is , but finding the entry is a linear search.

Solution

The costs are the same. size is in both, and get, containsKey, put, and remove are in both, because both find a key by linear search. They differ in memory: the array carries spare capacity, since it has more slots than entries unless it is full, and the linked list carries a next reference on every node.