Когда дженерики Java требуют <? расширяется T> вместо <T> и там какая-либо оборотная сторона переключения?

для сопоставления действительного IP-адреса с регулярным выражением используйте

^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$

вместо

^([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])){3}$

, поскольку многие механизмы регулярного выражения соответствуют первой возможности в последовательности ИЛИ

Вы можете попробовать свой движок регулярных выражений: 10.48.0.200

проверить разницу здесь

190
задан blacktide 9 June 2016 в 12:38
поделиться

6 ответов

Во-первых - я должен направить вас на http://www.angelikalanger.com/GenericsFAQ /JavaGenericsFAQ.html - она ​​делает потрясающую работу.

Основная идея состоит в том, что вы используете

<T extends SomeClass>

, когда фактический параметр может быть SomeClass или любым его подтипом.

В вашем примере

Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));

вы говорите, что ожидаемый может содержать объекты Class, которые представляют любой класс, реализующий Serializable . В вашей карте результатов указано, что она может содержать только объекты класса Date .

Когда вы передаете результат, вы устанавливаете T ровно на Map из ] String to Date объекты класса, который не соответствует Map of String чему-либо, что ' s Сериализуемый .

Одна вещь, которую нужно проверить - вы уверены, что хотите Class , а не Date ? Отображение String в Class в целом не кажется очень полезным (все, что он может содержать, это Date.class как значения, а не экземпляры Дата )

Что касается обобщения assertThat , идея заключается в том, что метод может гарантировать, что передается Matcher , который соответствует типу результата.

132
ответ дан 23 November 2019 в 05:37
поделиться

Это сводится к следующему:

Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date

Вы можете видеть, что ссылка на класс c1 может содержать экземпляр Long (поскольку базовый объект в данный момент времени мог быть List ), но, очевидно, не может быть приведен к Date, поскольку нет гарантии, что "неизвестный" класс был Date. Это небезопасно для типов, поэтому компилятор запрещает это.

Однако, если мы вводим какой-либо другой объект, скажем, List (в вашем примере это объект Matcher), то становится истинным следующее:

List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile

... Однако, если вид Списка делается? расширяет T вместо T ....

List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile

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

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

14
ответ дан 23 November 2019 в 05:37
поделиться

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

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

9
ответ дан 23 November 2019 в 05:37
поделиться

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

Другими словами, List означает, что вы можете назначить эту ссылку другим спискам, где типом является некоторый неизвестный тип, который является или подклассом Serializable. НЕ думайте об этом с точки зрения того, что ЕДИНЫЙ СПИСОК может содержать подклассы Serializable (потому что это неправильная семантика и приводит к неправильному пониманию Generics).

8
ответ дан 23 November 2019 в 05:37
поделиться

что делать, если вы используете

Map<String, ? extends Class<? extends Serializable>> expected = null;
1
ответ дан 23 November 2019 в 05:37
поделиться

Спасибо всем, кто ответил на вопрос, это действительно помогло мне прояснить ситуацию. В конце концов, ответ Скотта Стэнчфилда был наиболее близок к тому, как я его понял, но, поскольку я не понимал его, когда он впервые написал его, я пытаюсь переформулировать проблему, чтобы, надеюсь, кому-то другому это принесет пользу.

Я Я собираюсь переформулировать вопрос в терминах List, поскольку он имеет только один общий параметр, и это упростит понимание.

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

случай List. Суть моего вопроса заключается в том, почему метод, который принимает тип T и список, не принимает список чего-то более низкого по цепочке наследования, чем 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);
}

Это не будет компилироваться, потому что список Параметр - это список дат, а не список строк. Обобщения не были бы очень полезны, если бы это было скомпилировано.

То же самое применимо к Map > Это не то же самое, что Map > . Они не ковариантны, поэтому, если бы я хотел взять значение из карты, содержащей классы дат, и поместить его на карту, содержащую сериализуемые элементы, это нормально, но подпись метода, которая гласит:

private <T> void genericAdd(T value, List<T> list)

Хочет иметь возможность делать и то, и другое. :

T x = list.get(0);

и

list.add(value);

В этом случае, хотя метод junit не На самом деле, это важно, сигнатура метода требует ковариации, которую он не получает, поэтому не компилируется.

По второму вопросу,

Matcher<? extends T>

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

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

РЕДАКТИРОВАТЬ (после дальнейшего размышления и опыта):

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

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

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

26
ответ дан 23 November 2019 в 05:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: