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


2 Answers

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

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

Цель параметризованного класса (например, List <Date> или Map <K, V> как в примере) заключается в том, чтобы заставить downcast и предоставить компилятору гарантию того, что это безопасно (без исключений во время выполнения).

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

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

То же самое относится к Map <String, Class<? extends Serializable>> <String, Class<? extends Serializable>> Это не то же самое, что Map <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. Цель состоит в том, чтобы статически убедиться, что совпадение соответствует фактическому объекту, и нет способа исключить объект из этого вычисления.

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

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

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

Question

В следующем примере (используя JUnit с совпадениями Hamcrest):

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

Это не скомпилировано с assertThat подписи JUnit assertThat :

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 метода assertThat на:

public static <T> void assertThat(T result, Matcher<? extends T> matcher)

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

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

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



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

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

Если у нас есть список T , Список может, конечно, содержать экземпляры типов, которые расширяют T Если Список содержит Животные, Список может содержать как Собаки, так и Кошки (оба Животные). Собаки имеют свойство «woofVolume», а у кошек есть свойство «meowVolume». Хотя нам может нравиться сортировка на основе этих свойств, особенно для подклассов T , как мы можем ожидать, что этот метод будет делать это? Ограничение компаратора заключается в том, что он может сравнивать только две вещи только одного типа ( T ). Поэтому, требуя просто Comparator<T> , этот метод будет использоваться. Но создатель этого метода признал, что если что-то есть T , то это также пример суперклассов T Поэтому он позволяет использовать компаратор T или любой суперкласс из T , т. Е. ? super T ? super T




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

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




Related