Когда для генерации 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) 

Сообщение об ошибке компилятора:

 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) 

Затем компиляция работает.

Итак, три вопроса:

  1. Почему точно не компилируется текущая версия? Хотя я смутно понимаю проблемы ковариации здесь, я, конечно, не мог объяснить это, если бы мне пришлось.
  2. Есть ли недостаток в изменении метода assertThat для Matcher Matcher ? Существуют ли другие случаи, которые могут сломаться, если вы это сделаете?
  3. Есть ли какая-либо точка для обобщения метода 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()); } } 

Во-первых – я должен направить вас на 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 

Имел бы недостаток в том, чтобы действительно принимать что-либо, когда T – объект, который не является целью API. objective состоит в том, чтобы статически убедиться, что совпадение соответствует фактическому объекту, и нет способа исключить объект из этого вычисления.

Ответ на третий вопрос заключается в том, что ничто не будет потеряно с точки зрения неконтролируемой функциональности (в JUnit API не было бы опасного приведения типов, если этот метод не был обобщен), но они пытаются выполнить что-то еще – статически убедитесь, что возможны два параметра.

EDIT (после дальнейшего созерцания и опыта):

Одной из больших проблем с сигнатурой метода assertThat является попытка приравнять переменную T с общим параметром T. Это не работает, потому что они не ковариантны. Так, например, у вас может быть T, который является List но затем передайте соответствие, которое компилятор выполнит в Matcher> . Если бы это был не параметр типа, все было бы хорошо, потому что List и ArrayList ковариантны, но поскольку Generics, в отношении компилятора, требует ArrayList, он не может переносить Список по причинам, которые, я надеюсь, ясны из приведенного выше.

Это сводится к:

 Class 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> l1 = null; List> l2 = null; l1 = l2; // compiles l2 = l1; // won't compile 

Я думаю, изменив Matcher to Matcher Matcher to Matcher , вы в основном представляете сценарий, аналогичный назначению l1 = l2;

Это все еще очень запутывает наличие вложенных подстановочных знаков, но, надеюсь, имеет смысл, почему это помогает понять дженерики, глядя на то, как вы можете назначить общие ссылки друг другу. Это также путано, поскольку компилятор выводит тип T, когда вы вызываете вызов функции (вы явно не говорите, что это T).

Причина, по которой ваш исходный код не компилируется, заключается в том, что не означает «любой class, который расширяет Serializable», но «какой-то неизвестный, но специфический class, который расширяет Serializable».

Например, учитывая код как написанный, вполне можно назначить new TreeMap()> для new TreeMap()> . Если компилятор разрешил компиляцию кода, assertThat() предположительно сломается, потому что он будет ожидать объекты Date вместо объектов Long он находит на карте.

Один из способов понять подстановочные знаки – это думать, что подстановочный знак не указывает тип возможных объектов, которые задают общую ссылку, может иметь «есть», но тип других общих ссылок, с которыми он совместим (это может показаться запутанным …) Таким образом, первый ответ очень вводит в заблуждение в его формулировке.

Другими словами, List List означает, что вы можете назначить эту ссылку другим спискам, где тип – это неизвестный тип, который является или подclassом Serializable. НЕ думайте об этом с точки зрения того, что SINGLE LIST способен удерживать подclassы Serializable (потому что это некорректная семантика и приводит к недоразумению Generics).

Я знаю, что это старый вопрос, но я хочу поделиться примером, который, как мне кажется, объясняет ограниченные подстановочные знаки. java.util.Collections предлагает этот метод:

 public static  void sort(List list, Comparator 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; 
  • Сервлеты для тестирования модhive
  • Получить имя текущего исполняемого теста в JUnit 4
  • Как запустить все тесты, принадлежащие определенной категории в JUnit 4
  • JUnit confusion: использовать 'extends TestCase' или '@Test'?
  • Встроенный MongoDB при выполнении интеграционных тестов
  • Получение «NoSuchMethodError: org.hamcrest.Matcher.describeMismatch» при запуске теста в IntelliJ 10.5
  • Передайте локальный файл в URL в Java
  • Как я могу создать тестовый набор в JUnit 4?
  • Как сделать тестовые примеры JUnit параллельными?
  • java.lang.NoClassDefFoundError: com / sun / mail / util / MailLogger для тестового примера JUnit для Java-почты
  • Как проверить абстрактный class на Java с помощью JUnit?
  • Давайте будем гением компьютера.