Спасибо всем, кто ответил на вопрос, это действительно помогло мне прояснить ситуацию. В конце концов ответ Скотта Стэнчфилда ближе всего подошел к тому, как я понял это, но, поскольку я не понял его, когда он впервые написал это, я пытаюсь сформулировать проблему так, чтобы, надеюсь, кто-то другой выиграл.
Я собираюсь переформулировать вопрос в терминах List, поскольку он имеет только один общий параметр, и это облегчит его понимание.
Назначение параметризованного класса (например, List <Date>
или Map <K, V>
, как в примере) состоит в том, чтобы вызвать downcast и иметь гарантию компилятора, что это безопасно (без времени выполнения исключения).
Рассмотрим случай List. Суть моего вопроса заключается в том, почему метод, который принимает тип T и List, не будет принимать список чего-то, находящегося дальше по цепочке наследования, чем T. Рассмотрим этот надуманный пример:
List<java.util.Date> dateList = new ArrayList<java.util.Date>();
Serializable s = new String();
addGeneric(s, dateList);
....
private <T> void addGeneric(T element, List<T> list) {
list.add(element);
}
Это не скомпилируется, поскольку параметр списка представляет собой список дат, а не список строк. Обобщения не были бы очень полезны, если бы это компилировалось.
То же самое относится к карте <String, Class<? extends Serializable>>
Это не то же самое, что карта <String, Class<java.util.Date>>
. Они не являются ковариантными, поэтому, если я хочу взять значение из карты, содержащей классы дат, и поместить ее в карту, содержащую сериализуемые элементы, это нормально, но сигнатура метода, которая говорит:
private <T> void genericAdd(T value, List<T> list)
Хочет иметь возможность сделать оба:
T x = list.get(0);
и
list.add(value);
В этом случае, хотя метод junit на самом деле не заботится об этих вещах, сигнатура метода требует ковариации, которую он не получает, поэтому он не компилируется.
По второму вопросу
Matcher<? extends T>
Недостатком было бы действительно принимать что-либо, когда T - это объект, который не является целью API. Намерение состоит в том, чтобы статически убедиться, что сопоставитель соответствует фактическому объекту, и нет никакого способа исключить Object из этого вычисления.
Ответ на третий вопрос заключается в том, что ничего не будет потеряно с точки зрения неконтролируемой функциональности (в JUnit API не будет небезопасного преобразования типов, если этот метод не был обобщен), но они пытаются выполнить что-то другое - статически убедитесь, что два параметра могут совпадать.
РЕДАКТИРОВАТЬ (после дальнейшего созерцания и опыта):
Одна из больших проблем с сигнатурой метода assertThat - это попытки приравнять переменную T к универсальному параметру T. Это не работает, потому что они не являются ковариантными. Так, например, вы можете иметь T, равное List<String>
, но затем передать совпадение, полученное компилятором, в Matcher<ArrayList<T>>
. Теперь, если бы это не был параметр типа, все было бы хорошо, потому что List и ArrayList ковариантны, но, поскольку Generics, с точки зрения компилятора, требует ArrayList, он не может допустить List по причинам, которые, я надеюсь, понятны из вышесказанного.