Приведенный ниже пример (с использованием JUnit с сопоставителями Hamcrest):
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
Это не компилируется с assertThat
сигнатурой метода JUnit :
public static <T> void assertThat(T actual, Matcher<T> matcher)
Сообщение об ошибке компилятора:
Error:Error:line (102)cannot find symbol method
assertThat(java.util.Map<java.lang.String,java.lang.Class<java.util.Date>>,
org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class
<? extends java.io.Serializable>>>)
Однако, если я изменю assertThat
сигнатуру метода на:
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
Тогда компиляция работает.
Итак, три вопроса:
- Почему именно текущая версия не компилируется? Хотя я смутно понимаю проблемы ковариации здесь, я, конечно, не мог бы объяснить это, если бы мне пришлось.
- Есть ли недостаток в изменении
assertThat
метода наMatcher<? extends T>
? Есть ли другие случаи, которые сломались бы, если бы вы сделали это? - Есть ли смысл обобщать
assertThat
метод в JUnit?Matcher
Класс , кажется, не требует, поскольку JUnit вызывает метод спичек, который не напечатанный с любыми родовыми, и просто выглядит как попытка заставить типа безопасности , который ничего не делает, так какMatcher
просто не будет на самом деле совпадение, и тест не пройден независимо. Небезопасные операции не выполняются (или так кажется).
Для справки, вот реализация JUnit assertThat
:
public static <T> void assertThat(T actual, Matcher<T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason);
description.appendText("\nExpected: ");
matcher.describeTo(description);
description
.appendText("\n got: ")
.appendValue(actual)
.appendText("\n");
throw new java.lang.AssertionError(description.toString());
}
}
Ответы:
Во-первых, я должен направить вас на http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html - она делает потрясающую работу.
Основная идея заключается в том, что вы используете
когда фактический параметр может быть
SomeClass
или любой его подтип.В вашем примере
Вы говорите, что
expected
можете содержать объекты Class, которые представляют любой реализуемый классSerializable
. Ваша карта результатов говорит, что она может содержать толькоDate
объекты классов.Когда вы передаете в результате вы устанавливаете
T
точноMap
изString
кDate
объектам класса, которые не соответствуютMap
отString
ни к чему , что этоSerializable
.Одно нужно проверить - ты уверен, что хочешь,
Class<Date>
а нетDate
? КартаString
toClass<Date>
не звучит ужасно полезной в целом (все, что она может содержать, этоDate.class
значения, а не экземплярыDate
)Что касается обобщения
assertThat
, идея заключается в том, что метод может гарантировать,Matcher
что передается a, который соответствует типу результата.источник
Спасибо всем, кто ответил на вопрос, это действительно помогло мне прояснить ситуацию. В конце концов ответ Скотта Стэнчфилда ближе всего подошел к тому, как я это понял, но так как я не понял его, когда он впервые написал это, я пытаюсь сформулировать проблему так, чтобы, надеюсь, кто-то другой выиграл.
Я собираюсь переформулировать вопрос в терминах List, поскольку он имеет только один общий параметр, и это облегчит его понимание.
Назначение параметризованного класса (такого как List
<Date>
или Map,<K, V>
как в примере) состоит в том, чтобы вызвать downcast и иметь гарантию компилятора, что это безопасно (без исключений времени выполнения).Рассмотрим случай List. Суть моего вопроса заключается в том, почему метод, который принимает тип T и List, не будет принимать список чего-то еще ниже по цепочке наследования, чем T. Рассмотрим этот надуманный пример:
Это не скомпилируется, потому что параметр списка - это список дат, а не список строк. Дженерики не будут очень полезны, если это скомпилировать.
То же самое относится и к карте.
<String, Class<? extends Serializable>>
Это не то же самое, что карта<String, Class<java.util.Date>>
. Они не являются ковариантными, поэтому, если я хотел взять значение из карты, содержащей классы дат, и поместить его в карту, содержащую сериализуемые элементы, это нормально, но сигнатура метода, которая говорит:Хочет иметь возможность сделать оба:
и
В этом случае, даже если метод junit на самом деле не заботится об этих вещах, сигнатура метода требует ковариации, которую он не получает, поэтому он не компилируется.
По второму вопросу
Недостатком было бы действительно принимать что-либо, когда T - это объект, который не является целью API. Цель состоит в том, чтобы статически убедиться, что сопоставитель соответствует фактическому объекту, и нет никакого способа исключить Object из этого вычисления.
Ответ на третий вопрос заключается в том, что ничего не будет потеряно с точки зрения неконтролируемой функциональности (не было бы небезопасного встраивания типов в JUnit API, если бы этот метод не был обобщен), но они пытаются выполнить что-то другое - статически убедитесь, что два параметра могут совпадать.
РЕДАКТИРОВАТЬ (после дальнейшего созерцания и опыта):
Одна из больших проблем с сигнатурой метода assertThat - попытки приравнять переменную T к универсальному параметру T. Это не работает, потому что они не являются ковариантными. Так, например, у вас может быть буква T, которая представляет собой,
List<String>
но затем передает совпадение, с которым работает компиляторMatcher<ArrayList<T>>
. Теперь, если бы это не был параметр типа, все было бы хорошо, потому что List и ArrayList ковариантны, но, поскольку Generics, с точки зрения компилятора, требует ArrayList, он не может допустить List по причинам, которые, я надеюсь, понятны из вышесказанного.источник
List<Date>
from с типомList<Object>
? Это должно быть правильно, даже если это не разрешено Java.Это сводится к:
Вы можете видеть, что ссылка на класс c1 может содержать экземпляр Long (поскольку базовый объект в данное время мог бы быть
List<Long>
), но, очевидно, не может быть приведена к Date, поскольку нет никакой гарантии, что «неизвестным» классом был Date. Это не типично безопасно, поэтому компилятор запрещает это.Однако, если мы представим какой-нибудь другой объект, скажем List (в вашем примере этот объект является Matcher), то следующее становится истинным:
... Однако, если тип списка становится? расширяет T вместо T ....
Я думаю, что изменяя
Matcher<T> to Matcher<? extends T>
, вы в основном вводите сценарий, похожий на назначение l1 = l2;Все еще очень запутанно иметь вложенные подстановочные знаки, но, надеюсь, это имеет смысл, поскольку это помогает понять обобщенные элементы, глядя на то, как вы можете назначать обобщенные ссылки друг на друга. Это также еще более сбивает с толку, поскольку компилятор выводит тип T при выполнении вызова функции (вы явно не говорите, что это был T).
источник
Причина, по которой исходный код не компилируется, заключается в том, что
<? extends Serializable>
это не означает «любой класс, расширяющий Serializable», но «некоторый неизвестный, но определенный класс, расширяющий Serializable».Например, если код , как написано, это вполне допустимо назначать
new TreeMap<String, Long.class>()>
вexpected
. Если бы компилятор позволил скомпилировать код,assertThat()
он, вероятно, сломался бы, потому что ожидал быDate
объекты вместоLong
объектов, найденных на карте.источник
<Serializable>
Один из способов понять подстановочные знаки - это подумать, что подстановочный знак не указывает тип возможных объектов, которые может иметь данная ссылка, а тип других общих ссылок, с которыми он совместим (это может показаться странным ...) Таким образом, первый ответ вводит в заблуждение.
Другими словами,
List<? extends Serializable>
означает, что вы можете назначить эту ссылку другим спискам, где типом является некоторый неизвестный тип, который является или подклассом Serializable. НЕ думайте об этом с точки зрения того, что ЕДИНЫЙ СПИСОК может содержать подклассы Serializable (потому что это неверная семантика и приводит к неправильному пониманию Generics).источник
<? extends T>
компилируется?Я знаю, что это старый вопрос, но я хочу поделиться примером, который, я думаю, довольно хорошо объясняет ограниченные подстановочные знаки.
java.util.Collections
предлагает этот метод:Если у нас есть список
T
, список, конечно, может содержать экземпляры типов, которые расширяютсяT
. Если список содержит животных, список может содержать как собак, так и кошек (оба животных). Собаки имеют свойство "woofVolume", а кошки имеют свойство "meowVolume". В то время как мы могли бы сортировать, основываясь на этих свойствах, относящихся к подклассамT
, как мы можем ожидать, что этот метод сделает это? Ограничением Comparator является то, что он может сравнивать только две вещи только одного типа (T
). Таким образом, требование простоComparator<T>
сделало бы этот метод пригодным для использования. Но создатель этого метода признал, что если что-то являетсяT
, то это также экземпляр суперклассовT
. Таким образом, он позволяет использовать компараторT
или любой суперклассT
, то есть? super T
.источник
Что делать, если вы используете
источник