Ответ 1
Взяв ваш первоначальный вопрос буквально: "Есть ли заметная разница" между Generic<?>
и Generic<? extends BaseType>
, ответ должен быть, они не эквивалентны.
JLS §4.5.1 четко заявляет:
Подстановочный знак
? extends Object
эквивалентен неограниченному шаблону?
.
Таким образом, это будет эквивалентно ? extends BaseType
, только если BaseType
равно Object
, но даже тогда они эквивалентны, но все еще имеют заметные отличия, например. в местах, где конверсия захвата не происходит:
boolean b1 = new Object() instanceof Supplier<?>; // valid code
boolean b2 = new Object() instanceof Supplier<? extends Object>; // invalid
Supplier<?>[] array1; // valid declaration
Supplier<? extends Object>[] array1; // invalid
Возможно, стоит отметить, что в отличие от первой интуиции, учитывая объявление Generic<T extends BaseType>
, указание Generic<? extends Object>
столь же корректно, как и эквивалентное Generic<?>
. Оценки подстановочного знака действительны, если они не являются явно различимыми с параметрами типа, и поскольку границы всегда являются подтипами Object
, ? extends Object
всегда действителен.
Итак, если у нас есть объявление типа, например
interface NumberSupplier<N extends Number> extends Supplier<N> {}
мы можем написать
NumberSupplier<? extends Object> s1;
NumberSupplier<? extends Serializable> s2;
NumberSupplier<? extends BigInteger> s3;
или даже
NumberSupplier<? extends CharSequence> s4;
Мы можем даже реализовать его без фактического типа, расширяющего Number
и CharSequence
с помощью () -> null
но не
NumberSupplier<? extends String> s5;
как String
и Number
доказуемо различны.
Когда дело доходит до присвоений, мы можем использовать уже описанные в вопросе правила подтипирования, чтобы заключить, что NumberSupplier<? extends BigInteger>
является подтипом NumberSupplier<? extends Object>
, потому что ? extends BigInteger
содержит ? extends Object
(а также содержит ? extends Number
), поскольку BigInteger
является подтипом Object
и Number
, но, как вы правильно отметили, это не относится к параметризованным типам, аргументы типа которых не являются подстановочными знаками.
Итак, если у нас есть такие объявления, как List<NumberSupplier<?>>
, List<NumberSupplier<? extends Object>>
или List<NumberSupplier<? extends Number>>
, и вы хотите узнать, есть ли подтип других в соответствии с § 4.3.5, содержит правило, единственным правилом, которое может применяться, является, когда аргументы типа являются одним и тем же типом (T <= T
), но тогда нам не нужны правила подтипирования, так как тогда все эти типы списков являются одинаковыми:
Два ссылочных типа - это один и тот же тип времени компиляции, если они имеют одинаковое двоичное имя (§13.1), и их аргументы типа, если они есть, одинаковы, применяя это определение рекурсивно.
Правило содержит все еще полезное, например. он позволяет заключить, что Map<String,? extends Number>
является подтипом Map<String,Integer>
, потому что для аргумента первого типа String <= String
применяется, а аргументы типа во втором типе параметров покрываются специальным шаблоном, содержащим правило.
Итак, оставшийся вопрос: какое правило позволяет сделать вывод, что NumberSupplier<?>
, NumberSupplier<? extends Object>
или NumberSupplier<? extends Number>
являются одинаковыми типами, так что List<NumberSupplier<?>>
, List<NumberSupplier<? extends Object>>
или List<NumberSupplier<? extends Number>>
можно присваивать друг друга.
Кажется, что это не конверсия захвата, так как преобразование захвата подразумевало бы вычисление эффективных границ, но также создавало "новую переменную типа" для каждого шаблона, который определенно отличается от типа. Но нет другого правила, охватывающего совместимость. Или я его не нашел. Попытка сопоставить спецификацию с фактическим поведением javac
имела некоторые очень интересные результаты:
Учитывая
interface MySupplier<S extends CharSequence&Appendable> extends Supplier<S> {}
Очевидно, что следующие утверждения:
List<MySupplier<? extends CharSequence>> list1 = Collections.emptyList();
List<MySupplier<? extends Appendable>> list2 = Collections.emptyList();
Поскольку в обоих случаях привязка подстановочных знаков является избыточной, поскольку она соответствует одной из привязанных S
s, мы можем догадаться, что они фактически одного и того же типа.
Но javac
думает, что они не
list1 = list2; // compiler error
list2 = list1; // dito
хотя любая операция, связанная с преобразованием захвата, завершает совместимые типы, например.
list1.set(0, list2.get(0)); // no problem
list2.set(0, list1.get(0)); // no problem
и выполняет отклоненные задания косвенно:
List<MySupplier<?>> list3;
list3 = list1;
list2 = list3; // no problem
// or
list3 = list2;
list1 = list3; // again no problem
но здесь ?
не эквивалентно ? extends Object
:
List<MySupplier<? extends Object>> list4;
list4 = list1; // compiler error
list2 = list4; // dito
// or
list4 = list2; // dito
list1 = list4; // dito
но опять же, косвенные назначения работают.
list4 = list3 = list1; // works
list1 = list3 = list4; // works
list4 = list3 = list2; // works
list2 = list3 = list4; // works
Итак, любое правило javac
использует здесь, его непереходное, которое исключает отношения подтипа, а также общее правило "его того же типа". Кажется, что это действительно un (der) указано и непосредственно влияет на реализацию. И, как в настоящее время реализовано, ?
без границ является особенным, позволяя цепочке присваивания не возможно с любым другим типом подстановочного знака.