Недавно от товарища пришел вопрос на засыпку: "Почему в интерфейсe java.util.Map метод get в качестве ключа принимает Object, а метод put использует женерики?"

    V get(Object key);
    V put(K key, V value);

Сходу ответить не смог, пришлось разбираться.
Сразу пришло на ум несколько гипотез:

  • раздолбайство
  • легаси-код
  • "так специально задумано"

Не смотря на то, что первые две гипотезы вполне правдоподобны, думаю самый интересный это третий вариант ("так специально задумано"). Если поискать в интернете,то можно найти следующее объяснение.

По "контракту", который был еще в версиях до женериков, Map должен работать с ключами по следующему правилу:

  key==null ? k==null : key.equals(k)

Легко видеть, что т.к. в при вызове метода key.equals(k) объекты key и k потенциально могут быть абсолютно разных классов, то в итоге мы не имеем право вводить ограничение на тип в сигнатуре метода.

Конечно большинство java-программистов знают, что если нужно переопределить метод equals, то по хорошему надо сделать проверку типа проверяемого объекта, но в жизни всякое случается...

В качестве примера к объяснению, в интернете можно встретить пример на списках.

Суть очень простая. Мы предполагаем, что списки равны тогда, когда равны все их элементы. При этом по большому счету не так уж и важно какой именно это список (ArrayList, LinkedList и т.д.).
В итоге если использовать список в качестве ключа, то возникнет следующая (вполне реальная) ситуация.

        Map<linkedlist , Integer> m = new HashMap</linkedlist><linkedlist , Integer>();
        LinkedList a = new LinkedList();
        a.add("1");
        a.add("2");
        m.put(a, a.size());
 
        // конечно здесь мы явно создаем ArrayList, 
        // но впринципе нам может вернуться какой-то непонятный список 
        // из какого-то непонятного метода:
        // например List b = doVeryStrangeMethod();
        List b = new ArrayList(a);
       // Этот вызов метода нормально компилируется и вернет результат:
        System.out.println(m.get(b));
       // А вот этот вызов приведет к ошибке компиляции, т.к. put с generic-ом
       //  m.put(b, b.size());
</linkedlist>

P.S.: Лично я в жизни не сталкивался с ситуацией, когда нужно было использовать список в качестве ключа.