Подтвердить что ты не робот

Общий метод с параметрами против не общего метода с подстановочными знаками

В соответствии с этой записью в Часто задаваемых вопросах Java Generics, есть некоторые обстоятельства, когда общий метод не имеет эквивалентного не общего метода, который использует подстановочный знак типы. Согласно этому ответу,

Если сигнатура метода использует многоуровневые подстановочные типы, всегда существует разница между сигнатурой общего метода и версией подстановочного знака.

Они приводят пример метода <T> void print1( List <Box<T>> list), который "требует список ящиков того же типа". Подстановочная версия void print2( List <Box<?>> list), "принимает гетерогенный список ящиков разных типов" и, следовательно, не эквивалентна.

Как вы интерпретируете различия между следующими двумя сигнатурами метода:

 <T extends Iterable<?>> void f(Class<T> x) {}
                         void g(Class<? extends Iterable<?>> x) {}

Интуитивно, кажется, что эти определения должны быть эквивалентными. Однако вызов f(ArrayList.class) компилируется с использованием первого метода, но вызов g(ArrayList.class) с использованием второго метода приводит к ошибке времени компиляции:

g(java.lang.Class<? extends java.lang.Iterable<?>>) in Test
    cannot be applied to (java.lang.Class<java.util.ArrayList>)

Интересно, что обе функции можно вызывать с аргументами друг друга, потому что следующие компиляции:

class Test {
    <T extends Iterable<?>> void f(Class<T> x) {
        g(x);
    }
    void g(Class<? extends Iterable<?>> x) {
        f(x);
    }
}

Используя javap -verbose Test, я вижу, что f() имеет общую сигнатуру

<T::Ljava/lang/Iterable<*>;>(Ljava/lang/Class<TT;>;)V;

и g() имеет общую сигнатуру

(Ljava/lang/Class<+Ljava/lang/Iterable<*>;>;)V;

Чем объясняется это поведение? Как интерпретировать различия между сигнатурами этих методов?

4b9b3361

Ответ 1

Ну, идя по спецификации, ни вызов не является законным. Но почему первый тип проверяет, а второй - нет?

Разница заключается в том, как проверяются методы применимости (см. §15.12.2 и §15.12.2.2 в частности).

  • Для простого неприменимого g, аргумент Class<ArrayList> должен быть подтипом Class<? extends Iterable<?>>. Это означает, что ? extends Iterable<?> должен содержать ArrayList, написанный ArrayList <= ? extends Iterable<?>. Правила 4 и 1 могут применяться транзитивно, так что ArrayList должен быть подтипом Iterable<?>.

    Переход §4.10.2 любая параметризация C<...> является (прямым) подтипом необработанного типа C. Таким образом, ArrayList<?> является подтипом ArrayList, но не наоборот. Transitively, ArrayList не является подтипом Iterable<?>.

    Таким образом, g не применимо.

  • f является общим, для простоты предположим, что аргумент типа ArrayList явно указан. Чтобы проверить f применимость, Class<ArrayList> должен быть подтипом Class<T> [T=ArrayList] = Class<ArrayList>. Поскольку подтипирование reflexisve, это правда.

    Также для применимости f аргумент типа должен быть в пределах своих границ. Это не потому, что, как мы показали выше, ArrayList не является подтипом Iterable<?>.

Итак, почему он компилируется в любом случае?

Это ошибка. После отчета об ошибке и последующее исправление JDT-компилятор явно задает правила из первого случая (сдерживание аргумента типа). Второй случай все еще счастливо игнорируется, поскольку JDT считает ArrayList подтипом Iterable<?> (TypeBinding.isCompatibleWith(TypeBinding)).

Я не знаю, почему джавак ведет себя одинаково, но я предполагаю по тем же причинам. Вы заметите, что javac не выдаёт непроверенное предупреждение при назначении raw ArrayList на Iterable<?>.

Ответ 2

Если параметр типа был параметром с подстановочным параметром, то проблема не возникает:

Class<ArrayList<?>> foo = null;
f(foo);
g(foo);

Я думаю, что это почти наверняка странный случай, связанный с тем, что тип литерала класса Class<ArrayList>, и поэтому параметр type в этом случае (ArrayList) является сырым типом, а подтипирование взаимосвязь между raw ArrayList и параметром ArrayList<?> с подстановочными знаками сложна.

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

Ответ 3

Угадай: вещь, представляющая первый? (ArrayList) не "реализует" ArrayList<E> (в силу двойного вложенного шаблона). Я знаю, это звучит смешно, но...

Рассмотрим (для исходного списка):

 void g(Class<? extends Iterable<Object> x) {} // Fail
 void g(Class<? extends Iterable<?> x) {}  // Fail
 void g(Class<? extends Iterable x) {}  // OK

и

// Compiles
public class Test{
    <T extends Iterable<?>> void f(ArrayList<T> x) {}
    void g(ArrayList<? extends Iterable<?>> x) {}

    void d(){
        ArrayList<ArrayList<Integer>> d = new ArrayList<ArrayList<Integer>>();
        f(d);
        g(d);
    }
}

Это

// Does not compile on g(d)
public class Test{
    <T extends Iterable<?>> void f(ArrayList<T> x) {}
    void g(ArrayList<? extends Iterable<?>> x) {}

    void d(){
        ArrayList<ArrayList> d = new ArrayList<ArrayList>();
        f(d);
        g(d);
    }
}

Ответ 4

Это не совсем то же самое:

<T extends Iterable<?>> void f(Class<T> x) {}
void g(Class<? extends Iterable<?>> x) {}

Разница заключается в том, что g принимает "класс неизвестности, который реализует Iterable из неизвестного", но ArrayList<T> имеет ограничение Iterable<T>, а не Iterable<?>, поэтому он не соответствует.

Чтобы сделать его понятным, g примет Foo implements Iterable<?>, но не AraryList<T> implements Iterable<T>.