Когда для генерации Java требуется вместо и есть ли недостаток переключения?
В следующем примере (используя JUnit с совпадениями Hamcrest):
Map<String, Class> expected = null; Map<String, Class> result = null; assertThat(result, is(expected));
Это не скомпилировано с assertThat
подписи JUnit assertThat
:
public static void assertThat(T actual, Matcher matcher)
Сообщение об ошибке компилятора:
- Условно игнорирование тестов в JUnit 4
- Тестирование JUnit с использованием имитируемого пользовательского ввода
- Как запустить тесты JUnit из моего java-приложения?
- Сравните два объекта JSON в Java
- Как выполнить повторный запуск неудачных тестов JUnit?
Error:Error:line (102)cannot find symbol method assertThat(java.util.Map<java.lang.String,java.lang.Class>, org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class >>)
Однако, если я изменил assertThat
метода assertThat
на:
public static void assertThat(T result, Matcher matcher)
Затем компиляция работает.
Итак, три вопроса:
- Почему точно не компилируется текущая версия? Хотя я смутно понимаю проблемы ковариации здесь, я, конечно, не мог объяснить это, если бы мне пришлось.
- Есть ли недостаток в изменении метода
assertThat
дляMatcher
Matcher
? Существуют ли другие случаи, которые могут сломаться, если вы это сделаете? - Есть ли какая-либо точка для обобщения метода
assertThat
в JUnit? КлассMatcher
, похоже, не требует этого, поскольку JUnit вызывает метод совпадений, который не типизирован с каким-либо общим, и просто выглядит как попытка принудительного сохранения типа, который ничего не делает, посколькуMatcher
просто не будет на самом деле, и тест будет терпеть неудачу. Никаких опасных операций (или, как кажется).
Для справки, вот реализация JUnit assertThat
:
public static void assertThat(T actual, Matcher matcher) { assertThat("", actual, matcher); } public static void assertThat(String reason, T actual, Matcher 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()); } }
- Спекуляция для JUnit XML Output
- В Java как я могу проверить исключение с JUnit?
- В чем разница между ошибкой и ошибкой в JUnit?
- Утверждение о списке в Junit
- Как откатить транзакцию базы данных при тестировании служб с помощью Spring в JUnit?
- IntelliJ IDEA с Junit 4.7 "!!! Ожидается, что версия JUnit версии 3.8 или более поздняя:
- Группировка тестов JUnit
- Инициализация ложных объектов - MockIto
Во-первых – я должен направить вас на http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html – она делает удивительную работу.
Основная идея заключается в том, что вы используете
когда фактическим параметром может быть SomeClass
или любой его подтип.
В вашем примере,
Map> expected = null; Map> result = null; assertThat(result, is(expected));
Вы говорите, что expected
может содержать объекты classа, которые представляют любой class, который реализует Serializable
. Ваша карта результатов говорит, что она может содержать только объекты classа Date
.
Когда вы передаете результат, вы устанавливаете T
точно на объекты classа объектов String
to Date
, которые не соответствуют Map
of String
для всего, что является Serializable
.
Одна вещь для проверки – вы уверены, что хотите Class
а не Date
? Карта String
to Class
не звучит ужасно полезной вообще (все, что она может содержать, это Date.class
как значения, а не экземпляры Date
)
Что касается обобщения assertThat
, то идея заключается в том, что метод может гарантировать, что Matcher
который соответствует типу результата, передается.
Спасибо всем, кто ответил на вопрос, это действительно помогло мне прояснить ситуацию. В конце концов ответ Скотта Стейнфилда был ближе всего к тому, как я это понял, но так как я не понял его, когда он впервые написал его, я пытаюсь переформулировать проблему, чтобы, надеюсь, кто-то еще выиграл.
Я собираюсь повторить вопрос в терминах List, так как он имеет только один общий параметр, и это упростит его понимание.
objective параметризованного classа (например, List
или Map
как в примере) заключается в том, чтобы заставить downcast и предоставить компилятору гарантию того, что это безопасно (без исключений во время выполнения).
Рассмотрим случай Списка. Суть моего вопроса заключается в том, почему метод, который принимает тип T и список, не будет принимать список чего-либо дальше по цепочке наследования, чем T. Рассмотрим этот надуманный пример:
List dateList = new ArrayList(); Serializable s = new String(); addGeneric(s, dateList); .... private void addGeneric(T element, List list) { list.add(element); }
Это не будет компилироваться, потому что параметр list – это список дат, а не список строк. Дженерики не были бы очень полезными, если бы это скомпилировалось.
То же самое относится к Map
Это не то же самое, что Map
. Они не ковариантны, поэтому, если бы я хотел взять значение с карты, содержащей classы дат, и поместить ее в карту, содержащую сериализуемые элементы, это нормально, но подпись метода, которая гласит:
private void genericAdd(T value, List list)
Хочет иметь возможность сделать так:
T x = list.get(0);
а также
list.add(value);
В этом случае, хотя метод junit действительно не заботится об этих вещах, для подписи метода требуется ковариация, которую она не получает, поэтому она не компилируется.
По второму вопросу,
Matcher extends T>
Имел бы недостаток в том, чтобы действительно принимать что-либо, когда T – объект, который не является целью API. objective состоит в том, чтобы статически убедиться, что совпадение соответствует фактическому объекту, и нет способа исключить объект из этого вычисления.
Ответ на третий вопрос заключается в том, что ничто не будет потеряно с точки зрения неконтролируемой функциональности (в JUnit API не было бы опасного приведения типов, если этот метод не был обобщен), но они пытаются выполнить что-то еще – статически убедитесь, что возможны два параметра.
EDIT (после дальнейшего созерцания и опыта):
Одной из больших проблем с сигнатурой метода assertThat является попытка приравнять переменную T с общим параметром T. Это не работает, потому что они не ковариантны. Так, например, у вас может быть T, который является List
но затем передайте соответствие, которое компилятор выполнит в Matcher
. Если бы это был не параметр типа, все было бы хорошо, потому что List и ArrayList ковариантны, но поскольку Generics, в отношении компилятора, требует ArrayList, он не может переносить Список по причинам, которые, я надеюсь, ясны из приведенного выше.
Это сводится к:
Class extends Serializable> c1 = null; Class d1 = null; c1 = d1; // compiles d1 = c1; // wont compile - would require cast to Date
Вы можете видеть, что ссылка classа c1 может содержать длинный экземпляр (поскольку базовый объект в заданное время мог быть List
), но, очевидно, нельзя отнести к дате, поскольку нет гарантии, что «неизвестный» class был Дата. Это не typisafe, поэтому компилятор его запрещает.
Однако, если мы введем какой-либо другой объект, скажем, List (в вашем примере этот объект является Matcher), тогда будет выполняться следующее:
List> l1 = null; List> l2 = null; l1 = l2; // wont compile l2 = l1; // wont compile
… Однако, если тип списка становится? расширяет T вместо T ….
List extends Class extends Serializable>> l1 = null; List extends Class> l2 = null; l1 = l2; // compiles l2 = l1; // won't compile
Я думаю, изменив Matcher
Matcher
, вы в основном представляете сценарий, аналогичный назначению l1 = l2;
Это все еще очень запутывает наличие вложенных подстановочных знаков, но, надеюсь, имеет смысл, почему это помогает понять дженерики, глядя на то, как вы можете назначить общие ссылки друг другу. Это также путано, поскольку компилятор выводит тип T, когда вы вызываете вызов функции (вы явно не говорите, что это T).
Причина, по которой ваш исходный код не компилируется, заключается в том, что extends Serializable>
extends Serializable>
не означает «любой class, который расширяет Serializable», но «какой-то неизвестный, но специфический class, который расширяет Serializable».
Например, учитывая код как написанный, вполне можно назначить new TreeMap
для new TreeMap
. Если компилятор разрешил компиляцию кода, assertThat()
предположительно сломается, потому что он будет ожидать объекты Date
вместо объектов Long
он находит на карте.
Один из способов понять подстановочные знаки – это думать, что подстановочный знак не указывает тип возможных объектов, которые задают общую ссылку, может иметь «есть», но тип других общих ссылок, с которыми он совместим (это может показаться запутанным …) Таким образом, первый ответ очень вводит в заблуждение в его формулировке.
Другими словами, List extends Serializable>
List extends Serializable>
означает, что вы можете назначить эту ссылку другим спискам, где тип – это неизвестный тип, который является или подclassом Serializable. НЕ думайте об этом с точки зрения того, что SINGLE LIST способен удерживать подclassы Serializable (потому что это некорректная семантика и приводит к недоразумению Generics).
Я знаю, что это старый вопрос, но я хочу поделиться примером, который, как мне кажется, объясняет ограниченные подстановочные знаки. java.util.Collections
предлагает этот метод:
public static void sort(List list, Comparator super T> c) { list.sort(c); }
Если у нас есть список T
, Список может, конечно, содержать экземпляры типов, которые расширяют T
Если Список содержит Животные, Список может содержать как Собаки, так и Кошки (оба Животные). Собаки имеют свойство «woofVolume», а у кошек есть свойство «meowVolume». Хотя нам может нравиться сортировка на основе этих свойств, особенно для подclassов T
, как мы можем ожидать, что этот метод будет делать это? Ограничение компаратора заключается в том, что он может сравнивать только две вещи только одного типа ( T
). Поэтому, требуя просто Comparator
, этот метод будет использоваться. Но создатель этого метода признал, что если что-то есть T
, то это также пример суперclassов T
Поэтому он позволяет использовать компаратор T
или любой суперclass из T
, т. Е. ? super T
? super T
что, если вы используете
Map> expected = null;